gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: more work on new history logic


From: gnunet
Subject: [taler-exchange] branch master updated: more work on new history logic
Date: Mon, 18 Sep 2023 22:15:39 +0200

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

grothoff pushed a commit to branch master
in repository exchange.

The following commit(s) were added to refs/heads/master by this push:
     new dfe576f9 more work on new history logic
dfe576f9 is described below

commit dfe576f9379954ab8164da7521bef930d3af3948
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon Sep 18 22:11:28 2023 +0200

    more work on new history logic
---
 src/exchange/taler-exchange-httpd.c                |  110 +-
 src/exchange/taler-exchange-httpd_coins_get.h      |    2 +-
 src/exchange/taler-exchange-httpd_reserves_get.c   |   20 +-
 src/exchange/taler-exchange-httpd_reserves_get.h   |    9 +-
 .../taler-exchange-httpd_reserves_history.c        |   52 +-
 .../taler-exchange-httpd_reserves_history.h        |   12 +-
 src/exchange/taler-exchange-httpd_responses.h      |    2 +-
 src/include/taler_exchange_service.h               |   59 -
 src/include/taler_mhd_lib.h                        |   44 +
 src/include/taler_util.h                           |   10 +
 src/lib/exchange_api_age_withdraw.c                |  126 +-
 src/lib/exchange_api_batch_withdraw2.c             |  113 +-
 src/lib/exchange_api_coins_history.c               | 1290 ++++++++++++++++
 src/lib/exchange_api_common.c                      | 1633 +-------------------
 src/lib/exchange_api_common.h                      |   37 -
 src/lib/exchange_api_reserves_history.c            |  784 +++++++++-
 src/mhd/mhd_parsing.c                              |   36 +
 17 files changed, 2275 insertions(+), 2064 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index d059e743..7129f6d1 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -402,14 +402,17 @@ handle_get_coins (struct TEH_RequestContext *rc,
                                        
TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
                                        args[0]);
   }
-
-  if (NULL == args[1])
-    return TEH_handler_coins_get (rc,
-                                  &coin_pub);
-  if (0 == strcmp (args[1],
-                   "link"))
-    return TEH_handler_link (rc,
-                             &coin_pub);
+  if (NULL != args[1])
+  {
+    if (0 == strcmp (args[1],
+                     "history"))
+      return TEH_handler_coins_get (rc,
+                                    &coin_pub);
+    if (0 == strcmp (args[1],
+                     "link"))
+      return TEH_handler_link (rc,
+                               &coin_pub);
+  }
   return TALER_MHD_reply_with_error (rc->connection,
                                      MHD_HTTP_NOT_FOUND,
                                      TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
@@ -731,11 +734,6 @@ handle_post_reserves (struct TEH_RequestContext *rc,
       .op = "withdraw",
       .handler = &TEH_handler_withdraw
     },
-    // FIXME: change to GET!
-    {
-      .op = "history",
-      .handler = &TEH_handler_reserves_history
-    },
     {
       .op = "purse",
       .handler = &TEH_handler_reserves_purse
@@ -777,6 +775,87 @@ handle_post_reserves (struct TEH_RequestContext *rc,
 }
 
 
+/**
+ * Signature of functions that handle GET operations on reserves.
+ *
+ * @param rc request context
+ * @param reserve_pub the public key of the reserve
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ReserveGetOpHandler)(struct TEH_RequestContext *rc,
+                       const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+/**
+ * Handle a "GET /reserves/$RESERVE_PUB[/$OP]" request.  Parses the 
"reserve_pub"
+ * EdDSA key of the reserve and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args NULL-terminated array of additional options, zero, one or two
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_reserves (struct TEH_RequestContext *rc,
+                     const char *const args[])
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1]), optional
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    ReserveGetOpHandler handler;
+
+  } h[] = {
+    {
+      .op = NULL,
+      .handler = &TEH_handler_reserves_get
+    },
+    {
+      .op = "history",
+      .handler = &TEH_handler_reserves_history
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &reserve_pub,
+                                       sizeof (reserve_pub))) )
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+                                       args[0]);
+  }
+  for (unsigned int i = 0; NULL != h[i].handler; i++)
+  {
+    if ( ( (NULL == args[1]) &&
+           (NULL == h[i].op) ) ||
+         ( (NULL != args[1]) &&
+           (NULL != h[i].op) &&
+           (0 == strcmp (h[i].op,
+                         args[1])) ) )
+      return h[i].handler (rc,
+                           &reserve_pub);
+  }
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
 /**
  * Signature of functions that handle operations on purses.
  *
@@ -1546,8 +1625,9 @@ handle_mhd_request (void *cls,
     {
       .url = "reserves",
       .method = MHD_HTTP_METHOD_GET,
-      .handler.get = &TEH_handler_reserves_get,
-      .nargs = 1
+      .handler.get = &handle_get_reserves,
+      .nargs = 2,
+      .nargs_is_upper_bound = true
     },
     {
       .url = "reserves",
diff --git a/src/exchange/taler-exchange-httpd_coins_get.h 
b/src/exchange/taler-exchange-httpd_coins_get.h
index 712269c3..90405b55 100644
--- a/src/exchange/taler-exchange-httpd_coins_get.h
+++ b/src/exchange/taler-exchange-httpd_coins_get.h
@@ -37,7 +37,7 @@ TEH_reserves_get_cleanup (void);
 
 
 /**
- * Handle a GET "/coins/$COIN_PUB" request.  Parses the
+ * Handle a GET "/coins/$COIN_PUB/history" request.  Parses the
  * given "coins_pub" in @a args (which should contain the
  * EdDSA public key of a reserve) and then respond with the
  * transaction history of the coin.
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c 
b/src/exchange/taler-exchange-httpd_reserves_get.c
index a46886ef..0775a4c6 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.c
+++ b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -170,8 +170,9 @@ db_event_cb (void *cls,
 
 
 MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
-                          const char *const args[1])
+TEH_handler_reserves_get (
+  struct TEH_RequestContext *rc,
+  const struct TALER_ReservePublicKeyP *reserve_pub)
 {
   struct ReservePoller *rp = rc->rh_ctx;
 
@@ -185,18 +186,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc,
     GNUNET_CONTAINER_DLL_insert (rp_head,
                                  rp_tail,
                                  rp);
-    if (GNUNET_OK !=
-        GNUNET_STRINGS_string_to_data (args[0],
-                                       strlen (args[0]),
-                                       &rp->reserve_pub,
-                                       sizeof (rp->reserve_pub)))
-    {
-      GNUNET_break_op (0);
-      return TALER_MHD_reply_with_error (rc->connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
-                                         args[0]);
-    }
+    rp->reserve_pub = *reserve_pub;
     TALER_MHD_parse_request_timeout (rc->connection,
                                      &rp->timeout);
   }
@@ -254,7 +244,7 @@ TEH_handler_reserves_get (struct TEH_RequestContext *rc,
         return TALER_MHD_reply_with_error (rc->connection,
                                            MHD_HTTP_NOT_FOUND,
                                            
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
-                                           args[0]);
+                                           NULL);
       }
       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                   "Long-polling on reserve for %s\n",
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h 
b/src/exchange/taler-exchange-httpd_reserves_get.h
index 30c6559f..6c453d0c 100644
--- a/src/exchange/taler-exchange-httpd_reserves_get.h
+++ b/src/exchange/taler-exchange-httpd_reserves_get.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2020 Taler Systems SA
+  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
@@ -43,11 +43,12 @@ TEH_reserves_get_cleanup (void);
  * status of the reserve.
  *
  * @param rc request context
- * @param args array of additional options (length: 1, just the reserve_pub)
+ * @param reserve_pub public key of the reserve
  * @return MHD result code
  */
 MHD_RESULT
-TEH_handler_reserves_get (struct TEH_RequestContext *rc,
-                          const char *const args[1]);
+TEH_handler_reserves_get (
+  struct TEH_RequestContext *rc,
+  const struct TALER_ReservePublicKeyP *reserve_pub);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c 
b/src/exchange/taler-exchange-httpd_reserves_history.c
index 1bf73cb2..0c692c8f 100644
--- a/src/exchange/taler-exchange-httpd_reserves_history.c
+++ b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -23,7 +23,6 @@
 #include "platform.h"
 #include <gnunet/gnunet_util_lib.h>
 #include <jansson.h>
-#include "taler_mhd_lib.h"
 #include "taler_json_lib.h"
 #include "taler_dbevents.h"
 #include "taler-exchange-httpd_keys.h"
@@ -53,6 +52,11 @@ struct ReserveHistoryContext
    */
   struct TALER_EXCHANGEDB_ReserveHistory *rh;
 
+  /**
+   * Requested startin offset for the reserve history.
+   */
+  uint64_t start_off;
+
   /**
    * Current reserve balance.
    */
@@ -415,7 +419,7 @@ reserve_history_transaction (void *cls,
 
   qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
                                         rsc->reserve_pub,
-                                        0,
+                                        rsc->start_off,
                                         &rsc->balance,
                                         &rsc->rh);
   if (GNUNET_DB_STATUS_HARD_ERROR == qs)
@@ -432,40 +436,28 @@ reserve_history_transaction (void *cls,
 
 
 MHD_RESULT
-TEH_handler_reserves_history (struct TEH_RequestContext *rc,
-                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
-                              const json_t *root)
+TEH_handler_reserves_history (
+  struct TEH_RequestContext *rc,
+  const struct TALER_ReservePublicKeyP *reserve_pub)
 {
-  struct ReserveHistoryContext rsc;
+  struct ReserveHistoryContext rsc = {
+    .reserve_pub = reserve_pub
+  };
   MHD_RESULT mhd_ret;
-  uint64_t start_off = 0;
   struct TALER_ReserveSignatureP reserve_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &reserve_sig),
-    GNUNET_JSON_spec_end ()
-  };
-
+  bool required = true;
+
+  TALER_MHD_parse_request_header_auto (rc->connection,
+                                       TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+                                       &reserve_sig,
+                                       required);
+  TALER_MHD_parse_request_number (rc->connection,
+                                  "start",
+                                  &rsc.start_off);
   rsc.reserve_pub = reserve_pub;
-  {
-    enum GNUNET_GenericReturnValue res;
 
-    res = TALER_MHD_parse_json_data (rc->connection,
-                                     root,
-                                     spec);
-    if (GNUNET_SYSERR == res)
-    {
-      GNUNET_break (0);
-      return MHD_NO; /* hard failure */
-    }
-    if (GNUNET_NO == res)
-    {
-      GNUNET_break_op (0);
-      return MHD_YES; /* failure */
-    }
-  }
   if (GNUNET_OK !=
-      TALER_wallet_reserve_history_verify (start_off,
+      TALER_wallet_reserve_history_verify (rsc.start_off,
                                            reserve_pub,
                                            &reserve_sig))
   {
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h 
b/src/exchange/taler-exchange-httpd_reserves_history.h
index 9a2a9378..e1bd7ae1 100644
--- a/src/exchange/taler-exchange-httpd_reserves_history.h
+++ b/src/exchange/taler-exchange-httpd_reserves_history.h
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_reserves_history.h
- * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
+ * @brief Handle /reserves/$RESERVE_PUB/history requests
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
@@ -24,20 +24,20 @@
 #define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
 
 #include <microhttpd.h>
+#include "taler_mhd_lib.h"
 #include "taler-exchange-httpd.h"
 
 
 /**
- * Handle a POST "/reserves/$RID/history" request.
+ * Handle a GET "/reserves/$RID/history" request.
  *
  * @param rc request context
  * @param reserve_pub public key of the reserve
- * @param root uploaded body from the client
  * @return MHD result code
  */
 MHD_RESULT
-TEH_handler_reserves_history (struct TEH_RequestContext *rc,
-                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
-                              const json_t *root);
+TEH_handler_reserves_history (
+  struct TEH_RequestContext *rc,
+  const struct TALER_ReservePublicKeyP *reserve_pub);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_responses.h 
b/src/exchange/taler-exchange-httpd_responses.h
index 3e55f0b0..877e8878 100644
--- a/src/exchange/taler-exchange-httpd_responses.h
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -24,13 +24,13 @@
  */
 #ifndef TALER_EXCHANGE_HTTPD_RESPONSES_H
 #define TALER_EXCHANGE_HTTPD_RESPONSES_H
+
 #include <gnunet/gnunet_util_lib.h>
 #include <jansson.h>
 #include <microhttpd.h>
 #include "taler_error_codes.h"
 #include "taler-exchange-httpd.h"
 #include "taler-exchange-httpd_db.h"
-#include <gnunet/gnunet_mhd_compat.h>
 
 
 /**
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
index 77d4f2ba..f973cfb0 100644
--- a/src/include/taler_exchange_service.h
+++ b/src/include/taler_exchange_service.h
@@ -3778,65 +3778,6 @@ TALER_EXCHANGE_deposits_get_cancel (
   struct TALER_EXCHANGE_DepositGetHandle *dwh);
 
 
-/**
- * Convenience function.  Verifies a coin's transaction history as
- * returned by the exchange.
- *
- * @param dk fee structure for the coin
- * @param coin_pub public key of the coin
- * @param history history of the coin in json encoding
- * @param[out] total how much of the coin has been spent according to @a 
history
- * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_verify_coin_history (
-  const struct TALER_EXCHANGE_DenomPublicKey *dk,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const json_t *history,
-  struct TALER_Amount *total);
-
-
-/**
- * Parse history given in JSON format and return it in binary
- * format.
- *
- * @param keys exchange keys
- * @param history JSON array with the history
- * @param reserve_pub public key of the reserve to inspect
- * @param currency currency we expect the balance to be in
- * @param[out] total_in set to value of credits to reserve
- * @param[out] total_out set to value of debits from reserve
- * @param history_length number of entries in @a history
- * @param[out] rhistory array of length @a history_length, set to the
- *             parsed history entries
- * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
- *         were set,
- *         #GNUNET_SYSERR if there was a protocol violation in @a history
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_parse_reserve_history (
-  const struct TALER_EXCHANGE_Keys *keys,
-  const json_t *history,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  const char *currency,
-  struct TALER_Amount *total_in,
-  struct TALER_Amount *total_out,
-  unsigned int history_length,
-  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]);
-
-
-/**
- * Free memory (potentially) allocated by 
#TALER_EXCHANGE_parse_reserve_history().
- *
- * @param len number of entries in @a rhistory
- * @param[in] rhistory result to free
- */
-void
-TALER_EXCHANGE_free_reserve_history (
-  unsigned int len,
-  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]);
-
-
 /* ********************* /recoup *********************** */
 
 
diff --git a/src/include/taler_mhd_lib.h b/src/include/taler_mhd_lib.h
index e4aa916e..60c5b209 100644
--- a/src/include/taler_mhd_lib.h
+++ b/src/include/taler_mhd_lib.h
@@ -476,6 +476,50 @@ TALER_MHD_parse_request_arg_timeout (struct MHD_Connection 
*connection,
   } while (0)
 
 
+/**
+ * Extract optional numeric limit argument from request.
+ *
+ * @param connection the MHD connection
+ * @param name name of the query parameter
+ * @param[out] off set to the offet, unchanged if the
+ *             option was not given
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if an error was returned on @a connection (caller should 
return #MHD_YES) and
+ *     #GNUNET_SYSERR if we failed to return an error (caller should return 
#MHD_NO)
+ */
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection,
+                                    const char *name,
+                                    uint64_t *off);
+
+
+/**
+ * Extract optional numeric argument from request.
+ * Macro that *returns* #MHD_YES/#MHD_NO if the
+ * requested argument existed but failed to parse.
+ *
+ * @param connection the MHD connection
+ * @param name name of the argument to parse
+ * @param[out] off set to the given numeric value,
+ *    unchanged if value was not specified
+ */
+#define TALER_MHD_parse_request_number(connection,name,off)  \
+  do {                                                         \
+    switch (TALER_MHD_parse_request_arg_number (connection,   \
+                                                name, \
+                                                off))  \
+    {                      \
+    case GNUNET_SYSERR:    \
+      GNUNET_break (0);    \
+      return MHD_NO;       \
+    case GNUNET_NO:        \
+      GNUNET_break_op (0); \
+    case GNUNET_OK:        \
+      break;               \
+    }                      \
+  } while (0)
+
+
 /**
  * Extract fixed-size base32crockford encoded data from request argument.
  *
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
index 8762f7dc..77cb5859 100644
--- a/src/include/taler_util.h
+++ b/src/include/taler_util.h
@@ -87,6 +87,16 @@
  */
 #define TALER_AML_OFFICER_SIGNATURE_HEADER "Taler-AML-Officer-Signature"
 
+/**
+ * Header with signature for reserve history requests.
+ */
+#define TALER_RESERVE_HISTORY_SIGNATURE_HEADER 
"Taler-Reserve-History-Signature"
+
+/**
+ * Header with signature for coin history requests.
+ */
+#define TALER_COIN_HISTORY_SIGNATURE_HEADER "Taler-Coin-History-Signature"
+
 /**
  * Log an error message at log-level 'level' that indicates
  * a failure of the command 'cmd' with the message given
diff --git a/src/lib/exchange_api_age_withdraw.c 
b/src/lib/exchange_api_age_withdraw.c
index f47736c0..c78d3cc5 100644
--- a/src/lib/exchange_api_age_withdraw.c
+++ b/src/lib/exchange_api_age_withdraw.c
@@ -356,108 +356,6 @@ reserve_age_withdraw_ok (
 }
 
 
-/**
- * FIXME: This function should be common to batch- and age-withdraw
- *
- * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/age-withdraw 
operation.
- * Check the signatures on the batch withdraw transactions in the provided
- * history and that the balances add up.  We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param keys The denomination keys from the exchange
- * @param reserve_pub The reserve's public key
- * @param requested_amount The requested amount
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_age_withdraw_payment_required (
-  struct TALER_EXCHANGE_Keys *keys,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  const struct TALER_Amount *requested_amount,
-  const json_t *json)
-{
-  struct TALER_Amount balance;
-  struct TALER_Amount total_in_from_history;
-  struct TALER_Amount total_out_from_history;
-  json_t *history;
-  size_t len;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount_any ("balance",
-                                &balance),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  history = json_object_get (json,
-                             "history");
-  if (NULL == history)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-
-  /* go over transaction history and compute
-     total incoming and outgoing amounts */
-  len = json_array_size (history);
-  {
-    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
-    /* Use heap allocation as "len" may be very big and thus this may
-       not fit on the stack. Use "GNUNET_malloc_large" as a malicious
-       exchange may theoretically try to crash us by giving a history
-       that does not fit into our memory. */
-    rhistory = GNUNET_malloc_large (
-      sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
-      * len);
-    if (NULL == rhistory)
-    {
-      GNUNET_break (0);
-      return GNUNET_SYSERR;
-    }
-
-    if (GNUNET_OK !=
-        TALER_EXCHANGE_parse_reserve_history (
-          keys,
-          history,
-          reserve_pub,
-          balance.currency,
-          &total_in_from_history,
-          &total_out_from_history,
-          len,
-          rhistory))
-    {
-      GNUNET_break_op (0);
-      TALER_EXCHANGE_free_reserve_history (len,
-                                           rhistory);
-      return GNUNET_SYSERR;
-    }
-    TALER_EXCHANGE_free_reserve_history (len,
-                                         rhistory);
-  }
-
-  /* Check that funds were really insufficient */
-  if (0 >= TALER_amount_cmp (requested_amount,
-                             &balance))
-  {
-    /* Requested amount is smaller or equal to reported balance,
-       so this should not have failed. */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 /**
  * Function called when we're done processing the
  * HTTP /reserves/$RESERVE_PUB/age-withdraw request.
@@ -544,29 +442,7 @@ handle_reserve_age_withdraw_blinded_finished (
   case MHD_HTTP_CONFLICT:
     /* The age requirements might not have been met */
     awbr.hr.ec = TALER_JSON_get_error_code (j_response);
-    if (TALER_EC_EXCHANGE_AGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE == awbr.hr.ec)
-    {
-      awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
-      break;
-    }
-
-    /* The exchange says that the reserve has insufficient funds;
-       check the signatures in the history... */
-    if (GNUNET_OK !=
-        reserve_age_withdraw_payment_required (awbh->keys,
-                                               &awbh->reserve_pub,
-                                               &awbh->amount_with_fee,
-                                               j_response))
-    {
-      GNUNET_break_op (0);
-      awbr.hr.http_status = 0;
-      awbr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-    }
-    else
-    {
-      awbr.hr.ec = TALER_JSON_get_error_code (j_response);
-      awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
-    }
+    awbr.hr.hint = TALER_JSON_get_error_hint (j_response);
     break;
   case MHD_HTTP_GONE:
     /* could happen if denomination was revoked */
diff --git a/src/lib/exchange_api_batch_withdraw2.c 
b/src/lib/exchange_api_batch_withdraw2.c
index 1f59a698..12c6aeff 100644
--- a/src/lib/exchange_api_batch_withdraw2.c
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -154,102 +154,6 @@ reserve_batch_withdraw_ok (struct 
TALER_EXCHANGE_BatchWithdraw2Handle *wh,
 }
 
 
-/**
- * We got a 409 CONFLICT response for the 
/reserves/$RESERVE_PUB/batch-withdraw operation.
- * Check the signatures on the batch withdraw transactions in the provided
- * history and that the balances add up.  We don't do anything directly
- * with the information, as the JSON will be returned to the application.
- * However, our job is ensuring that the exchange followed the protocol, and
- * this in particular means checking all of the signatures in the history.
- *
- * @param wh operation handle
- * @param json reply from the exchange
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-static enum GNUNET_GenericReturnValue
-reserve_batch_withdraw_payment_required (
-  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
-  const json_t *json)
-{
-  struct TALER_Amount balance;
-  struct TALER_Amount total_in_from_history;
-  struct TALER_Amount total_out_from_history;
-  json_t *history;
-  size_t len;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount_any ("balance",
-                                &balance),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (json,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  history = json_object_get (json,
-                             "history");
-  if (NULL == history)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-
-  /* go over transaction history and compute
-     total incoming and outgoing amounts */
-  len = json_array_size (history);
-  {
-    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
-
-    /* Use heap allocation as "len" may be very big and thus this may
-       not fit on the stack. Use "GNUNET_malloc_large" as a malicious
-       exchange may theoretically try to crash us by giving a history
-       that does not fit into our memory. */
-    rhistory = GNUNET_malloc_large (
-      sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
-      * len);
-    if (NULL == rhistory)
-    {
-      GNUNET_break (0);
-      return GNUNET_SYSERR;
-    }
-
-    if (GNUNET_OK !=
-        TALER_EXCHANGE_parse_reserve_history (
-          wh->keys,
-          history,
-          &wh->reserve_pub,
-          balance.currency,
-          &total_in_from_history,
-          &total_out_from_history,
-          len,
-          rhistory))
-    {
-      GNUNET_break_op (0);
-      TALER_EXCHANGE_free_reserve_history (len,
-                                           rhistory);
-      return GNUNET_SYSERR;
-    }
-    TALER_EXCHANGE_free_reserve_history (len,
-                                         rhistory);
-  }
-
-  /* Check that funds were really insufficient */
-  if (0 >= TALER_amount_cmp (&wh->requested_amount,
-                             &balance))
-  {
-    /* Requested amount is smaller or equal to reported balance,
-       so this should not have failed. */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 /**
  * Function called when we're done processing the
  * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
@@ -334,21 +238,8 @@ handle_reserve_batch_withdraw_finished (void *cls,
     bwr.hr.hint = TALER_JSON_get_error_hint (j);
     break;
   case MHD_HTTP_CONFLICT:
-    /* The exchange says that the reserve has insufficient funds;
-       check the signatures in the history... */
-    if (GNUNET_OK !=
-        reserve_batch_withdraw_payment_required (wh,
-                                                 j))
-    {
-      GNUNET_break_op (0);
-      bwr.hr.http_status = 0;
-      bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
-    }
-    else
-    {
-      bwr.hr.ec = TALER_JSON_get_error_code (j);
-      bwr.hr.hint = TALER_JSON_get_error_hint (j);
-    }
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
     break;
   case MHD_HTTP_GONE:
     /* could happen if denomination was revoked */
diff --git a/src/lib/exchange_api_coins_history.c 
b/src/lib/exchange_api_coins_history.c
new file mode 100644
index 00000000..ae718c45
--- /dev/null
+++ b/src/lib/exchange_api_coins_history.c
@@ -0,0 +1,1290 @@
+/*
+  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_coins_history.c
+ * @brief Implementation of the POST /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ *
+ * NOTE: this is an incomplete draft, never finished!
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history 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 /coins/$RID/history Handle
+ */
+struct TALER_EXCHANGE_CoinsHistoryHandle
+{
+
+  /**
+   * The keys of the exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_CoinsHistoryCallback cb;
+
+  /**
+   * Public key of the coin we are querying.
+   */
+  struct TALER_CoinPublicKeyP coin_pub;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+};
+
+
+/**
+ * Context for coin helpers.
+ */
+struct CoinHistoryParseContext
+{
+
+  /**
+   * Denomination of the coin.
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *dk;
+
+  /**
+   * Our coin public key.
+   */
+  const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+  /**
+   * Where to sum up total refunds.
+   */
+  struct TALER_Amount rtotal;
+
+  /**
+   * Total amount encountered.
+   */
+  struct TALER_Amount *total;
+
+};
+
+
+/**
+ * Signature of functions that operate on one of
+ * the coin's history entries.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+typedef enum GNUNET_GenericReturnValue
+(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
+                   const struct TALER_Amount *amount,
+                   json_t *transaction);
+
+
+/**
+ * Handle deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_deposit (struct CoinHistoryParseContext *pc,
+              const struct TALER_Amount *amount,
+              json_t *transaction)
+{
+  struct TALER_MerchantWireHashP h_wire;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  struct TALER_ExtensionPolicyHashP h_policy;
+  bool no_h_policy;
+  struct GNUNET_HashCode wallet_data_hash;
+  bool no_wallet_data_hash;
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct GNUNET_TIME_Timestamp refund_deadline = {0};
+  struct TALER_CoinSpendSignatureP sig;
+  struct TALER_AgeCommitmentHash hac;
+  bool no_hac;
+  struct TALER_Amount deposit_fee;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &h_contract_terms),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+                                   &wallet_data_hash),
+      &no_wallet_data_hash),
+    GNUNET_JSON_spec_fixed_auto ("h_wire",
+                                 &h_wire),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &hac),
+      &no_hac),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_policy",
+                                   &h_policy),
+      &no_h_policy),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &wallet_timestamp),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_timestamp ("refund_deadline",
+                                  &refund_deadline),
+      NULL),
+    TALER_JSON_spec_amount_any ("deposit_fee",
+                                &deposit_fee),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_deposit_verify (
+        amount,
+        &deposit_fee,
+        &h_wire,
+        &h_contract_terms,
+        no_wallet_data_hash ? NULL : &wallet_data_hash,
+        no_hac ? NULL : &hac,
+        no_h_policy ? NULL : &h_policy,
+        &pc->dk->h_key,
+        wallet_timestamp,
+        &merchant_pub,
+        refund_deadline,
+        pc->coin_pub,
+        &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* check that deposit fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&deposit_fee,
+                                   &pc->dk->fees.deposit)) ||
+       (0 !=
+        TALER_amount_cmp (&deposit_fee,
+                          &pc->dk->fees.deposit)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle melt entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_melt (struct CoinHistoryParseContext *pc,
+           const struct TALER_Amount *amount,
+           json_t *transaction)
+{
+  struct TALER_CoinSpendSignatureP sig;
+  struct TALER_RefreshCommitmentP rc;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+  bool no_hac;
+  struct TALER_Amount melt_fee;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("rc",
+                                 &rc),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &h_age_commitment),
+      &no_hac),
+    TALER_JSON_spec_amount_any ("melt_fee",
+                                &melt_fee),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* check that melt fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&melt_fee,
+                                   &pc->dk->fees.refresh)) ||
+       (0 !=
+        TALER_amount_cmp (&melt_fee,
+                          &pc->dk->fees.refresh)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_melt_verify (
+        amount,
+        &melt_fee,
+        &rc,
+        &pc->dk->h_key,
+        no_hac
+        ? NULL
+        : &h_age_commitment,
+        pc->coin_pub,
+        &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_refund (struct CoinHistoryParseContext *pc,
+             const struct TALER_Amount *amount,
+             json_t *transaction)
+{
+  struct TALER_PrivateContractHashP h_contract_terms;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_MerchantSignatureP sig;
+  struct TALER_Amount refund_fee;
+  struct TALER_Amount sig_amount;
+  uint64_t rtransaction_id;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("refund_fee",
+                                &refund_fee),
+    GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_uint64 ("rtransaction_id",
+                             &rtransaction_id),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (&sig_amount,
+                        &refund_fee,
+                        amount))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_merchant_refund_verify (pc->coin_pub,
+                                    &h_contract_terms,
+                                    rtransaction_id,
+                                    &sig_amount,
+                                    &merchant_pub,
+                                    &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* NOTE: theoretically, we could also check that the given
+     merchant_pub and h_contract_terms appear in the
+     history under deposits.  However, there is really no benefit
+     for the exchange to lie here, so not checking is probably OK
+     (an auditor ought to check, though). Then again, we similarly
+     had no reason to check the merchant's signature (other than a
+     well-formendess check). */
+
+  /* check that refund fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&refund_fee,
+                                   &pc->dk->fees.refund)) ||
+       (0 !=
+        TALER_amount_cmp (&refund_fee,
+                          &pc->dk->fees.refund)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup (struct CoinHistoryParseContext *pc,
+             const struct TALER_Amount *amount,
+             json_t *transaction)
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  struct GNUNET_TIME_Timestamp timestamp;
+  union TALER_DenominationBlindingKeyP coin_bks;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                 &reserve_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_blind",
+                                 &coin_bks),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_verify (
+        timestamp,
+        amount,
+        pc->coin_pub,
+        &reserve_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_recoup_verify (&pc->dk->h_key,
+                                  &coin_bks,
+                                  pc->coin_pub,
+                                  &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle recoup-refresh entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup_refresh (struct CoinHistoryParseContext *pc,
+                     const struct TALER_Amount *amount,
+                     json_t *transaction)
+{
+  /* This is the coin that was subjected to a recoup,
+       the value being credited to the old coin. */
+  struct TALER_CoinSpendPublicKeyP old_coin_pub;
+  union TALER_DenominationBlindingKeyP coin_bks;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+                                 &old_coin_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_blind",
+                                 &coin_bks),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_refresh_verify (
+        timestamp,
+        amount,
+        pc->coin_pub,
+        &old_coin_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_recoup_verify (&pc->dk->h_key,
+                                  &coin_bks,
+                                  pc->coin_pub,
+                                  &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle old coin recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_old_coin_recoup (struct CoinHistoryParseContext *pc,
+                      const struct TALER_Amount *amount,
+                      json_t *transaction)
+{
+  /* This is the coin that was credited in a recoup,
+       the value being credited to the this coin. */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendPublicKeyP new_coin_pub;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &new_coin_pub),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_refresh_verify (
+        timestamp,
+        amount,
+        &new_coin_pub,
+        pc->coin_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle purse deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_deposit (struct CoinHistoryParseContext *pc,
+                    const struct TALER_Amount *amount,
+                    json_t *transaction)
+{
+  struct TALER_PurseContractPublicKeyP purse_pub;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  const char *exchange_base_url;
+  bool refunded;
+  struct TALER_AgeCommitmentHash phac = { 0 };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &purse_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &coin_sig),
+      NULL),
+    GNUNET_JSON_spec_string ("exchange_base_url",
+                             &exchange_base_url),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &phac),
+      NULL),
+    GNUNET_JSON_spec_bool ("refunded",
+                           &refunded),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_purse_deposit_verify (
+        exchange_base_url,
+        &purse_pub,
+        amount,
+        &pc->dk->h_key,
+        &phac,
+        pc->coin_pub,
+        &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (refunded)
+  {
+    /* We wave the deposit fee. */
+    if (0 >
+        TALER_amount_add (&pc->rtotal,
+                          &pc->rtotal,
+                          &pc->dk->fees.deposit))
+    {
+      /* overflow in refund history? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle purse refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_refund (struct CoinHistoryParseContext *pc,
+                   const struct TALER_Amount *amount,
+                   json_t *transaction)
+{
+  struct TALER_PurseContractPublicKeyP purse_pub;
+  struct TALER_Amount refund_fee;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("refund_fee",
+                                &refund_fee),
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &purse_pub),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_purse_refund_verify (
+        amount,
+        &refund_fee,
+        pc->coin_pub,
+        &purse_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&refund_fee,
+                                   &pc->dk->fees.refund)) ||
+       (0 !=
+        TALER_amount_cmp (&refund_fee,
+                          &pc->dk->fees.refund)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle reserve deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
+                           const struct TALER_Amount *amount,
+                           json_t *transaction)
+{
+  struct TALER_ReserveSignatureP reserve_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &reserve_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_open_deposit_verify (
+        amount,
+        &reserve_sig,
+        pc->coin_pub,
+        &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Convenience function.  Verifies a coin's transaction history as
+ * returned by the exchange.
+ *
+ * FIXME: add support for partial histories!
+ * NOTE: this API will thus still change!
+ *
+ * @param dk fee structure for the coin
+ * @param coin_pub public key of the coin
+ * @param history history of the coin in json encoding
+ * @param[out] total how much of the coin has been spent according to @a 
history
+ * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+verify_coin_history (
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const json_t *history,
+  struct TALER_Amount *total)
+{
+  const char *currency = dk->value.currency;
+  const struct
+  {
+    const char *type;
+    CoinCheckHelper helper;
+  } map[] = {
+    { "DEPOSIT", &help_deposit },
+    { "MELT", &help_melt },
+    { "REFUND", &help_refund },
+    { "RECOUP", &help_recoup },
+    { "RECOUP-REFRESH", &help_recoup_refresh },
+    { "OLD-COIN-RECOUP", &help_old_coin_recoup },
+    { "PURSE-DEPOSIT", &help_purse_deposit },
+    { "PURSE-REFUND", &help_purse_refund },
+    { "RESERVE-OPEN-DEPOSIT", &help_reserve_open_deposit },
+    { NULL, NULL }
+  };
+  struct CoinHistoryParseContext pc = {
+    .dk = dk,
+    .coin_pub = coin_pub,
+    .total = total
+  };
+  size_t len;
+
+  if (NULL == history)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len = json_array_size (history);
+  if (0 == len)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        &pc.rtotal));
+  for (size_t off = 0; off<len; off++)
+  {
+    enum GNUNET_GenericReturnValue add;
+    json_t *transaction;
+    struct TALER_Amount amount;
+    const char *type;
+    struct GNUNET_JSON_Specification spec_glob[] = {
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
+      GNUNET_JSON_spec_string ("type",
+                               &type),
+      GNUNET_JSON_spec_end ()
+    };
+
+    transaction = json_array_get (history,
+                                  off);
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (transaction,
+                           spec_glob,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    if (GNUNET_YES !=
+        TALER_amount_cmp_currency (&amount,
+                                   &pc.rtotal))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Operation of type %s with amount %s\n",
+                type,
+                TALER_amount2s (&amount));
+    add = GNUNET_SYSERR;
+    for (unsigned int i = 0; NULL != map[i].type; i++)
+    {
+      if (0 == strcasecmp (type,
+                           map[i].type))
+      {
+        add = map[i].helper (&pc,
+                             &amount,
+                             transaction);
+        break;
+      }
+    }
+    switch (add)
+    {
+    case GNUNET_SYSERR:
+      /* entry type not supported, new version on server? */
+      GNUNET_break_op (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected type `%s' in response\n",
+                  type);
+      return GNUNET_SYSERR;
+    case GNUNET_YES:
+      /* This amount should be added to the total */
+      if (0 >
+          TALER_amount_add (total,
+                            total,
+                            &amount))
+      {
+        /* overflow in history already!? inconceivable! Bad exchange! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case GNUNET_NO:
+      /* This amount should be subtracted from the total.
+
+         However, for the implementation, we first *add* up all of
+         these negative amounts, as we might get refunds before
+         deposits from a semi-evil exchange.  Then, at the end, we do
+         the subtraction by calculating "total = total - rtotal" */
+      if (0 >
+          TALER_amount_add (&pc.rtotal,
+                            &pc.rtotal,
+                            &amount))
+      {
+        /* overflow in refund history? inconceivable! Bad exchange! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    } /* end of switch(add) */
+  }
+  /* Finally, subtract 'rtotal' from total to handle the subtractions */
+  if (0 >
+      TALER_amount_subtract (total,
+                             total,
+                             &pc.rtotal))
+  {
+    /* underflow in history? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in
+ * the history of @a proof and thus whatever transaction
+ * is authorized by @a coin_sig is a conflict with
+ * @a proof.
+ *
+ * @param proof a proof to check
+ * @param coin_sig signature that must not be in @a proof
+ * @return #GNUNET_OK if @a coin_sig is not in @a proof
+ */
+// FIXME: should be used...
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict_ (
+  const json_t *proof,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  json_t *history;
+  size_t off;
+  json_t *entry;
+
+  history = json_object_get (proof,
+                             "history");
+  if (NULL == history)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_array_foreach (history, off, entry)
+  {
+    struct TALER_CoinSpendSignatureP cs;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                   &cs),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (entry,
+                           spec,
+                           NULL, NULL))
+      continue; /* entry without coin signature */
+    if (0 ==
+        GNUNET_memcmp (&cs,
+                       coin_sig))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Check that the provided @a proof indeeds indicates
+ * a conflict for @a coin_pub.
+ *
+ * @param keys exchange keys
+ * @param proof provided conflict proof
+ * @param dk denomination of @a coin_pub that the client
+ *           used
+ * @param coin_pub public key of the coin
+ * @param required balance required on the coin for the operation
+ * @return #GNUNET_OK if @a proof holds
+ */
+// FIXME: should be used!
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_conflict_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *proof,
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *required)
+{
+  enum TALER_ErrorCode ec;
+
+  ec = TALER_JSON_get_error_code (proof);
+  switch (ec)
+  {
+  case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+    /* Nothing to check anymore here, proof needs to be
+       checked in the GET /coins/$COIN_PUB handler */
+    break;
+  case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+    // FIXME: write check!
+    break;
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_coins_history_ok (struct TALER_EXCHANGE_CoinsHistoryHandle *rsh,
+                         const json_t *j)
+{
+  const json_t *history;
+  unsigned int len;
+  struct TALER_EXCHANGE_CoinHistory rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &rs.details.ok.balance),
+    GNUNET_JSON_spec_array_const ("history",
+                                  &history),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_CoinHistoryEntry *rhistory;
+
+    rhistory = GNUNET_new_array (len,
+                                 struct TALER_EXCHANGE_CoinHistoryEntry);
+    if (GNUNET_OK !=
+        parse_coin_history (rsh->keys,
+                            history,
+                            &rsh->coin_pub,
+                            rs.details.ok.balance.currency,
+                            &rs.details.ok.total_in,
+                            &rs.details.ok.total_out,
+                            len,
+                            rhistory))
+    {
+      GNUNET_break_op (0);
+      free_coin_history (len,
+                         rhistory);
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+    if (NULL != rsh->cb)
+    {
+      rs.details.ok.history = rhistory;
+      rs.details.ok.history_len = len;
+      rsh->cb (rsh->cb_cls,
+               &rs);
+      rsh->cb = NULL;
+    }
+    free_coin_history (len,
+                       rhistory);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /coins/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CoinsHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_coins_history_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_EXCHANGE_CoinsHistoryHandle *rsh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_CoinHistory rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  rsh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_coins_history_ok (rsh,
+                                 j))
+    {
+      rs.hr.http_status = 0;
+      rs.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 */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for coins history\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec);
+    break;
+  }
+  if (NULL != rsh->cb)
+  {
+    rsh->cb (rsh->cb_cls,
+             &rs);
+    rsh->cb = NULL;
+  }
+  TALER_EXCHANGE_coins_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_CoinsHistoryHandle *
+TALER_EXCHANGE_coins_history (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_CoinPrivateKeyP *coin_priv,
+  uint64_t start_off,
+  TALER_EXCHANGE_CoinsHistoryCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_CoinsHistoryHandle *rsh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_CoinPublicKeyP) * 2 + 64];
+  struct curl_slist *job_headers;
+
+  rsh = GNUNET_new (struct TALER_EXCHANGE_CoinsHistoryHandle);
+  rsh->cb = cb;
+  rsh->cb_cls = cb_cls;
+  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+                                      &rsh->coin_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_CoinPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &rsh->coin_pub,
+      sizeof (rsh->coin_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    if (0 != start_off)
+      GNUNET_snprintf (arg_str,
+                       sizeof (arg_str),
+                       "coins/%s/history?start=%llu",
+                       pub_str,
+                       (unsigned long long) start_off);
+    else
+      GNUNET_snprintf (arg_str,
+                       sizeof (arg_str),
+                       "coins/%s/history",
+                       pub_str);
+  }
+  rsh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == rsh->url)
+  {
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (rsh->url);
+    GNUNET_free (rsh);
+    return NULL;
+  }
+
+  {
+    struct TALER_CoinSignatureP coin_sig;
+    char *sig_hdr;
+    char *hdr;
+
+    TALER_wallet_coin_history_sign (start_off,
+                                    coin_priv,
+                                    &coin_sig);
+
+    sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+      &coin_sig,
+      sizeof (coin_sig));
+    GNUNET_asprintf (&hdr,
+                     "%s: %s",
+                     TALER_COIN_HISTORY_SIGNATURE_HEADER,
+                     sig_hdr);
+    GNUNET_free (sig_hdr);
+    job_headers = curl_slist_append (NULL,
+                                     hdr);
+    GNUNET_free (hdr);
+    if (NULL == job_headers)
+    {
+      GNUNET_break (0);
+      return NULL;
+    }
+  }
+
+  rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+  rsh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   job_headers,
+                                   &handle_coins_history_finished,
+                                   rsh);
+  curl_slist_free_all (job_headers);
+  return rsh;
+}
+
+
+void
+TALER_EXCHANGE_coins_history_cancel (
+  struct TALER_EXCHANGE_CoinsHistoryHandle *rsh)
+{
+  if (NULL != rsh->job)
+  {
+    GNUNET_CURL_job_cancel (rsh->job);
+    rsh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&rsh->post_ctx);
+  GNUNET_free (rsh->url);
+  TALER_EXCHANGE_keys_decref (rsh->keys);
+  GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_coins_history.c */
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
index 3ad88017..4846e118 100644
--- a/src/lib/exchange_api_common.c
+++ b/src/lib/exchange_api_common.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2015-2022 Taler Systems SA
+  Copyright (C) 2015-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
@@ -27,1542 +27,6 @@
 #include "taler_signatures.h"
 
 
-/**
- * Context for history entry helpers.
- */
-struct HistoryParseContext
-{
-
-  /**
-   * Keys of the exchange we use.
-   */
-  const struct TALER_EXCHANGE_Keys *keys;
-
-  /**
-   * Our reserve public key.
-   */
-  const struct TALER_ReservePublicKeyP *reserve_pub;
-
-  /**
-   * Array of UUIDs.
-   */
-  struct GNUNET_HashCode *uuids;
-
-  /**
-   * Where to sum up total inbound amounts.
-   */
-  struct TALER_Amount *total_in;
-
-  /**
-   * Where to sum up total outbound amounts.
-   */
-  struct TALER_Amount *total_out;
-
-  /**
-   * Number of entries already used in @e uuids.
-   */
-  unsigned int uuid_off;
-};
-
-
-/**
- * Type of a function called to parse a reserve history
- * entry @a rh.
- *
- * @param[in,out] rh where to write the result
- * @param[in,out] uc UUID context for duplicate detection
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-typedef enum GNUNET_GenericReturnValue
-(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-               struct HistoryParseContext *uc,
-               const json_t *transaction);
-
-
-/**
- * Parse "credit" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-              struct HistoryParseContext *uc,
-              const json_t *transaction)
-{
-  const char *wire_url;
-  uint64_t wire_reference;
-  struct GNUNET_TIME_Timestamp timestamp;
-  struct GNUNET_JSON_Specification withdraw_spec[] = {
-    GNUNET_JSON_spec_uint64 ("wire_reference",
-                             &wire_reference),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &timestamp),
-    GNUNET_JSON_spec_string ("sender_account_url",
-                             &wire_url),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_CREDIT;
-  if (0 >
-      TALER_amount_add (uc->total_in,
-                        uc->total_in,
-                        &rh->amount))
-  {
-    /* overflow in history already!? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         withdraw_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  rh->details.in_details.sender_url = GNUNET_strdup (wire_url);
-  rh->details.in_details.wire_reference = wire_reference;
-  rh->details.in_details.timestamp = timestamp;
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "credit" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-                struct HistoryParseContext *uc,
-                const json_t *transaction)
-{
-  struct TALER_ReserveSignatureP sig;
-  struct TALER_DenominationHashP h_denom_pub;
-  struct TALER_BlindedCoinHashP bch;
-  struct TALER_Amount withdraw_fee;
-  struct GNUNET_JSON_Specification withdraw_spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &sig),
-    TALER_JSON_spec_amount_any ("withdraw_fee",
-                                &withdraw_fee),
-    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
-                                 &h_denom_pub),
-    GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
-                                 &bch),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         withdraw_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-
-  /* Check that the signature is a valid withdraw request */
-  if (GNUNET_OK !=
-      TALER_wallet_withdraw_verify (&h_denom_pub,
-                                    &rh->amount,
-                                    &bch,
-                                    uc->reserve_pub,
-                                    &sig))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (withdraw_spec);
-    return GNUNET_SYSERR;
-  }
-  /* check that withdraw fee matches expectations! */
-  {
-    const struct TALER_EXCHANGE_Keys *key_state;
-    const struct TALER_EXCHANGE_DenomPublicKey *dki;
-
-    key_state = uc->keys;
-    dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
-                                                       &h_denom_pub);
-    if ( (GNUNET_YES !=
-          TALER_amount_cmp_currency (&withdraw_fee,
-                                     &dki->fees.withdraw)) ||
-         (0 !=
-          TALER_amount_cmp (&withdraw_fee,
-                            &dki->fees.withdraw)) )
-    {
-      GNUNET_break_op (0);
-      GNUNET_JSON_parse_free (withdraw_spec);
-      return GNUNET_SYSERR;
-    }
-    rh->details.withdraw.fee = withdraw_fee;
-  }
-  rh->details.withdraw.out_authorization_sig
-    = json_object_get (transaction,
-                       "signature");
-  /* Check check that the same withdraw transaction
-       isn't listed twice by the exchange. We use the
-       "uuid" array to remember the hashes of all
-       signatures, and compare the hashes to find
-       duplicates. */
-  GNUNET_CRYPTO_hash (&sig,
-                      sizeof (sig),
-                      &uc->uuids[uc->uuid_off]);
-  for (unsigned int i = 0; i<uc->uuid_off; i++)
-  {
-    if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
-                            &uc->uuids[i]))
-    {
-      GNUNET_break_op (0);
-      GNUNET_JSON_parse_free (withdraw_spec);
-      return GNUNET_SYSERR;
-    }
-  }
-  uc->uuid_off++;
-
-  if (0 >
-      TALER_amount_add (uc->total_out,
-                        uc->total_out,
-                        &rh->amount))
-  {
-    /* overflow in history already!? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (withdraw_spec);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "recoup" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-              struct HistoryParseContext *uc,
-              const json_t *transaction)
-{
-  const struct TALER_EXCHANGE_Keys *key_state;
-  struct GNUNET_JSON_Specification recoup_spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &rh->details.recoup_details.coin_pub),
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &rh->details.recoup_details.exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &rh->details.recoup_details.exchange_pub),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &rh->details.recoup_details.timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_RECOUP;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         recoup_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  key_state = uc->keys;
-  if (GNUNET_OK !=
-      TALER_EXCHANGE_test_signing_key (key_state,
-                                       &rh->details.
-                                       recoup_details.exchange_pub))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_confirm_recoup_verify (
-        rh->details.recoup_details.timestamp,
-        &rh->amount,
-        &rh->details.recoup_details.coin_pub,
-        uc->reserve_pub,
-        &rh->details.recoup_details.exchange_pub,
-        &rh->details.recoup_details.exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 >
-      TALER_amount_add (uc->total_in,
-                        uc->total_in,
-                        &rh->amount))
-  {
-    /* overflow in history already!? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "closing" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-               struct HistoryParseContext *uc,
-               const json_t *transaction)
-{
-  const struct TALER_EXCHANGE_Keys *key_state;
-  struct GNUNET_JSON_Specification closing_spec[] = {
-    GNUNET_JSON_spec_string (
-      "receiver_account_details",
-      &rh->details.close_details.receiver_account_details),
-    GNUNET_JSON_spec_fixed_auto ("wtid",
-                                 &rh->details.close_details.wtid),
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &rh->details.close_details.exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &rh->details.close_details.exchange_pub),
-    TALER_JSON_spec_amount_any ("closing_fee",
-                                &rh->details.close_details.fee),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &rh->details.close_details.timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_CLOSING;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         closing_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  key_state = uc->keys;
-  if (GNUNET_OK !=
-      TALER_EXCHANGE_test_signing_key (
-        key_state,
-        &rh->details.close_details.exchange_pub))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_reserve_closed_verify (
-        rh->details.close_details.timestamp,
-        &rh->amount,
-        &rh->details.close_details.fee,
-        rh->details.close_details.receiver_account_details,
-        &rh->details.close_details.wtid,
-        uc->reserve_pub,
-        &rh->details.close_details.exchange_pub,
-        &rh->details.close_details.exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 >
-      TALER_amount_add (uc->total_out,
-                        uc->total_out,
-                        &rh->amount))
-  {
-    /* overflow in history already!? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "merge" reserve history entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-             struct HistoryParseContext *uc,
-             const json_t *transaction)
-{
-  uint32_t flags32;
-  struct GNUNET_JSON_Specification merge_spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                 &rh->details.merge_details.h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("merge_pub",
-                                 &rh->details.merge_details.merge_pub),
-    GNUNET_JSON_spec_fixed_auto ("purse_pub",
-                                 &rh->details.merge_details.purse_pub),
-    GNUNET_JSON_spec_uint32 ("min_age",
-                             &rh->details.merge_details.min_age),
-    GNUNET_JSON_spec_uint32 ("flags",
-                             &flags32),
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &rh->details.merge_details.reserve_sig),
-    TALER_JSON_spec_amount_any ("purse_fee",
-                                &rh->details.merge_details.purse_fee),
-    GNUNET_JSON_spec_timestamp ("merge_timestamp",
-                                &rh->details.merge_details.merge_timestamp),
-    GNUNET_JSON_spec_timestamp ("purse_expiration",
-                                &rh->details.merge_details.purse_expiration),
-    GNUNET_JSON_spec_bool ("merged",
-                           &rh->details.merge_details.merged),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_MERGE;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         merge_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  rh->details.merge_details.flags =
-    (enum TALER_WalletAccountMergeFlags) flags32;
-  if (GNUNET_OK !=
-      TALER_wallet_account_merge_verify (
-        rh->details.merge_details.merge_timestamp,
-        &rh->details.merge_details.purse_pub,
-        rh->details.merge_details.purse_expiration,
-        &rh->details.merge_details.h_contract_terms,
-        &rh->amount,
-        &rh->details.merge_details.purse_fee,
-        rh->details.merge_details.min_age,
-        rh->details.merge_details.flags,
-        uc->reserve_pub,
-        &rh->details.merge_details.reserve_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (rh->details.merge_details.merged)
-  {
-    if (0 >
-        TALER_amount_add (uc->total_in,
-                          uc->total_in,
-                          &rh->amount))
-    {
-      /* overflow in history already!? inconceivable! Bad exchange! */
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-  }
-  else
-  {
-    if (0 >
-        TALER_amount_add (uc->total_out,
-                          uc->total_out,
-                          &rh->details.merge_details.purse_fee))
-    {
-      /* overflow in history already!? inconceivable! Bad exchange! */
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "open" reserve open entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-            struct HistoryParseContext *uc,
-            const json_t *transaction)
-{
-  struct GNUNET_JSON_Specification open_spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &rh->details.open_request.reserve_sig),
-    TALER_JSON_spec_amount_any ("open_payment",
-                                &rh->details.open_request.reserve_payment),
-    GNUNET_JSON_spec_uint32 ("requested_min_purses",
-                             &rh->details.open_request.purse_limit),
-    GNUNET_JSON_spec_timestamp ("request_timestamp",
-                                &rh->details.open_request.request_timestamp),
-    GNUNET_JSON_spec_timestamp ("requested_expiration",
-                                &rh->details.open_request.reserve_expiration),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_OPEN;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         open_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_reserve_open_verify (
-        &rh->amount,
-        rh->details.open_request.request_timestamp,
-        rh->details.open_request.reserve_expiration,
-        rh->details.open_request.purse_limit,
-        uc->reserve_pub,
-        &rh->details.open_request.reserve_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 >
-      TALER_amount_add (uc->total_out,
-                        uc->total_out,
-                        &rh->amount))
-  {
-    /* overflow in history already!? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Parse "close" reserve close entry.
- *
- * @param[in,out] rh entry to parse
- * @param uc our context
- * @param transaction the transaction to parse
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
-             struct HistoryParseContext *uc,
-             const json_t *transaction)
-{
-  struct GNUNET_JSON_Specification close_spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &rh->details.close_request.reserve_sig),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_payto",
-                                   &rh->details.close_request.
-                                   target_account_h_payto),
-      NULL),
-    GNUNET_JSON_spec_timestamp ("request_timestamp",
-                                &rh->details.close_request.request_timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  rh->type = TALER_EXCHANGE_RTT_CLOSE;
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         close_spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  /* force amount to invalid */
-  memset (&rh->amount,
-          0,
-          sizeof (rh->amount));
-  if (GNUNET_OK !=
-      TALER_wallet_reserve_close_verify (
-        rh->details.close_request.request_timestamp,
-        &rh->details.close_request.target_account_h_payto,
-        uc->reserve_pub,
-        &rh->details.close_request.reserve_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_parse_reserve_history (
-  const struct TALER_EXCHANGE_Keys *keys,
-  const json_t *history,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  const char *currency,
-  struct TALER_Amount *total_in,
-  struct TALER_Amount *total_out,
-  unsigned int history_length,
-  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
-{
-  const struct
-  {
-    const char *type;
-    ParseHelper helper;
-  } map[] = {
-    { "CREDIT", &parse_credit },
-    { "WITHDRAW", &parse_withdraw },
-    { "RECOUP", &parse_recoup },
-    { "MERGE", &parse_merge },
-    { "CLOSING", &parse_closing },
-    { "OPEN", &parse_open },
-    { "CLOSE", &parse_close },
-    { NULL, NULL }
-  };
-  struct GNUNET_HashCode uuid[history_length];
-  struct HistoryParseContext uc = {
-    .keys = keys,
-    .reserve_pub = reserve_pub,
-    .uuids = uuid,
-    .total_in = total_in,
-    .total_out = total_out
-  };
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (currency,
-                                        total_in));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (currency,
-                                        total_out));
-  for (unsigned int off = 0; off<history_length; off++)
-  {
-    struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
-    json_t *transaction;
-    struct TALER_Amount amount;
-    const char *type;
-    struct GNUNET_JSON_Specification hist_spec[] = {
-      GNUNET_JSON_spec_string ("type",
-                               &type),
-      TALER_JSON_spec_amount_any ("amount",
-                                  &amount),
-      /* 'wire' and 'signature' are optional depending on 'type'! */
-      GNUNET_JSON_spec_end ()
-    };
-    bool found = false;
-
-    transaction = json_array_get (history,
-                                  off);
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (transaction,
-                           hist_spec,
-                           NULL, NULL))
-    {
-      GNUNET_break_op (0);
-      json_dumpf (transaction,
-                  stderr,
-                  JSON_INDENT (2));
-      return GNUNET_SYSERR;
-    }
-    rh->amount = amount;
-    if (GNUNET_YES !=
-        TALER_amount_cmp_currency (&amount,
-                                   total_in))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-    for (unsigned int i = 0; NULL != map[i].type; i++)
-    {
-      if (0 == strcasecmp (map[i].type,
-                           type))
-      {
-        found = true;
-        if (GNUNET_OK !=
-            map[i].helper (rh,
-                           &uc,
-                           transaction))
-        {
-          GNUNET_break_op (0);
-          return GNUNET_SYSERR;
-        }
-        break;
-      }
-    }
-    if (! found)
-    {
-      /* unexpected 'type', protocol incompatibility, complain! */
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-  }
-  return GNUNET_OK;
-}
-
-
-void
-TALER_EXCHANGE_free_reserve_history (
-  unsigned int len,
-  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
-{
-  for (unsigned int i = 0; i<len; i++)
-  {
-    switch (rhistory[i].type)
-    {
-    case TALER_EXCHANGE_RTT_CREDIT:
-      GNUNET_free (rhistory[i].details.in_details.sender_url);
-      break;
-    case TALER_EXCHANGE_RTT_WITHDRAWAL:
-      break;
-    case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
-      break;
-    case TALER_EXCHANGE_RTT_RECOUP:
-      break;
-    case TALER_EXCHANGE_RTT_CLOSING:
-      break;
-    case TALER_EXCHANGE_RTT_MERGE:
-      break;
-    case TALER_EXCHANGE_RTT_OPEN:
-      break;
-    case TALER_EXCHANGE_RTT_CLOSE:
-      break;
-    }
-  }
-  GNUNET_free (rhistory);
-}
-
-
-/**
- * Context for coin helpers.
- */
-struct CoinHistoryParseContext
-{
-
-  /**
-   * Denomination of the coin.
-   */
-  const struct TALER_EXCHANGE_DenomPublicKey *dk;
-
-  /**
-   * Our coin public key.
-   */
-  const struct TALER_CoinSpendPublicKeyP *coin_pub;
-
-  /**
-   * Where to sum up total refunds.
-   */
-  struct TALER_Amount rtotal;
-
-  /**
-   * Total amount encountered.
-   */
-  struct TALER_Amount *total;
-
-};
-
-
-/**
- * Signature of functions that operate on one of
- * the coin's history entries.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-typedef enum GNUNET_GenericReturnValue
-(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
-                   const struct TALER_Amount *amount,
-                   json_t *transaction);
-
-
-/**
- * Handle deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_deposit (struct CoinHistoryParseContext *pc,
-              const struct TALER_Amount *amount,
-              json_t *transaction)
-{
-  struct TALER_MerchantWireHashP h_wire;
-  struct TALER_PrivateContractHashP h_contract_terms;
-  struct TALER_ExtensionPolicyHashP h_policy;
-  bool no_h_policy;
-  struct GNUNET_HashCode wallet_data_hash;
-  bool no_wallet_data_hash;
-  struct GNUNET_TIME_Timestamp wallet_timestamp;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  struct GNUNET_TIME_Timestamp refund_deadline = {0};
-  struct TALER_CoinSpendSignatureP sig;
-  struct TALER_AgeCommitmentHash hac;
-  bool no_hac;
-  struct TALER_Amount deposit_fee;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &sig),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                 &h_contract_terms),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
-                                   &wallet_data_hash),
-      &no_wallet_data_hash),
-    GNUNET_JSON_spec_fixed_auto ("h_wire",
-                                 &h_wire),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &hac),
-      &no_hac),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_policy",
-                                   &h_policy),
-      &no_h_policy),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &wallet_timestamp),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_timestamp ("refund_deadline",
-                                  &refund_deadline),
-      NULL),
-    TALER_JSON_spec_amount_any ("deposit_fee",
-                                &deposit_fee),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &merchant_pub),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_deposit_verify (
-        amount,
-        &deposit_fee,
-        &h_wire,
-        &h_contract_terms,
-        no_wallet_data_hash ? NULL : &wallet_data_hash,
-        no_hac ? NULL : &hac,
-        no_h_policy ? NULL : &h_policy,
-        &pc->dk->h_key,
-        wallet_timestamp,
-        &merchant_pub,
-        refund_deadline,
-        pc->coin_pub,
-        &sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  /* check that deposit fee matches our expectations from /keys! */
-  if ( (GNUNET_YES !=
-        TALER_amount_cmp_currency (&deposit_fee,
-                                   &pc->dk->fees.deposit)) ||
-       (0 !=
-        TALER_amount_cmp (&deposit_fee,
-                          &pc->dk->fees.deposit)) )
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_YES;
-}
-
-
-/**
- * Handle melt entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_melt (struct CoinHistoryParseContext *pc,
-           const struct TALER_Amount *amount,
-           json_t *transaction)
-{
-  struct TALER_CoinSpendSignatureP sig;
-  struct TALER_RefreshCommitmentP rc;
-  struct TALER_AgeCommitmentHash h_age_commitment;
-  bool no_hac;
-  struct TALER_Amount melt_fee;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &sig),
-    GNUNET_JSON_spec_fixed_auto ("rc",
-                                 &rc),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &h_age_commitment),
-      &no_hac),
-    TALER_JSON_spec_amount_any ("melt_fee",
-                                &melt_fee),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-
-  /* check that melt fee matches our expectations from /keys! */
-  if ( (GNUNET_YES !=
-        TALER_amount_cmp_currency (&melt_fee,
-                                   &pc->dk->fees.refresh)) ||
-       (0 !=
-        TALER_amount_cmp (&melt_fee,
-                          &pc->dk->fees.refresh)) )
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_melt_verify (
-        amount,
-        &melt_fee,
-        &rc,
-        &pc->dk->h_key,
-        no_hac
-        ? NULL
-        : &h_age_commitment,
-        pc->coin_pub,
-        &sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_YES;
-}
-
-
-/**
- * Handle refund entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_refund (struct CoinHistoryParseContext *pc,
-             const struct TALER_Amount *amount,
-             json_t *transaction)
-{
-  struct TALER_PrivateContractHashP h_contract_terms;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  struct TALER_MerchantSignatureP sig;
-  struct TALER_Amount refund_fee;
-  struct TALER_Amount sig_amount;
-  uint64_t rtransaction_id;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount_any ("refund_fee",
-                                &refund_fee),
-    GNUNET_JSON_spec_fixed_auto ("merchant_sig",
-                                 &sig),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                 &h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &merchant_pub),
-    GNUNET_JSON_spec_uint64 ("rtransaction_id",
-                             &rtransaction_id),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (0 >
-      TALER_amount_add (&sig_amount,
-                        &refund_fee,
-                        amount))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_merchant_refund_verify (pc->coin_pub,
-                                    &h_contract_terms,
-                                    rtransaction_id,
-                                    &sig_amount,
-                                    &merchant_pub,
-                                    &sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  /* NOTE: theoretically, we could also check that the given
-     merchant_pub and h_contract_terms appear in the
-     history under deposits.  However, there is really no benefit
-     for the exchange to lie here, so not checking is probably OK
-     (an auditor ought to check, though). Then again, we similarly
-     had no reason to check the merchant's signature (other than a
-     well-formendess check). */
-
-  /* check that refund fee matches our expectations from /keys! */
-  if ( (GNUNET_YES !=
-        TALER_amount_cmp_currency (&refund_fee,
-                                   &pc->dk->fees.refund)) ||
-       (0 !=
-        TALER_amount_cmp (&refund_fee,
-                          &pc->dk->fees.refund)) )
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_NO;
-}
-
-
-/**
- * Handle recoup entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_recoup (struct CoinHistoryParseContext *pc,
-             const struct TALER_Amount *amount,
-             json_t *transaction)
-{
-  struct TALER_ReservePublicKeyP reserve_pub;
-  struct GNUNET_TIME_Timestamp timestamp;
-  union TALER_DenominationBlindingKeyP coin_bks;
-  struct TALER_ExchangePublicKeyP exchange_pub;
-  struct TALER_ExchangeSignatureP exchange_sig;
-  struct TALER_CoinSpendSignatureP coin_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &exchange_pub),
-    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                 &reserve_pub),
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &coin_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_blind",
-                                 &coin_bks),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_confirm_recoup_verify (
-        timestamp,
-        amount,
-        pc->coin_pub,
-        &reserve_pub,
-        &exchange_pub,
-        &exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_recoup_verify (&pc->dk->h_key,
-                                  &coin_bks,
-                                  pc->coin_pub,
-                                  &coin_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_YES;
-}
-
-
-/**
- * Handle recoup-refresh entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_recoup_refresh (struct CoinHistoryParseContext *pc,
-                     const struct TALER_Amount *amount,
-                     json_t *transaction)
-{
-  /* This is the coin that was subjected to a recoup,
-       the value being credited to the old coin. */
-  struct TALER_CoinSpendPublicKeyP old_coin_pub;
-  union TALER_DenominationBlindingKeyP coin_bks;
-  struct GNUNET_TIME_Timestamp timestamp;
-  struct TALER_ExchangePublicKeyP exchange_pub;
-  struct TALER_ExchangeSignatureP exchange_sig;
-  struct TALER_CoinSpendSignatureP coin_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &exchange_pub),
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &coin_sig),
-    GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
-                                 &old_coin_pub),
-    GNUNET_JSON_spec_fixed_auto ("coin_blind",
-                                 &coin_bks),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_confirm_recoup_refresh_verify (
-        timestamp,
-        amount,
-        pc->coin_pub,
-        &old_coin_pub,
-        &exchange_pub,
-        &exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_recoup_verify (&pc->dk->h_key,
-                                  &coin_bks,
-                                  pc->coin_pub,
-                                  &coin_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_YES;
-}
-
-
-/**
- * Handle old coin recoup entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_old_coin_recoup (struct CoinHistoryParseContext *pc,
-                      const struct TALER_Amount *amount,
-                      json_t *transaction)
-{
-  /* This is the coin that was credited in a recoup,
-       the value being credited to the this coin. */
-  struct TALER_ExchangePublicKeyP exchange_pub;
-  struct TALER_ExchangeSignatureP exchange_sig;
-  struct TALER_CoinSpendPublicKeyP new_coin_pub;
-  struct GNUNET_TIME_Timestamp timestamp;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &exchange_pub),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &new_coin_pub),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &timestamp),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_confirm_recoup_refresh_verify (
-        timestamp,
-        amount,
-        &new_coin_pub,
-        pc->coin_pub,
-        &exchange_pub,
-        &exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_NO;
-}
-
-
-/**
- * Handle purse deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_purse_deposit (struct CoinHistoryParseContext *pc,
-                    const struct TALER_Amount *amount,
-                    json_t *transaction)
-{
-  struct TALER_PurseContractPublicKeyP purse_pub;
-  struct TALER_CoinSpendSignatureP coin_sig;
-  const char *exchange_base_url;
-  bool refunded;
-  struct TALER_AgeCommitmentHash phac = { 0 };
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("purse_pub",
-                                 &purse_pub),
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &coin_sig),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &coin_sig),
-      NULL),
-    GNUNET_JSON_spec_string ("exchange_base_url",
-                             &exchange_base_url),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &phac),
-      NULL),
-    GNUNET_JSON_spec_bool ("refunded",
-                           &refunded),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_purse_deposit_verify (
-        exchange_base_url,
-        &purse_pub,
-        amount,
-        &pc->dk->h_key,
-        &phac,
-        pc->coin_pub,
-        &coin_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (refunded)
-  {
-    /* We wave the deposit fee. */
-    if (0 >
-        TALER_amount_add (&pc->rtotal,
-                          &pc->rtotal,
-                          &pc->dk->fees.deposit))
-    {
-      /* overflow in refund history? inconceivable! Bad exchange! */
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-  }
-  return GNUNET_YES;
-}
-
-
-/**
- * Handle purse refund entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_purse_refund (struct CoinHistoryParseContext *pc,
-                   const struct TALER_Amount *amount,
-                   json_t *transaction)
-{
-  struct TALER_PurseContractPublicKeyP purse_pub;
-  struct TALER_Amount refund_fee;
-  struct TALER_ExchangePublicKeyP exchange_pub;
-  struct TALER_ExchangeSignatureP exchange_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    TALER_JSON_spec_amount_any ("refund_fee",
-                                &refund_fee),
-    GNUNET_JSON_spec_fixed_auto ("purse_pub",
-                                 &purse_pub),
-    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
-                                 &exchange_sig),
-    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
-                                 &exchange_pub),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_exchange_online_purse_refund_verify (
-        amount,
-        &refund_fee,
-        pc->coin_pub,
-        &purse_pub,
-        &exchange_pub,
-        &exchange_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if ( (GNUNET_YES !=
-        TALER_amount_cmp_currency (&refund_fee,
-                                   &pc->dk->fees.refund)) ||
-       (0 !=
-        TALER_amount_cmp (&refund_fee,
-                          &pc->dk->fees.refund)) )
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_NO;
-}
-
-
-/**
- * Handle reserve deposit entry in the coin's history.
- *
- * @param[in,out] pc overall context
- * @param amount main amount of this operation
- * @param transaction JSON details for the operation
- * @return #GNUNET_SYSERR on error,
- *         #GNUNET_OK to add, #GNUNET_NO to subtract
- */
-static enum GNUNET_GenericReturnValue
-help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
-                           const struct TALER_Amount *amount,
-                           json_t *transaction)
-{
-  struct TALER_ReserveSignatureP reserve_sig;
-  struct TALER_CoinSpendSignatureP coin_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
-                                 &reserve_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &coin_sig),
-    GNUNET_JSON_spec_end ()
-  };
-
-  if (GNUNET_OK !=
-      GNUNET_JSON_parse (transaction,
-                         spec,
-                         NULL, NULL))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  if (GNUNET_OK !=
-      TALER_wallet_reserve_open_deposit_verify (
-        amount,
-        &reserve_sig,
-        pc->coin_pub,
-        &coin_sig))
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_YES;
-}
-
-
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_verify_coin_history (
-  const struct TALER_EXCHANGE_DenomPublicKey *dk,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const json_t *history,
-  struct TALER_Amount *total)
-{
-  const char *currency = dk->value.currency;
-  const struct
-  {
-    const char *type;
-    CoinCheckHelper helper;
-  } map[] = {
-    { "DEPOSIT", &help_deposit },
-    { "MELT", &help_melt },
-    { "REFUND", &help_refund },
-    { "RECOUP", &help_recoup },
-    { "RECOUP-REFRESH", &help_recoup_refresh },
-    { "OLD-COIN-RECOUP", &help_old_coin_recoup },
-    { "PURSE-DEPOSIT", &help_purse_deposit },
-    { "PURSE-REFUND", &help_purse_refund },
-    { "RESERVE-OPEN-DEPOSIT", &help_reserve_open_deposit },
-    { NULL, NULL }
-  };
-  struct CoinHistoryParseContext pc = {
-    .dk = dk,
-    .coin_pub = coin_pub,
-    .total = total
-  };
-  size_t len;
-
-  if (NULL == history)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  len = json_array_size (history);
-  if (0 == len)
-  {
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (currency,
-                                        total));
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_set_zero (currency,
-                                        &pc.rtotal));
-  for (size_t off = 0; off<len; off++)
-  {
-    enum GNUNET_GenericReturnValue add;
-    json_t *transaction;
-    struct TALER_Amount amount;
-    const char *type;
-    struct GNUNET_JSON_Specification spec_glob[] = {
-      TALER_JSON_spec_amount_any ("amount",
-                                  &amount),
-      GNUNET_JSON_spec_string ("type",
-                               &type),
-      GNUNET_JSON_spec_end ()
-    };
-
-    transaction = json_array_get (history,
-                                  off);
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (transaction,
-                           spec_glob,
-                           NULL, NULL))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-    if (GNUNET_YES !=
-        TALER_amount_cmp_currency (&amount,
-                                   &pc.rtotal))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Operation of type %s with amount %s\n",
-                type,
-                TALER_amount2s (&amount));
-    add = GNUNET_SYSERR;
-    for (unsigned int i = 0; NULL != map[i].type; i++)
-    {
-      if (0 == strcasecmp (type,
-                           map[i].type))
-      {
-        add = map[i].helper (&pc,
-                             &amount,
-                             transaction);
-        break;
-      }
-    }
-    switch (add)
-    {
-    case GNUNET_SYSERR:
-      /* entry type not supported, new version on server? */
-      GNUNET_break_op (0);
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Unexpected type `%s' in response\n",
-                  type);
-      return GNUNET_SYSERR;
-    case GNUNET_YES:
-      /* This amount should be added to the total */
-      if (0 >
-          TALER_amount_add (total,
-                            total,
-                            &amount))
-      {
-        /* overflow in history already!? inconceivable! Bad exchange! */
-        GNUNET_break_op (0);
-        return GNUNET_SYSERR;
-      }
-      break;
-    case GNUNET_NO:
-      /* This amount should be subtracted from the total.
-
-         However, for the implementation, we first *add* up all of
-         these negative amounts, as we might get refunds before
-         deposits from a semi-evil exchange.  Then, at the end, we do
-         the subtraction by calculating "total = total - rtotal" */
-      if (0 >
-          TALER_amount_add (&pc.rtotal,
-                            &pc.rtotal,
-                            &amount))
-      {
-        /* overflow in refund history? inconceivable! Bad exchange! */
-        GNUNET_break_op (0);
-        return GNUNET_SYSERR;
-      }
-      break;
-    } /* end of switch(add) */
-  }
-  /* Finally, subtract 'rtotal' from total to handle the subtractions */
-  if (0 >
-      TALER_amount_subtract (total,
-                             total,
-                             &pc.rtotal))
-  {
-    /* underflow in history? inconceivable! Bad exchange! */
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 const struct TALER_EXCHANGE_SigningPublicKey *
 TALER_EXCHANGE_get_signing_key_info (
   const struct TALER_EXCHANGE_Keys *keys,
@@ -1811,59 +275,7 @@ TALER_EXCHANGE_check_purse_econtract_conflict_ (
 }
 
 
-/**
- * Verify that @a coin_sig does NOT appear in
- * the history of @a proof and thus whatever transaction
- * is authorized by @a coin_sig is a conflict with
- * @a proof.
- *
- * @param proof a proof to check
- * @param coin_sig signature that must not be in @a proof
- * @return #GNUNET_OK if @a coin_sig is not in @a proof
- */
-// FIXME: move to exchange_api_coin_history.c!
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_signature_conflict_ (
-  const json_t *proof,
-  const struct TALER_CoinSpendSignatureP *coin_sig)
-{
-  json_t *history;
-  size_t off;
-  json_t *entry;
-
-  history = json_object_get (proof,
-                             "history");
-  if (NULL == history)
-  {
-    GNUNET_break (0);
-    return GNUNET_SYSERR;
-  }
-  json_array_foreach (history, off, entry)
-  {
-    struct TALER_CoinSpendSignatureP cs;
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                   &cs),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (entry,
-                           spec,
-                           NULL, NULL))
-      continue; /* entry without coin signature */
-    if (0 ==
-        GNUNET_memcmp (&cs,
-                       coin_sig))
-    {
-      GNUNET_break_op (0);
-      return GNUNET_SYSERR;
-    }
-  }
-  return GNUNET_OK;
-}
-
-
+// FIXME: should be used...
 enum GNUNET_GenericReturnValue
 TALER_EXCHANGE_check_coin_denomination_conflict_ (
   const json_t *proof,
@@ -1896,47 +308,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ (
 }
 
 
-/**
- * Check that the provided @a proof indeeds indicates
- * a conflict for @a coin_pub.
- *
- * @param keys exchange keys
- * @param proof provided conflict proof
- * @param dk denomination of @a coin_pub that the client
- *           used
- * @param coin_pub public key of the coin
- * @param required balance required on the coin for the operation
- * @return #GNUNET_OK if @a proof holds
- */
-// FIXME: move to exchange_api_coin_history.c!
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_conflict_ (
-  const struct TALER_EXCHANGE_Keys *keys,
-  const json_t *proof,
-  const struct TALER_EXCHANGE_DenomPublicKey *dk,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const struct TALER_Amount *required)
-{
-  enum TALER_ErrorCode ec;
-
-  ec = TALER_JSON_get_error_code (proof);
-  switch (ec)
-  {
-  case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
-    /* Nothing to check anymore here, proof needs to be
-       checked in the GET /coins/$COIN_PUB handler */
-    break;
-  case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
-    // FIXME: write check!
-    break;
-  default:
-    GNUNET_break_op (0);
-    return GNUNET_SYSERR;
-  }
-  return GNUNET_OK;
-}
-
-
 enum GNUNET_GenericReturnValue
 TALER_EXCHANGE_get_min_denomination_ (
   const struct TALER_EXCHANGE_Keys *keys,
diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h
index 0fcaac3f..f1f0fd7f 100644
--- a/src/lib/exchange_api_common.h
+++ b/src/lib/exchange_api_common.h
@@ -145,43 +145,6 @@ TALER_EXCHANGE_check_coin_denomination_conflict_ (
   const struct TALER_DenominationHashP *ch_denom_pub);
 
 
-/**
- * Verify that @a coin_sig does NOT appear in
- * the history of @a proof and thus whatever transaction
- * is authorized by @a coin_sig is a conflict with
- * @a proof.
- *
- * @param proof a proof to check
- * @param coin_sig signature that must not be in @a proof
- * @return #GNUNET_OK if @a coin_sig is not in @a proof
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_signature_conflict_ (
-  const json_t *proof,
-  const struct TALER_CoinSpendSignatureP *coin_sig);
-
-
-/**
- * Check that the provided @a proof indeeds indicates
- * a conflict for @a coin_pub.
- *
- * @param keys exchange keys
- * @param proof provided conflict proof
- * @param dk denomination of @a coin_pub that the client
- *           used
- * @param coin_pub public key of the coin
- * @param required balance required on the coin for the operation
- * @return #GNUNET_OK if @a proof holds
- */
-enum GNUNET_GenericReturnValue
-TALER_EXCHANGE_check_coin_conflict_ (
-  const struct TALER_EXCHANGE_Keys *keys,
-  const json_t *proof,
-  const struct TALER_EXCHANGE_DenomPublicKey *dk,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const struct TALER_Amount *required);
-
-
 /**
  * Find the smallest denomination amount in @e keys.
  *
diff --git a/src/lib/exchange_api_reserves_history.c 
b/src/lib/exchange_api_reserves_history.c
index c92fad5e..b62da1af 100644
--- a/src/lib/exchange_api_reserves_history.c
+++ b/src/lib/exchange_api_reserves_history.c
@@ -77,6 +77,725 @@ struct TALER_EXCHANGE_ReservesHistoryHandle
 };
 
 
+/**
+ * Context for history entry helpers.
+ */
+struct HistoryParseContext
+{
+
+  /**
+   * Keys of the exchange we use.
+   */
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * Our reserve public key.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Array of UUIDs.
+   */
+  struct GNUNET_HashCode *uuids;
+
+  /**
+   * Where to sum up total inbound amounts.
+   */
+  struct TALER_Amount *total_in;
+
+  /**
+   * Where to sum up total outbound amounts.
+   */
+  struct TALER_Amount *total_out;
+
+  /**
+   * Number of entries already used in @e uuids.
+   */
+  unsigned int uuid_off;
+};
+
+
+/**
+ * Type of a function called to parse a reserve history
+ * entry @a rh.
+ *
+ * @param[in,out] rh where to write the result
+ * @param[in,out] uc UUID context for duplicate detection
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+               struct HistoryParseContext *uc,
+               const json_t *transaction);
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+              struct HistoryParseContext *uc,
+              const json_t *transaction)
+{
+  const char *wire_url;
+  uint64_t wire_reference;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct GNUNET_JSON_Specification withdraw_spec[] = {
+    GNUNET_JSON_spec_uint64 ("wire_reference",
+                             &wire_reference),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_string ("sender_account_url",
+                             &wire_url),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CREDIT;
+  if (0 >
+      TALER_amount_add (uc->total_in,
+                        uc->total_in,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         withdraw_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rh->details.in_details.sender_url = GNUNET_strdup (wire_url);
+  rh->details.in_details.wire_reference = wire_reference;
+  rh->details.in_details.timestamp = timestamp;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+                struct HistoryParseContext *uc,
+                const json_t *transaction)
+{
+  struct TALER_ReserveSignatureP sig;
+  struct TALER_DenominationHashP h_denom_pub;
+  struct TALER_BlindedCoinHashP bch;
+  struct TALER_Amount withdraw_fee;
+  struct GNUNET_JSON_Specification withdraw_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &sig),
+    TALER_JSON_spec_amount_any ("withdraw_fee",
+                                &withdraw_fee),
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 &h_denom_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+                                 &bch),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         withdraw_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Check that the signature is a valid withdraw request */
+  if (GNUNET_OK !=
+      TALER_wallet_withdraw_verify (&h_denom_pub,
+                                    &rh->amount,
+                                    &bch,
+                                    uc->reserve_pub,
+                                    &sig))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (withdraw_spec);
+    return GNUNET_SYSERR;
+  }
+  /* check that withdraw fee matches expectations! */
+  {
+    const struct TALER_EXCHANGE_Keys *key_state;
+    const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+    key_state = uc->keys;
+    dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+                                                       &h_denom_pub);
+    if ( (GNUNET_YES !=
+          TALER_amount_cmp_currency (&withdraw_fee,
+                                     &dki->fees.withdraw)) ||
+         (0 !=
+          TALER_amount_cmp (&withdraw_fee,
+                            &dki->fees.withdraw)) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (withdraw_spec);
+      return GNUNET_SYSERR;
+    }
+    rh->details.withdraw.fee = withdraw_fee;
+  }
+  rh->details.withdraw.out_authorization_sig
+    = json_object_get (transaction,
+                       "signature");
+  /* Check check that the same withdraw transaction
+       isn't listed twice by the exchange. We use the
+       "uuid" array to remember the hashes of all
+       signatures, and compare the hashes to find
+       duplicates. */
+  GNUNET_CRYPTO_hash (&sig,
+                      sizeof (sig),
+                      &uc->uuids[uc->uuid_off]);
+  for (unsigned int i = 0; i<uc->uuid_off; i++)
+  {
+    if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
+                            &uc->uuids[i]))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (withdraw_spec);
+      return GNUNET_SYSERR;
+    }
+  }
+  uc->uuid_off++;
+
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (withdraw_spec);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "recoup" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+              struct HistoryParseContext *uc,
+              const json_t *transaction)
+{
+  const struct TALER_EXCHANGE_Keys *key_state;
+  struct GNUNET_JSON_Specification recoup_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &rh->details.recoup_details.coin_pub),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &rh->details.recoup_details.exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &rh->details.recoup_details.exchange_pub),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &rh->details.recoup_details.timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_RECOUP;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         recoup_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  key_state = uc->keys;
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_test_signing_key (key_state,
+                                       &rh->details.
+                                       recoup_details.exchange_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_verify (
+        rh->details.recoup_details.timestamp,
+        &rh->amount,
+        &rh->details.recoup_details.coin_pub,
+        uc->reserve_pub,
+        &rh->details.recoup_details.exchange_pub,
+        &rh->details.recoup_details.exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_in,
+                        uc->total_in,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "closing" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+               struct HistoryParseContext *uc,
+               const json_t *transaction)
+{
+  const struct TALER_EXCHANGE_Keys *key_state;
+  struct GNUNET_JSON_Specification closing_spec[] = {
+    GNUNET_JSON_spec_string (
+      "receiver_account_details",
+      &rh->details.close_details.receiver_account_details),
+    GNUNET_JSON_spec_fixed_auto ("wtid",
+                                 &rh->details.close_details.wtid),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &rh->details.close_details.exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &rh->details.close_details.exchange_pub),
+    TALER_JSON_spec_amount_any ("closing_fee",
+                                &rh->details.close_details.fee),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &rh->details.close_details.timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CLOSING;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         closing_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  key_state = uc->keys;
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_test_signing_key (
+        key_state,
+        &rh->details.close_details.exchange_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_reserve_closed_verify (
+        rh->details.close_details.timestamp,
+        &rh->amount,
+        &rh->details.close_details.fee,
+        rh->details.close_details.receiver_account_details,
+        &rh->details.close_details.wtid,
+        uc->reserve_pub,
+        &rh->details.close_details.exchange_pub,
+        &rh->details.close_details.exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "merge" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+             struct HistoryParseContext *uc,
+             const json_t *transaction)
+{
+  uint32_t flags32;
+  struct GNUNET_JSON_Specification merge_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &rh->details.merge_details.h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merge_pub",
+                                 &rh->details.merge_details.merge_pub),
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &rh->details.merge_details.purse_pub),
+    GNUNET_JSON_spec_uint32 ("min_age",
+                             &rh->details.merge_details.min_age),
+    GNUNET_JSON_spec_uint32 ("flags",
+                             &flags32),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.merge_details.reserve_sig),
+    TALER_JSON_spec_amount_any ("purse_fee",
+                                &rh->details.merge_details.purse_fee),
+    GNUNET_JSON_spec_timestamp ("merge_timestamp",
+                                &rh->details.merge_details.merge_timestamp),
+    GNUNET_JSON_spec_timestamp ("purse_expiration",
+                                &rh->details.merge_details.purse_expiration),
+    GNUNET_JSON_spec_bool ("merged",
+                           &rh->details.merge_details.merged),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_MERGE;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         merge_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rh->details.merge_details.flags =
+    (enum TALER_WalletAccountMergeFlags) flags32;
+  if (GNUNET_OK !=
+      TALER_wallet_account_merge_verify (
+        rh->details.merge_details.merge_timestamp,
+        &rh->details.merge_details.purse_pub,
+        rh->details.merge_details.purse_expiration,
+        &rh->details.merge_details.h_contract_terms,
+        &rh->amount,
+        &rh->details.merge_details.purse_fee,
+        rh->details.merge_details.min_age,
+        rh->details.merge_details.flags,
+        uc->reserve_pub,
+        &rh->details.merge_details.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (rh->details.merge_details.merged)
+  {
+    if (0 >
+        TALER_amount_add (uc->total_in,
+                          uc->total_in,
+                          &rh->amount))
+    {
+      /* overflow in history already!? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  else
+  {
+    if (0 >
+        TALER_amount_add (uc->total_out,
+                          uc->total_out,
+                          &rh->details.merge_details.purse_fee))
+    {
+      /* overflow in history already!? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "open" reserve open entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+            struct HistoryParseContext *uc,
+            const json_t *transaction)
+{
+  struct GNUNET_JSON_Specification open_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.open_request.reserve_sig),
+    TALER_JSON_spec_amount_any ("open_payment",
+                                &rh->details.open_request.reserve_payment),
+    GNUNET_JSON_spec_uint32 ("requested_min_purses",
+                             &rh->details.open_request.purse_limit),
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rh->details.open_request.request_timestamp),
+    GNUNET_JSON_spec_timestamp ("requested_expiration",
+                                &rh->details.open_request.reserve_expiration),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_OPEN;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         open_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_open_verify (
+        &rh->amount,
+        rh->details.open_request.request_timestamp,
+        rh->details.open_request.reserve_expiration,
+        rh->details.open_request.purse_limit,
+        uc->reserve_pub,
+        &rh->details.open_request.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "close" reserve close entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+             struct HistoryParseContext *uc,
+             const json_t *transaction)
+{
+  struct GNUNET_JSON_Specification close_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.close_request.reserve_sig),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_payto",
+                                   &rh->details.close_request.
+                                   target_account_h_payto),
+      NULL),
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rh->details.close_request.request_timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CLOSE;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         close_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* force amount to invalid */
+  memset (&rh->amount,
+          0,
+          sizeof (rh->amount));
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_close_verify (
+        rh->details.close_request.request_timestamp,
+        &rh->details.close_request.target_account_h_payto,
+        uc->reserve_pub,
+        &rh->details.close_request.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+static void
+free_reserve_history (
+  unsigned int len,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
+{
+  for (unsigned int i = 0; i<len; i++)
+  {
+    switch (rhistory[i].type)
+    {
+    case TALER_EXCHANGE_RTT_CREDIT:
+      GNUNET_free (rhistory[i].details.in_details.sender_url);
+      break;
+    case TALER_EXCHANGE_RTT_WITHDRAWAL:
+      break;
+    case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+      break;
+    case TALER_EXCHANGE_RTT_RECOUP:
+      break;
+    case TALER_EXCHANGE_RTT_CLOSING:
+      break;
+    case TALER_EXCHANGE_RTT_MERGE:
+      break;
+    case TALER_EXCHANGE_RTT_OPEN:
+      break;
+    case TALER_EXCHANGE_RTT_CLOSE:
+      break;
+    }
+  }
+  GNUNET_free (rhistory);
+}
+
+
+/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param keys exchange keys
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] total_in set to value of credits to reserve
+ * @param[out] total_out set to value of debits from reserve
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ *             parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ *         were set,
+ *         #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+static enum GNUNET_GenericReturnValue
+parse_reserve_history (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *history,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *currency,
+  struct TALER_Amount *total_in,
+  struct TALER_Amount *total_out,
+  unsigned int history_length,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
+{
+  const struct
+  {
+    const char *type;
+    ParseHelper helper;
+  } map[] = {
+    { "CREDIT", &parse_credit },
+    { "WITHDRAW", &parse_withdraw },
+    { "RECOUP", &parse_recoup },
+    { "MERGE", &parse_merge },
+    { "CLOSING", &parse_closing },
+    { "OPEN", &parse_open },
+    { "CLOSE", &parse_close },
+    { NULL, NULL }
+  };
+  struct GNUNET_HashCode uuid[history_length];
+  struct HistoryParseContext uc = {
+    .keys = keys,
+    .reserve_pub = reserve_pub,
+    .uuids = uuid,
+    .total_in = total_in,
+    .total_out = total_out
+  };
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total_in));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total_out));
+  for (unsigned int off = 0; off<history_length; off++)
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
+    json_t *transaction;
+    struct TALER_Amount amount;
+    const char *type;
+    struct GNUNET_JSON_Specification hist_spec[] = {
+      GNUNET_JSON_spec_string ("type",
+                               &type),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
+      /* 'wire' and 'signature' are optional depending on 'type'! */
+      GNUNET_JSON_spec_end ()
+    };
+    bool found = false;
+
+    transaction = json_array_get (history,
+                                  off);
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (transaction,
+                           hist_spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      json_dumpf (transaction,
+                  stderr,
+                  JSON_INDENT (2));
+      return GNUNET_SYSERR;
+    }
+    rh->amount = amount;
+    if (GNUNET_YES !=
+        TALER_amount_cmp_currency (&amount,
+                                   total_in))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    for (unsigned int i = 0; NULL != map[i].type; i++)
+    {
+      if (0 == strcasecmp (map[i].type,
+                           type))
+      {
+        found = true;
+        if (GNUNET_OK !=
+            map[i].helper (rh,
+                           &uc,
+                           transaction))
+        {
+          GNUNET_break_op (0);
+          return GNUNET_SYSERR;
+        }
+        break;
+      }
+    }
+    if (! found)
+    {
+      /* unexpected 'type', protocol incompatibility, complain! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
 /**
  * We received an #MHD_HTTP_OK history code. Handle the JSON
  * response.
@@ -119,18 +838,18 @@ handle_reserves_history_ok (struct 
TALER_EXCHANGE_ReservesHistoryHandle *rsh,
     rhistory = GNUNET_new_array (len,
                                  struct TALER_EXCHANGE_ReserveHistoryEntry);
     if (GNUNET_OK !=
-        TALER_EXCHANGE_parse_reserve_history (rsh->keys,
-                                              history,
-                                              &rsh->reserve_pub,
-                                              rs.details.ok.balance.currency,
-                                              &rs.details.ok.total_in,
-                                              &rs.details.ok.total_out,
-                                              len,
-                                              rhistory))
+        parse_reserve_history (rsh->keys,
+                               history,
+                               &rsh->reserve_pub,
+                               rs.details.ok.balance.currency,
+                               &rs.details.ok.total_in,
+                               &rs.details.ok.total_out,
+                               len,
+                               rhistory))
     {
       GNUNET_break_op (0);
-      TALER_EXCHANGE_free_reserve_history (len,
-                                           rhistory);
+      free_reserve_history (len,
+                            rhistory);
       GNUNET_JSON_parse_free (spec);
       return GNUNET_SYSERR;
     }
@@ -142,8 +861,8 @@ handle_reserves_history_ok (struct 
TALER_EXCHANGE_ReservesHistoryHandle *rsh,
                &rs);
       rsh->cb = NULL;
     }
-    TALER_EXCHANGE_free_reserve_history (len,
-                                         rhistory);
+    free_reserve_history (len,
+                          rhistory);
   }
   return GNUNET_OK;
 }
@@ -244,7 +963,7 @@ TALER_EXCHANGE_reserves_history (
   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
   CURL *eh;
   char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 64];
-  struct TALER_ReserveSignatureP reserve_sig;
+  struct curl_slist *job_headers;
 
   rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
   rsh->cb = cb;
@@ -289,34 +1008,41 @@ TALER_EXCHANGE_reserves_history (
     GNUNET_free (rsh);
     return NULL;
   }
-  TALER_wallet_reserve_history_sign (start_off,
-                                     reserve_priv,
-                                     &reserve_sig);
+
   {
-    json_t *history_obj = GNUNET_JSON_PACK (
-      GNUNET_JSON_pack_data_auto ("reserve_sig",
-                                  &reserve_sig));
+    struct TALER_ReserveSignatureP reserve_sig;
+    char *sig_hdr;
+    char *hdr;
 
-    if (GNUNET_OK !=
-        TALER_curl_easy_post (&rsh->post_ctx,
-                              eh,
-                              history_obj))
+    TALER_wallet_reserve_history_sign (start_off,
+                                       reserve_priv,
+                                       &reserve_sig);
+
+    sig_hdr = GNUNET_STRINGS_data_to_string_alloc (
+      &reserve_sig,
+      sizeof (reserve_sig));
+    GNUNET_asprintf (&hdr,
+                     "%s: %s",
+                     TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+                     sig_hdr);
+    GNUNET_free (sig_hdr);
+    job_headers = curl_slist_append (NULL,
+                                     hdr);
+    GNUNET_free (hdr);
+    if (NULL == job_headers)
     {
       GNUNET_break (0);
-      curl_easy_cleanup (eh);
-      json_decref (history_obj);
-      GNUNET_free (rsh->url);
-      GNUNET_free (rsh);
       return NULL;
     }
-    json_decref (history_obj);
   }
+
   rsh->keys = TALER_EXCHANGE_keys_incref (keys);
   rsh->job = GNUNET_CURL_job_add2 (ctx,
                                    eh,
-                                   rsh->post_ctx.headers,
+                                   job_headers,
                                    &handle_reserves_history_finished,
                                    rsh);
+  curl_slist_free_all (job_headers);
   return rsh;
 }
 
diff --git a/src/mhd/mhd_parsing.c b/src/mhd/mhd_parsing.c
index b1f8417e..381b064f 100644
--- a/src/mhd/mhd_parsing.c
+++ b/src/mhd/mhd_parsing.c
@@ -209,6 +209,42 @@ TALER_MHD_parse_request_arg_timeout (struct MHD_Connection 
*connection,
 }
 
 
+enum GNUNET_GenericReturnValue
+TALER_MHD_parse_request_arg_number (struct MHD_Connection *connection,
+                                    const char *name,
+                                    uint64_t *off)
+{
+  const char *ts;
+  char dummy;
+  unsigned long long num;
+
+  ts = MHD_lookup_connection_value (connection,
+                                    MHD_GET_ARGUMENT_KIND,
+                                    name);
+  if (NULL == ts)
+    return GNUNET_OK;
+  if (1 !=
+      sscanf (ts,
+              "%llu%c",
+              &num,
+              &dummy))
+  {
+    MHD_RESULT mret;
+
+    GNUNET_break_op (0);
+    mret = TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       name);
+    return (MHD_YES == mret)
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  *off = (uint64_t) num;
+  return GNUNET_OK;
+}
+
+
 enum GNUNET_GenericReturnValue
 TALER_MHD_parse_json_data (struct MHD_Connection *connection,
                            const json_t *root,

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