gnunet-svn
[Top][All Lists]
Advanced

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

[taler-exchange] branch master updated: major exchange DB schema change:


From: gnunet
Subject: [taler-exchange] branch master updated: major exchange DB schema change: store common properties of batch deposit in batch_deposits table, and coin-specific ones in coin_deposits table; plus minor cleanups
Date: Sun, 10 Sep 2023 19:13:59 +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 852c4666 major exchange DB schema change: store common properties of 
batch deposit in batch_deposits table, and coin-specific ones in coin_deposits 
table; plus minor cleanups
852c4666 is described below

commit 852c46668f1c9f766de9473c50e787e32a0f8697
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Sep 10 19:13:49 2023 +0200

    major exchange DB schema change: store common properties of batch deposit 
in batch_deposits table, and coin-specific ones in coin_deposits table; plus 
minor cleanups
---
 src/auditor/taler-auditor-sync.c                   |   3 +-
 src/auditor/taler-helper-auditor-coins.c           |   2 +-
 src/auditor/taler-helper-auditor-wire.c            |  10 +-
 src/benchmark/taler-aggregator-benchmark.c         |  53 +-
 src/exchange/Makefile.am                           |   1 -
 src/exchange/taler-exchange-aggregator.c           |   8 +-
 src/exchange/taler-exchange-httpd.c                |   5 -
 src/exchange/taler-exchange-httpd_batch-deposit.c  | 498 ++++++++----------
 src/exchange/taler-exchange-httpd_deposit.c        | 569 ---------------------
 src/exchange/taler-exchange-httpd_deposit.h        |  49 --
 src/exchangedb/0002-aggregation_tracking.sql       |   9 +-
 src/exchangedb/0002-batch_deposits.sql             | 154 ++++++
 .../{0002-refunds.sql => 0002-coin_deposits.sql}   | 101 ++--
 .../{0002-refunds.sql => 0002-coin_history.sql}    |  76 +--
 src/exchangedb/0002-deposits.sql                   | 426 ---------------
 src/exchangedb/0002-refunds.sql                    |  10 +-
 src/exchangedb/Makefile.am                         |   6 +-
 src/exchangedb/exchange-0002.sql.in                |   4 +-
 src/exchangedb/exchange_do_deposit.sql             | 228 +++++----
 src/exchangedb/exchange_do_gc.sql                  |  26 +-
 src/exchangedb/exchange_do_get_ready_deposit.sql   |  69 ---
 src/exchangedb/exchange_do_refund.sql              |  42 +-
 src/exchangedb/exchange_do_refund_by_coin.sql      |  86 ----
 src/exchangedb/exchange_get_ready_deposit.sql      |  60 ---
 src/exchangedb/perf_deposits_get_ready.c           |  43 +-
 src/exchangedb/perf_select_refunds_by_coin.c       |  75 +--
 src/exchangedb/pg_aggregate.c                      |  57 ++-
 src/exchangedb/pg_do_deposit.c                     |  85 +--
 src/exchangedb/pg_do_deposit.h                     |  17 +-
 src/exchangedb/pg_get_coin_transactions.c          |  42 +-
 src/exchangedb/pg_get_ready_deposit.c              |  87 +---
 src/exchangedb/pg_have_deposit2.c                  |  28 +-
 src/exchangedb/pg_insert_aggregation_tracking.c    |  53 --
 src/exchangedb/pg_insert_aggregation_tracking.h    |  42 --
 src/exchangedb/pg_insert_deposit.c                 | 103 ----
 src/exchangedb/pg_insert_deposit.h                 |  40 --
 src/exchangedb/pg_insert_records_by_table.c        | 122 +++--
 src/exchangedb/pg_insert_refund.c                  |   9 +-
 src/exchangedb/pg_lookup_records_by_table.c        | 156 ++++--
 src/exchangedb/pg_lookup_serial_by_table.c         |  20 +-
 src/exchangedb/pg_lookup_transfer_by_deposit.c     |  51 +-
 src/exchangedb/pg_lookup_wire_transfer.c           |  17 +-
 src/exchangedb/pg_persist_policy_details.h         |   2 +
 ...e.c => pg_select_batch_deposits_missing_wire.c} |  58 ++-
 ...e.h => pg_select_batch_deposits_missing_wire.h} |  17 +-
 ...c => pg_select_coin_deposits_above_serial_id.c} |  76 +--
 ...h => pg_select_coin_deposits_above_serial_id.h} |   6 +-
 src/exchangedb/pg_select_refunds_above_serial_id.c |  20 +-
 src/exchangedb/pg_select_refunds_by_coin.c         | 210 +-------
 src/exchangedb/plugin_exchangedb_postgres.c        |  18 +-
 src/exchangedb/procedures.sql.in                   |   2 -
 src/exchangedb/test_exchangedb.c                   | 159 +++---
 src/include/taler_exchangedb_plugin.h              | 273 +++++++---
 src/testing/testing_api_cmd_age_withdraw.c         |  23 +-
 src/testing/testing_api_cmd_insert_deposit.c       |  52 +-
 55 files changed, 1622 insertions(+), 2836 deletions(-)

diff --git a/src/auditor/taler-auditor-sync.c b/src/auditor/taler-auditor-sync.c
index 2b4bf855..e4022d32 100644
--- a/src/auditor/taler-auditor-sync.c
+++ b/src/auditor/taler-auditor-sync.c
@@ -108,7 +108,8 @@ static struct Table tables[] = {
   { .rt = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS},
   { .rt = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS},
   { .rt = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS},
-  { .rt = TALER_EXCHANGEDB_RT_DEPOSITS},
+  { .rt = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS},
+  { .rt = TALER_EXCHANGEDB_RT_COIN_DEPOSITS},
   { .rt = TALER_EXCHANGEDB_RT_REFUNDS},
   { .rt = TALER_EXCHANGEDB_RT_WIRE_OUT},
   { .rt = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING},
diff --git a/src/auditor/taler-helper-auditor-coins.c 
b/src/auditor/taler-helper-auditor-coins.c
index 8edbcf29..5ce7fcb4 100644
--- a/src/auditor/taler-helper-auditor-coins.c
+++ b/src/auditor/taler-helper-auditor-coins.c
@@ -2553,7 +2553,7 @@ analyze_coins (void *cls)
 
   /* process deposits */
   if (0 >
-      (qs = TALER_ARL_edb->select_deposits_above_serial_id (
+      (qs = TALER_ARL_edb->select_coin_deposits_above_serial_id (
          TALER_ARL_edb->cls,
          ppc.last_deposit_serial_id,
          &deposit_cb,
diff --git a/src/auditor/taler-helper-auditor-wire.c 
b/src/auditor/taler-helper-auditor-wire.c
index 4e4d0ae2..edfdd5af 100644
--- a/src/auditor/taler-helper-auditor-wire.c
+++ b/src/auditor/taler-helper-auditor-wire.c
@@ -914,11 +914,11 @@ check_for_required_transfers (void)
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Analyzing exchange's unfinished deposits (deadline: %s)\n",
               GNUNET_TIME_timestamp2s (next_timestamp));
-  qs = TALER_ARL_edb->select_deposits_missing_wire (TALER_ARL_edb->cls,
-                                                    pp.last_timestamp,
-                                                    next_timestamp,
-                                                    &wire_missing_cb,
-                                                    &next_timestamp);
+  qs = TALER_ARL_edb->select_batch_deposits_missing_wire (TALER_ARL_edb->cls,
+                                                          pp.last_timestamp,
+                                                          next_timestamp,
+                                                          &wire_missing_cb,
+                                                          &next_timestamp);
   if (0 > qs)
   {
     GNUNET_break (0);
diff --git a/src/benchmark/taler-aggregator-benchmark.c 
b/src/benchmark/taler-aggregator-benchmark.c
index 193a1ada..b59d3a8c 100644
--- a/src/benchmark/taler-aggregator-benchmark.c
+++ b/src/benchmark/taler-aggregator-benchmark.c
@@ -274,7 +274,11 @@ static bool
 add_deposit (const struct Merchant *m)
 {
   struct Deposit d;
-  struct TALER_EXCHANGEDB_Deposit deposit;
+  struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+  struct TALER_EXCHANGEDB_BatchDeposit bd = {
+    .cdis = &deposit,
+    .num_cdis = 1
+  };
   uint64_t known_coin_id;
   struct TALER_DenominationHashP dph;
   struct TALER_AgeCommitmentHash agh;
@@ -302,29 +306,40 @@ add_deposit (const struct Merchant *m)
   }
   deposit.coin = d.coin;
   RANDOMIZE (&deposit.csig);
-  deposit.merchant_pub = m->merchant_pub;
-  deposit.h_contract_terms = d.h_contract_terms;
-  deposit.wire_salt = m->wire_salt;
-  deposit.receiver_wire_account = m->payto_uri;
-  deposit.timestamp = random_time ();
+  bd.merchant_pub = m->merchant_pub;
+  bd.h_contract_terms = d.h_contract_terms;
+  bd.wire_salt = m->wire_salt;
+  bd.receiver_wire_account = m->payto_uri;
+  bd.wallet_timestamp = random_time ();
   do {
-    deposit.refund_deadline = random_time ();
-    deposit.wire_deadline = random_time ();
-  } while (GNUNET_TIME_timestamp_cmp (deposit.wire_deadline,
+    bd.refund_deadline = random_time ();
+    bd.wire_deadline = random_time ();
+  } while (GNUNET_TIME_timestamp_cmp (bd.wire_deadline,
                                       <,
-                                      deposit.refund_deadline));
+                                      bd.refund_deadline));
 
   make_amount (1, 0, &deposit.amount_with_fee);
-  make_amount (0, 5, &deposit.deposit_fee);
-  if (0 >=
-      plugin->insert_deposit (plugin->cls,
-                              random_time (),
-                              &deposit))
+
   {
-    GNUNET_break (0);
-    global_ret = EXIT_FAILURE;
-    GNUNET_SCHEDULER_shutdown ();
-    return false;
+    struct GNUNET_TIME_Timestamp now;
+    bool balance_ok;
+    uint32_t bad_idx;
+    bool conflict;
+
+    now = random_time ();
+    if (0 >=
+        plugin->do_deposit (plugin->cls,
+                            &bd,
+                            &now,
+                            &balance_ok,
+                            &bad_idx,
+                            &conflict))
+    {
+      GNUNET_break (0);
+      global_ret = EXIT_FAILURE;
+      GNUNET_SCHEDULER_shutdown ();
+      return false;
+    }
   }
   if (GNUNET_YES ==
       eval_probability (((float) refund_rate) / 100.0))
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
index 732f8dba..607ea919 100644
--- a/src/exchange/Makefile.am
+++ b/src/exchange/Makefile.am
@@ -138,7 +138,6 @@ taler_exchange_httpd_SOURCES = \
   taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
   taler-exchange-httpd_csr.c taler-exchange-httpd_csr.h \
   taler-exchange-httpd_db.c taler-exchange-httpd_db.h \
-  taler-exchange-httpd_deposit.c taler-exchange-httpd_deposit.h \
   taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \
   taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \
   taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \
diff --git a/src/exchange/taler-exchange-aggregator.c 
b/src/exchange/taler-exchange-aggregator.c
index 0b2a7dad..7a579a9f 100644
--- a/src/exchange/taler-exchange-aggregator.c
+++ b/src/exchange/taler-exchange-aggregator.c
@@ -726,10 +726,16 @@ do_aggregate (struct AggregationUnit *au)
     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                 &au->wtid,
                                 sizeof (au->wtid));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "No transient aggregation found, starting %s\n",
+                TALER_B2S (&au->wtid));
     au->have_transient = false;
     break;
   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     au->have_transient = true;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Transient aggregation found, resuming %s\n",
+                TALER_B2S (&au->wtid));
     break;
   }
   qs = db_plugin->aggregate (db_plugin->cls,
@@ -751,7 +757,7 @@ do_aggregate (struct AggregationUnit *au)
                 "Serialization issue, trying again later!\n");
     return GNUNET_NO;
   }
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Aggregation total is %s.\n",
               TALER_amount2s (&au->total_amount));
   /* Subtract wire transfer fee and round to the unit supported by the
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
index acda91f5..149c60ca 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -39,7 +39,6 @@
 #include "taler-exchange-httpd_config.h"
 #include "taler-exchange-httpd_contract.h"
 #include "taler-exchange-httpd_csr.h"
-#include "taler-exchange-httpd_deposit.h"
 #include "taler-exchange-httpd_deposits_get.h"
 #include "taler-exchange-httpd_extensions.h"
 #include "taler-exchange-httpd_keys.h"
@@ -324,10 +323,6 @@ handle_post_coins (struct TEH_RequestContext *rc,
     CoinOpHandler handler;
 
   } h[] = {
-    {
-      .op = "deposit",
-      .handler = &TEH_handler_deposit
-    },
     {
       .op = "melt",
       .handler = &TEH_handler_melt
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c 
b/src/exchange/taler-exchange-httpd_batch-deposit.c
index 488f85ab..a5283d20 100644
--- a/src/exchange/taler-exchange-httpd_batch-deposit.c
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2022 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
@@ -30,7 +30,7 @@
 #include <pthread.h>
 #include "taler_json_lib.h"
 #include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_deposit.h"
+#include "taler-exchange-httpd_batch-deposit.h"
 #include "taler-exchange-httpd_responses.h"
 #include "taler_exchangedb_lib.h"
 #include "taler-exchange-httpd_keys.h"
@@ -41,10 +41,11 @@
  */
 struct BatchDepositContext
 {
+
   /**
-   * Information about the individual coin deposits.
+   * Array with the individual coin deposit fees.
    */
-  struct TALER_EXCHANGEDB_Deposit *deposits;
+  struct TALER_Amount *deposit_fees;
 
   /**
    * Our timestamp (when we received the request).
@@ -54,37 +55,9 @@ struct BatchDepositContext
   struct GNUNET_TIME_Timestamp exchange_timestamp;
 
   /**
-   * Hash over the proposal data between merchant and customer
-   * (remains unknown to the Exchange).
-   */
-  struct TALER_PrivateContractHashP h_contract_terms;
-
-  /**
-   * Public key of the merchant.  Enables later identification
-   * of the merchant in case of a need to rollback transactions.
-   */
-  struct TALER_MerchantPublicKeyP merchant_pub;
-
-  /**
-   * Salt used by the merchant to compute @e h_wire.
-   */
-  struct TALER_WireSaltP wire_salt;
-
-  /**
-   * Hash over the wire details (with @e wire_salt).
-   */
-  struct TALER_MerchantWireHashP h_wire;
-
-  /**
-   * Hash of the payto URI.
-   */
-  struct TALER_PaytoHashP h_payto;
-
-  /**
-   * Information about the receiver for executing the transaction.  URI in
-   * payto://-format.
+   * Details about the batch deposit operation.
    */
-  const char *payto_uri;
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
 
   /**
    * Additional details for policy extension relevant for this
@@ -93,14 +66,9 @@ struct BatchDepositContext
   json_t *policy_json;
 
   /**
-   * Will be true if policy_json were provided
+   * Hash over the merchant's payto://-URI with the wire salt.
    */
-  bool has_policy;
-
-  /**
-   * Hash over @e policy_details, might be all zero
-   */
-  struct TALER_ExtensionPolicyHashP h_policy;
+  struct TALER_MerchantWireHashP h_wire;
 
   /**
    * If @e policy_json was present, the corresponding policy extension
@@ -110,45 +78,16 @@ struct BatchDepositContext
   struct TALER_PolicyDetails policy_details;
 
   /**
-   * When @e policy_details are persisted, this contains the id of the record
-   * in the policy_details table.
-   */
-  uint64_t policy_details_serial_id;
-
-  /**
-   * Time when this request was generated.  Used, for example, to
-   * assess when (roughly) the income was achieved for tax purposes.
-   * Note that the Exchange will only check that the timestamp is not "too
-   * far" into the future (i.e. several days).  The fact that the
-   * timestamp falls within the validity period of the coin's
-   * denomination key is irrelevant for the validity of the deposit
-   * request, as obviously the customer and merchant could conspire to
-   * set any timestamp.  Also, the Exchange must accept very old deposit
-   * requests, as the merchant might have been unable to transmit the
-   * deposit request in a timely fashion (so back-dating is not
-   * prevented).
-   */
-  struct GNUNET_TIME_Timestamp timestamp;
-
-  /**
-   * How much time does the merchant have to issue a refund request?
-   * Zero if refunds are not allowed.  After this time, the coin
-   * cannot be refunded.
+   * Hash over @e policy_details, might be all zero
    */
-  struct GNUNET_TIME_Timestamp refund_deadline;
+  struct TALER_ExtensionPolicyHashP h_policy;
 
   /**
-   * How much time does the merchant have to execute the wire transfer?
-   * This time is advisory for aggregating transactions, not a hard
-   * constraint (as the merchant can theoretically pick any time,
-   * including one in the past).
+   * When @e policy_details are persisted, this contains the id of the record
+   * in the policy_details table.
    */
-  struct GNUNET_TIME_Timestamp wire_deadline;
+  uint64_t policy_details_serial_id;
 
-  /**
-   * Number of coins in the batch.
-   */
-  unsigned int num_coins;
 };
 
 
@@ -160,23 +99,25 @@ struct BatchDepositContext
  * requested batch deposit operation with the given wiring details.
  *
  * @param connection connection to the client
- * @param bdc information about the batch deposit
+ * @param dc information about the batch deposit
  * @return MHD result code
  */
 static MHD_RESULT
 reply_batch_deposit_success (
   struct MHD_Connection *connection,
-  const struct BatchDepositContext *bdc)
+  const struct BatchDepositContext *dc)
 {
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
   json_t *arr;
   struct TALER_ExchangePublicKeyP pub;
 
 again:
   arr = json_array ();
   GNUNET_assert (NULL != arr);
-  for (unsigned int i = 0; i<bdc->num_coins; i++)
+  for (unsigned int i = 0; i<bd->num_cdis; i++)
   {
-    const struct TALER_EXCHANGEDB_Deposit *deposit = &bdc->deposits[i];
+    const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &bd->cdis[i];
     struct TALER_ExchangePublicKeyP pubi;
     struct TALER_ExchangeSignatureP sig;
     enum TALER_ErrorCode ec;
@@ -184,20 +125,20 @@ again:
 
     GNUNET_assert (0 <=
                    TALER_amount_subtract (&amount_without_fee,
-                                          &deposit->amount_with_fee,
-                                          &deposit->deposit_fee));
+                                          &cdi->amount_with_fee,
+                                          &dc->deposit_fees[i]));
     if (TALER_EC_NONE !=
         (ec = TALER_exchange_online_deposit_confirmation_sign (
            &TEH_keys_exchange_sign_,
-           &bdc->h_contract_terms,
-           &bdc->h_wire,
-           bdc->has_policy ? &bdc->h_policy: NULL,
-           bdc->exchange_timestamp,
-           bdc->wire_deadline,
-           bdc->refund_deadline,
+           &bd->h_contract_terms,
+           &dc->h_wire,
+           NULL != dc->policy_json ? &dc->h_policy : NULL,
+           dc->exchange_timestamp,
+           bd->wire_deadline,
+           bd->refund_deadline,
            &amount_without_fee,
-           &deposit->coin.coin_pub,
-           &bdc->merchant_pub,
+           &cdi->coin.coin_pub,
+           &dc->bd.merchant_pub,
            &pubi,
            &sig)))
     {
@@ -232,7 +173,7 @@ again:
     connection,
     MHD_HTTP_OK,
     GNUNET_JSON_pack_timestamp ("exchange_timestamp",
-                                bdc->exchange_timestamp),
+                                dc->exchange_timestamp),
     GNUNET_JSON_pack_data_auto ("exchange_pub",
                                 &pub),
     GNUNET_JSON_pack_array_steal ("exchange_sigs",
@@ -259,79 +200,89 @@ batch_deposit_transaction (void *cls,
                            MHD_RESULT *mhd_ret)
 {
   struct BatchDepositContext *dc = cls;
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
   enum GNUNET_DB_QueryStatus qs = GNUNET_SYSERR;
+  uint32_t bad_balance_coin_index;
   bool balance_ok;
   bool in_conflict;
 
   /* If the deposit has a policy associated to it, persist it.  This will
    * insert or update the record. */
-  if (dc->has_policy)
+  if (NULL != dc->policy_json)
   {
     qs = TEH_plugin->persist_policy_details (
       TEH_plugin->cls,
       &dc->policy_details,
-      &dc->policy_details_serial_id,
+      &dc->bd.policy_details_serial_id,
       &dc->policy_details.accumulated_total,
       &dc->policy_details.fulfillment_state);
     if (qs < 0)
       return qs;
+    /* FIXME-Oec: dc->bd.policy_blocked not initialized,
+       likely should be set based on fulfillment_state!?*/
   }
 
-  for (unsigned int i = 0; i<dc->num_coins; i++)
+  /* FIXME: replace by batch insert! */
+  for (unsigned int i = 0; i<bd->num_cdis; i++)
   {
-    const struct TALER_EXCHANGEDB_Deposit *deposit = &dc->deposits[i];
+    const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &bd->cdis[i];
     uint64_t known_coin_id;
 
-    qs = TEH_make_coin_known (&deposit->coin,
+    qs = TEH_make_coin_known (&cdi->coin,
                               connection,
                               &known_coin_id,
                               mhd_ret);
     if (qs < 0)
       return qs;
-    qs = TEH_plugin->do_deposit (
-      TEH_plugin->cls,
-      deposit,
-      known_coin_id,
-      &dc->h_payto,
-      dc->has_policy
-      ?  &dc->policy_details_serial_id
-      : NULL,
-      &dc->exchange_timestamp,
-      &balance_ok,
-      &in_conflict);
-    if (qs < 0)
-    {
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-        return qs;
-      TALER_LOG_WARNING (
-        "Failed to store /batch-deposit information in database\n");
-      *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                             TALER_EC_GENERIC_DB_STORE_FAILED,
-                                             "batch-deposit");
+  }
+
+  qs = TEH_plugin->do_deposit (
+    TEH_plugin->cls,
+    bd,
+    &dc->exchange_timestamp,
+    &balance_ok,
+    &bad_balance_coin_index,
+    &in_conflict);
+  if (qs < 0)
+  {
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
       return qs;
-    }
-    if (in_conflict)
-    {
-      /* FIXME: #7267 conficting contract != insufficient funds */
-      *mhd_ret
-        = TEH_RESPONSE_reply_coin_insufficient_funds (
-            connection,
-            TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
-            &deposit->coin.denom_pub_hash,
-            &deposit->coin.coin_pub);
-      return GNUNET_DB_STATUS_HARD_ERROR;
-    }
-    if (! balance_ok)
-    {
-      *mhd_ret
-        = TEH_RESPONSE_reply_coin_insufficient_funds (
-            connection,
-            TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
-            &deposit->coin.denom_pub_hash,
-            &deposit->coin.coin_pub);
-      return GNUNET_DB_STATUS_HARD_ERROR;
-    }
+    TALER_LOG_WARNING (
+      "Failed to store /batch-deposit information in database\n");
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_STORE_FAILED,
+                                           "batch-deposit");
+    return qs;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "do_deposit returned %d / %s / %u / %s\n",
+              qs,
+              balance_ok ? "balance ok" : "balance insufficient",
+              bad_balance_coin_index,
+              in_conflict ? "in conflict" : "no conflict");
+  if (in_conflict)
+  {
+    /* FIXME: #7267 conflicting contract != insufficient funds */
+    *mhd_ret
+      = TEH_RESPONSE_reply_coin_insufficient_funds (
+          connection,
+          TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
+          &bd->cdis[0 /* SEE FIXME above! */].coin.denom_pub_hash,
+          &bd->cdis[0 /* SEE FIXME above! */].coin.coin_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (! balance_ok)
+  {
+    GNUNET_assert (bad_balance_coin_index < bd->num_cdis);
+    *mhd_ret
+      = TEH_RESPONSE_reply_coin_insufficient_funds (
+          connection,
+          TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+          &bd->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+          &bd->cdis[bad_balance_coin_index].coin.coin_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
   }
   TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
   return qs;
@@ -344,34 +295,37 @@ batch_deposit_transaction (void *cls,
  * @a ctx.
  *
  * @param connection connection we are handling
+ * @param dc information about the overall batch
  * @param jcoin coin data to parse
- * @param dc overall batch deposit context information to use
- * @param[out] deposit where to store the result
+ * @param[out] cdi where to store the result
+ * @param[out] deposit_fee where to write the deposit fee
  * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
  *         #GNUNET_SYSERR on failure and no error could be returned
  */
 static enum GNUNET_GenericReturnValue
 parse_coin (struct MHD_Connection *connection,
-            json_t *jcoin,
             const struct BatchDepositContext *dc,
-            struct TALER_EXCHANGEDB_Deposit *deposit)
+            json_t *jcoin,
+            struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
+            struct TALER_Amount *deposit_fee)
 {
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc->bd;
   struct GNUNET_JSON_Specification spec[] = {
     TALER_JSON_spec_amount ("contribution",
                             TEH_currency,
-                            &deposit->amount_with_fee),
+                            &cdi->amount_with_fee),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
-                                 &deposit->coin.denom_pub_hash),
+                                 &cdi->coin.denom_pub_hash),
     TALER_JSON_spec_denom_sig ("ub_sig",
-                               &deposit->coin.denom_sig),
+                               &cdi->coin.denom_sig),
     GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &deposit->coin.coin_pub),
+                                 &cdi->coin.coin_pub),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &deposit->coin.h_age_commitment),
-      &deposit->coin.no_age_commitment),
+                                   &cdi->coin.h_age_commitment),
+      &cdi->coin.no_age_commitment),
     GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &deposit->csig),
+                                 &cdi->csig),
     GNUNET_JSON_spec_end ()
   };
   enum GNUNET_GenericReturnValue res;
@@ -386,7 +340,7 @@ parse_coin (struct MHD_Connection *connection,
     struct TEH_DenominationKey *dk;
     MHD_RESULT mret;
 
-    dk = TEH_keys_denomination_by_hash (&deposit->coin.denom_pub_hash,
+    dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
                                         connection,
                                         &mret);
     if (NULL == dk)
@@ -395,7 +349,7 @@ parse_coin (struct MHD_Connection *connection,
       return mret;
     }
     if (0 > TALER_amount_cmp (&dk->meta.value,
-                              &deposit->amount_with_fee))
+                              &cdi->amount_with_fee))
     {
       GNUNET_break_op (0);
       GNUNET_JSON_parse_free (spec);
@@ -414,7 +368,7 @@ parse_coin (struct MHD_Connection *connection,
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
                 connection,
-                &deposit->coin.denom_pub_hash,
+                &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
                 "DEPOSIT"))
         ? GNUNET_NO
@@ -427,7 +381,7 @@ parse_coin (struct MHD_Connection *connection,
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
                 connection,
-                &deposit->coin.denom_pub_hash,
+                &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
                 "DEPOSIT"))
         ? GNUNET_NO
@@ -440,13 +394,13 @@ parse_coin (struct MHD_Connection *connection,
       return (MHD_YES ==
               TEH_RESPONSE_reply_expired_denom_pub_hash (
                 connection,
-                &deposit->coin.denom_pub_hash,
+                &cdi->coin.denom_pub_hash,
                 TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
                 "DEPOSIT"))
         ? GNUNET_NO
         : GNUNET_SYSERR;
     }
-    if (dk->denom_pub.cipher != deposit->coin.denom_sig.cipher)
+    if (dk->denom_pub.cipher != cdi->coin.denom_sig.cipher)
     {
       /* denomination cipher and denomination signature cipher not the same */
       GNUNET_JSON_parse_free (spec);
@@ -459,7 +413,7 @@ parse_coin (struct MHD_Connection *connection,
         : GNUNET_SYSERR;
     }
 
-    deposit->deposit_fee = dk->meta.fees.deposit;
+    *deposit_fee = dk->meta.fees.deposit;
     /* check coin signature */
     switch (dk->denom_pub.cipher)
     {
@@ -473,7 +427,7 @@ parse_coin (struct MHD_Connection *connection,
       break;
     }
     if (GNUNET_YES !=
-        TALER_test_coin_valid (&deposit->coin,
+        TALER_test_coin_valid (&cdi->coin,
                                &dk->denom_pub))
     {
       TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
@@ -487,8 +441,8 @@ parse_coin (struct MHD_Connection *connection,
         : GNUNET_SYSERR;
     }
   }
-  if (0 < TALER_amount_cmp (&deposit->deposit_fee,
-                            &deposit->amount_with_fee))
+  if (0 < TALER_amount_cmp (deposit_fee,
+                            &cdi->amount_with_fee))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -504,18 +458,18 @@ parse_coin (struct MHD_Connection *connection,
   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
   if (GNUNET_OK !=
       TALER_wallet_deposit_verify (
-        &deposit->amount_with_fee,
-        &deposit->deposit_fee,
+        &cdi->amount_with_fee,
+        deposit_fee,
         &dc->h_wire,
-        &dc->h_contract_terms,
-        &deposit->coin.h_age_commitment,
-        dc->has_policy ? &dc->h_policy : NULL,
-        &deposit->coin.denom_pub_hash,
-        dc->timestamp,
-        &dc->merchant_pub,
-        dc->refund_deadline,
-        &deposit->coin.coin_pub,
-        &deposit->csig))
+        &bd->h_contract_terms,
+        &cdi->coin.h_age_commitment,
+        NULL != dc->policy_json ? &dc->h_policy : NULL,
+        &cdi->coin.denom_pub_hash,
+        bd->wallet_timestamp,
+        &bd->merchant_pub,
+        bd->refund_deadline,
+        &cdi->coin.coin_pub,
+        &cdi->csig))
   {
     TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
     GNUNET_JSON_parse_free (spec);
@@ -523,17 +477,10 @@ parse_coin (struct MHD_Connection *connection,
             TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_FORBIDDEN,
                                         
TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
-                                        TALER_B2S (&deposit->coin.coin_pub)))
+                                        TALER_B2S (&cdi->coin.coin_pub)))
       ? GNUNET_NO
       : GNUNET_SYSERR;
   }
-  deposit->merchant_pub = dc->merchant_pub;
-  deposit->h_contract_terms = dc->h_contract_terms;
-  deposit->wire_salt = dc->wire_salt;
-  deposit->receiver_wire_account = (char *) dc->payto_uri;
-  deposit->timestamp = dc->timestamp;
-  deposit->refund_deadline = dc->refund_deadline;
-  deposit->wire_deadline = dc->wire_deadline;
   return GNUNET_OK;
 }
 
@@ -544,41 +491,38 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                            const char *const args[])
 {
   struct MHD_Connection *connection = rc->connection;
-  struct BatchDepositContext dc;
+  struct BatchDepositContext dc = { 0 };
+  struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
   const json_t *coins;
   bool no_refund_deadline = true;
-  bool no_policy_json = true;
   struct GNUNET_JSON_Specification spec[] = {
     GNUNET_JSON_spec_string ("merchant_payto_uri",
-                             &dc.payto_uri),
+                             &bd->receiver_wire_account),
     GNUNET_JSON_spec_fixed_auto ("wire_salt",
-                                 &dc.wire_salt),
+                                 &bd->wire_salt),
     GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &dc.merchant_pub),
+                                 &bd->merchant_pub),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                 &dc.h_contract_terms),
+                                 &bd->h_contract_terms),
     GNUNET_JSON_spec_array_const ("coins",
                                   &coins),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_json ("policy",
                              &dc.policy_json),
-      &no_policy_json),
+      NULL),
     GNUNET_JSON_spec_timestamp ("timestamp",
-                                &dc.timestamp),
+                                &bd->wallet_timestamp),
     GNUNET_JSON_spec_mark_optional (
       GNUNET_JSON_spec_timestamp ("refund_deadline",
-                                  &dc.refund_deadline),
+                                  &bd->refund_deadline),
       &no_refund_deadline),
     GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
-                                &dc.wire_deadline),
+                                &bd->wire_deadline),
     GNUNET_JSON_spec_end ()
   };
   enum GNUNET_GenericReturnValue res;
 
   (void) args;
-  memset (&dc,
-          0,
-          sizeof (dc));
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
@@ -593,13 +537,11 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
     return MHD_YES;   /* failure */
   }
 
-  dc.has_policy = ! no_policy_json;
-
   /* validate merchant's wire details (as far as we can) */
   {
     char *emsg;
 
-    emsg = TALER_payto_validate (dc.payto_uri);
+    emsg = TALER_payto_validate (bd->receiver_wire_account);
     if (NULL != emsg)
     {
       MHD_RESULT ret;
@@ -614,9 +556,9 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
       return ret;
     }
   }
-  if (GNUNET_TIME_timestamp_cmp (dc.refund_deadline,
+  if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
                                  >,
-                                 dc.wire_deadline))
+                                 bd->wire_deadline))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -625,7 +567,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                                        
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
                                        NULL);
   }
-  if (GNUNET_TIME_absolute_is_never (dc.wire_deadline.abs_time))
+  if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -634,14 +576,14 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                                        
TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
                                        NULL);
   }
-  TALER_payto_hash (dc.payto_uri,
-                    &dc.h_payto);
-  TALER_merchant_wire_signature_hash (dc.payto_uri,
-                                      &dc.wire_salt,
+  TALER_payto_hash (bd->receiver_wire_account,
+                    &bd->wire_target_h_payto);
+  TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
+                                      &bd->wire_salt,
                                       &dc.h_wire);
 
   /* handle policy, if present */
-  if (dc.has_policy)
+  if (NULL != dc.policy_json)
   {
     const char *error_hint = NULL;
 
@@ -659,8 +601,8 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                                &dc.h_policy);
   }
 
-  dc.num_coins = json_array_size (coins);
-  if (0 == dc.num_coins)
+  bd->num_cdis = json_array_size (coins);
+  if (0 == bd->num_cdis)
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -669,7 +611,7 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                        "coins");
   }
-  if (TALER_MAX_FRESH_COINS < dc.num_coins)
+  if (TALER_MAX_FRESH_COINS < bd->num_cdis)
   {
     GNUNET_break_op (0);
     GNUNET_JSON_parse_free (spec);
@@ -678,89 +620,95 @@ TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                        "coins");
   }
-  dc.deposits = GNUNET_new_array (dc.num_coins,
-                                  struct TALER_EXCHANGEDB_Deposit);
-  for (unsigned int i = 0; i<dc.num_coins; i++)
+
   {
-    do {
-      res = parse_coin (connection,
-                        json_array_get (coins, i),
-                        &dc,
-                        &dc.deposits[i]);
-      if (GNUNET_OK != res)
-        break;
+    struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
+      GNUNET_NZL (bd->num_cdis)];
+    struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
 
-      /* If applicable, accumulate all contributions into the policy_details */
-      if (dc.has_policy)
+    bd->cdis = cdis;
+    dc.deposit_fees = deposit_fees;
+    for (unsigned int i = 0; i<bd->num_cdis; i++)
+    {
+      do {
+        res = parse_coin (connection,
+                          &dc,
+                          json_array_get (coins,
+                                          i),
+                          &cdis[i],
+                          &deposit_fees[i]);
+        if (GNUNET_OK != res)
+          break;
+
+        /* If applicable, accumulate all contributions into the policy_details 
*/
+        if (NULL != dc.policy_json)
+        {
+          /* FIXME: how do deposit-fee and policy-fee interact? */
+          struct TALER_Amount amount_without_fee;
+
+          // FIXME-Oec: wrong enum type for 'res' here!
+          res = TALER_amount_subtract (&amount_without_fee,
+                                       &cdis[i].amount_with_fee,
+                                       &deposit_fees[i]);
+          // FIXME-Oec: rval of res not checked
+          res = TALER_amount_add (
+            &dc.policy_details.accumulated_total,
+            &dc.policy_details.accumulated_total,
+            &amount_without_fee);
+        }
+      } while(0);
+
+      if (GNUNET_OK != res)
       {
-        /* FIXME: how do deposit-fee and policy-fee interact? */
-        struct TALER_Amount amount_without_fee;
-
-        res = TALER_amount_subtract (&amount_without_fee,
-                                     &dc.deposits[i].amount_with_fee,
-                                     &dc.deposits[i].deposit_fee
-                                     );
-        res = TALER_amount_add (
-          &dc.policy_details.accumulated_total,
-          &dc.policy_details.accumulated_total,
-          &amount_without_fee);
+        for (unsigned int j = 0; j<i; j++)
+          TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+        GNUNET_JSON_parse_free (spec);
+        return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
       }
-    } while(0);
+    }
 
-    if (GNUNET_OK != res)
+    dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
+    if (GNUNET_SYSERR ==
+        TEH_plugin->preflight (TEH_plugin->cls))
     {
-      for (unsigned int j = 0; j<i; j++)
-        TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
-      GNUNET_free (dc.deposits);
+      GNUNET_break (0);
       GNUNET_JSON_parse_free (spec);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_START_FAILED,
+                                         "preflight failure");
     }
-  }
 
-  dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
-  if (GNUNET_SYSERR ==
-      TEH_plugin->preflight (TEH_plugin->cls))
-  {
-    GNUNET_break (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_START_FAILED,
-                                       "preflight failure");
-  }
-
-  /* execute transaction */
-  {
-    MHD_RESULT mhd_ret;
-
-    if (GNUNET_OK !=
-        TEH_DB_run_transaction (connection,
-                                "execute batch deposit",
-                                TEH_MT_REQUEST_BATCH_DEPOSIT,
-                                &mhd_ret,
-                                &batch_deposit_transaction,
-                                &dc))
+    /* execute transaction */
     {
-      GNUNET_JSON_parse_free (spec);
-      for (unsigned int j = 0; j<dc.num_coins; j++)
-        TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
-      GNUNET_free (dc.deposits);
-      GNUNET_JSON_parse_free (spec);
-      return mhd_ret;
+      MHD_RESULT mhd_ret;
+
+      if (GNUNET_OK !=
+          TEH_DB_run_transaction (connection,
+                                  "execute batch deposit",
+                                  TEH_MT_REQUEST_BATCH_DEPOSIT,
+                                  &mhd_ret,
+                                  &batch_deposit_transaction,
+                                  &dc))
+      {
+        for (unsigned int j = 0; j<bd->num_cdis; j++)
+          TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+        GNUNET_JSON_parse_free (spec);
+        return mhd_ret;
+      }
     }
-  }
 
-  /* generate regular response */
-  {
-    MHD_RESULT res;
+    /* generate regular response */
+    {
+      MHD_RESULT res;
 
-    res = reply_batch_deposit_success (connection,
-                                       &dc);
-    for (unsigned int j = 0; j<dc.num_coins; j++)
-      TALER_denom_sig_free (&dc.deposits[j].coin.denom_sig);
-    GNUNET_free (dc.deposits);
-    GNUNET_JSON_parse_free (spec);
-    return res;
+      res = reply_batch_deposit_success (connection,
+                                         &dc);
+      for (unsigned int j = 0; j<bd->num_cdis; j++)
+        TALER_denom_sig_free (&cdis[j].coin.denom_sig);
+      GNUNET_JSON_parse_free (spec);
+      return res;
+    }
   }
 }
 
diff --git a/src/exchange/taler-exchange-httpd_deposit.c 
b/src/exchange/taler-exchange-httpd_deposit.c
deleted file mode 100644
index 740db7c1..00000000
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014-2022 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Affero General Public License as published by the Free 
Software
-  Foundation; either version 3, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
-
-  You should have received a copy of the GNU Affero General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_deposit.c
- * @brief Handle /deposit requests; parses the POST and JSON and
- *        verifies the coin signature before handing things off
- *        to the database.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- * @author Özgür Kesim
- */
-#include "platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler_json_lib.h"
-#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_deposit.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Send confirmation of deposit success to client.  This function
- * will create a signed message affirming the given information
- * and return it to the client.  By this, the exchange affirms that
- * the coin had sufficient (residual) value for the specified
- * transaction and that it will execute the requested deposit
- * operation with the given wiring details.
- *
- * @param connection connection to the client
- * @param coin_pub public key of the coin
- * @param h_wire hash of wire details
- * @param h_policy hash of applicable policy extension
- * @param h_contract_terms hash of contract details
- * @param exchange_timestamp exchange's timestamp
- * @param refund_deadline until when this deposit be refunded
- * @param wire_deadline until when will the exchange wire the funds
- * @param merchant merchant public key
- * @param amount_without_fee fraction of coin value to deposit, without the fee
- * @return MHD result code
- */
-static MHD_RESULT
-reply_deposit_success (
-  struct MHD_Connection *connection,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-  const struct TALER_MerchantWireHashP *h_wire,
-  const struct TALER_ExtensionPolicyHashP *h_policy,
-  const struct TALER_PrivateContractHashP *h_contract_terms,
-  struct GNUNET_TIME_Timestamp exchange_timestamp,
-  struct GNUNET_TIME_Timestamp refund_deadline,
-  struct GNUNET_TIME_Timestamp wire_deadline,
-  const struct TALER_MerchantPublicKeyP *merchant,
-  const struct TALER_Amount *amount_without_fee)
-{
-  struct TALER_ExchangePublicKeyP pub;
-  struct TALER_ExchangeSignatureP sig;
-  enum TALER_ErrorCode ec;
-
-  ec = TALER_exchange_online_deposit_confirmation_sign (
-    &TEH_keys_exchange_sign_,
-    h_contract_terms,
-    h_wire,
-    h_policy,
-    exchange_timestamp,
-    wire_deadline,
-    refund_deadline,
-    amount_without_fee,
-    coin_pub,
-    merchant,
-    &pub,
-    &sig);
-  if (TALER_EC_NONE != ec)
-  {
-    return TALER_MHD_reply_with_ec (connection,
-                                    ec,
-                                    NULL);
-  }
-  return TALER_MHD_REPLY_JSON_PACK (
-    connection,
-    MHD_HTTP_OK,
-    GNUNET_JSON_pack_timestamp ("exchange_timestamp",
-                                exchange_timestamp),
-    GNUNET_JSON_pack_data_auto ("exchange_sig",
-                                &sig),
-    GNUNET_JSON_pack_data_auto ("exchange_pub",
-                                &pub));
-}
-
-
-/**
- * Closure for #deposit_transaction.
- */
-struct DepositContext
-{
-  /**
-   * Information about the deposit request.
-   */
-  const struct TALER_EXCHANGEDB_Deposit *deposit;
-
-  /**
-   * Our timestamp (when we received the request).
-   * Possibly updated by the transaction if the
-   * request is idempotent (was repeated).
-   */
-  struct GNUNET_TIME_Timestamp exchange_timestamp;
-
-  /**
-   * Hash of the payto URI.
-   */
-  struct TALER_PaytoHashP h_payto;
-
-  /**
-   * Row of of the coin in the known_coins table.
-   */
-  uint64_t known_coin_id;
-
-  /*
-   * True if @e policy_json was provided
-   */
-  bool has_policy;
-
-  /**
-   * If @e has_policy is true, the corresponding policy extension calculates
-   * these details.  These will be persisted in the policy_details table.
-   */
-  struct TALER_PolicyDetails policy_details;
-
-  /**
-   * Hash over the policy data for this deposit (remains unknown to the
-   * Exchange).  Needed for the verification of the deposit's signature
-   */
-  struct TALER_ExtensionPolicyHashP h_policy;
-
-  /**
-   * When has_policy is true, and deposit->policy_details are
-   * persisted, this contains the id of the record in the policy_details table.
-   */
-  uint64_t policy_details_serial_id;
-
-};
-
-
-/**
- * Execute database transaction for /deposit.  Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response.  IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls a `struct DepositContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-deposit_transaction (void *cls,
-                     struct MHD_Connection *connection,
-                     MHD_RESULT *mhd_ret)
-{
-  struct DepositContext *dc = cls;
-  enum GNUNET_DB_QueryStatus qs;
-  bool balance_ok;
-  bool in_conflict;
-
-  qs = TEH_make_coin_known (&dc->deposit->coin,
-                            connection,
-                            &dc->known_coin_id,
-                            mhd_ret);
-  if (qs < 0)
-    return qs;
-  /* If the deposit has a policy associated to it, persist it.  This will
-   * insert or update the record. */
-  if (dc->has_policy)
-  {
-    qs = TEH_plugin->persist_policy_details (
-      TEH_plugin->cls,
-      &dc->policy_details,
-      &dc->policy_details_serial_id,
-      &dc->policy_details.accumulated_total,
-      &dc->policy_details.fulfillment_state);
-
-    if (qs < 0)
-      return qs;
-  }
-  qs = TEH_plugin->do_deposit (
-    TEH_plugin->cls,
-    dc->deposit,
-    dc->known_coin_id,
-    &dc->h_payto,
-    (dc->has_policy)
-    ? &dc->policy_details_serial_id
-    : NULL,
-    &dc->exchange_timestamp,
-    &balance_ok,
-    &in_conflict);
-  if (qs < 0)
-  {
-    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      return qs;
-    TALER_LOG_WARNING ("Failed to store /deposit information in database\n");
-    *mhd_ret = TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           TALER_EC_GENERIC_DB_STORE_FAILED,
-                                           "deposit");
-    return qs;
-  }
-  if (in_conflict)
-  {
-    /* FIXME #7267: conflicting contract != insufficient funds */
-    *mhd_ret
-      = TEH_RESPONSE_reply_coin_insufficient_funds (
-          connection,
-          TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
-          &dc->deposit->coin.denom_pub_hash,
-          &dc->deposit->coin.coin_pub);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  if (! balance_ok)
-  {
-    *mhd_ret
-      = TEH_RESPONSE_reply_coin_insufficient_funds (
-          connection,
-          TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
-          &dc->deposit->coin.denom_pub_hash,
-          &dc->deposit->coin.coin_pub);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-  TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
-  return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
-                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                     const json_t *root)
-{
-  struct DepositContext dc;
-  struct TALER_EXCHANGEDB_Deposit deposit;
-  const char *payto_uri;
-  struct TALER_ExtensionPolicyHashP *ph_policy = NULL;
-  bool no_policy_json;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("merchant_payto_uri",
-                             &payto_uri),
-    GNUNET_JSON_spec_fixed_auto ("wire_salt",
-                                 &deposit.wire_salt),
-    TALER_JSON_spec_amount ("contribution",
-                            TEH_currency,
-                            &deposit.amount_with_fee),
-    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
-                                 &deposit.coin.denom_pub_hash),
-    TALER_JSON_spec_denom_sig ("ub_sig",
-                               &deposit.coin.denom_sig),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &deposit.merchant_pub),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
-                                 &deposit.h_contract_terms),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                   &deposit.coin.h_age_commitment),
-      &deposit.coin.no_age_commitment),
-    GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                 &deposit.csig),
-    GNUNET_JSON_spec_timestamp ("timestamp",
-                                &deposit.timestamp),
-    /* TODO: refund_deadline and merchant_pub will move into the
-     * extension policy_merchant_refunds */
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_timestamp ("refund_deadline",
-                                  &deposit.refund_deadline),
-      NULL),
-    GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
-                                &deposit.wire_deadline),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_json ("policy",
-                             &dc.policy_details.policy_json),
-      &no_policy_json),
-    GNUNET_JSON_spec_end ()
-  };
-  struct TALER_MerchantWireHashP h_wire;
-
-  memset (&deposit,
-          0,
-          sizeof (deposit));
-  deposit.coin.coin_pub = *coin_pub;
-  {
-    enum GNUNET_GenericReturnValue res;
-
-    res = TALER_MHD_parse_json_data (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 */
-    }
-  }
-
-  dc.has_policy = ! no_policy_json;
-
-  /* validate merchant's wire details (as far as we can) */
-  {
-    char *emsg;
-
-    emsg = TALER_payto_validate (payto_uri);
-    if (NULL != emsg)
-    {
-      MHD_RESULT ret;
-
-      GNUNET_break_op (0);
-      GNUNET_JSON_parse_free (spec);
-      ret = TALER_MHD_reply_with_error (connection,
-                                        MHD_HTTP_BAD_REQUEST,
-                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                        emsg);
-      GNUNET_free (emsg);
-      return ret;
-    }
-  }
-  if (GNUNET_TIME_timestamp_cmp (deposit.refund_deadline,
-                                 >,
-                                 deposit.wire_deadline))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
-                                       NULL);
-  }
-  if (GNUNET_TIME_absolute_is_never (deposit.wire_deadline.abs_time))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
-                                       NULL);
-  }
-  deposit.receiver_wire_account = (char *) payto_uri;
-  TALER_payto_hash (payto_uri,
-                    &dc.h_payto);
-  TALER_merchant_wire_signature_hash (payto_uri,
-                                      &deposit.wire_salt,
-                                      &h_wire);
-  dc.deposit = &deposit;
-
-  /* new deposit */
-  dc.exchange_timestamp = GNUNET_TIME_timestamp_get ();
-  /* check denomination exists and is valid */
-  {
-    struct TEH_DenominationKey *dk;
-    MHD_RESULT mret;
-
-    dk = TEH_keys_denomination_by_hash (&deposit.coin.denom_pub_hash,
-                                        connection,
-                                        &mret);
-    if (NULL == dk)
-    {
-      GNUNET_JSON_parse_free (spec);
-      return mret;
-    }
-    if (0 > TALER_amount_cmp (&dk->meta.value,
-                              &deposit.amount_with_fee))
-    {
-      GNUNET_break_op (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
-                                         NULL);
-    }
-    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
-    {
-      /* This denomination is past the expiration time for deposits */
-      GNUNET_JSON_parse_free (spec);
-      return TEH_RESPONSE_reply_expired_denom_pub_hash (
-        connection,
-        &deposit.coin.denom_pub_hash,
-        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
-        "DEPOSIT");
-    }
-    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
-    {
-      /* This denomination is not yet valid */
-      GNUNET_JSON_parse_free (spec);
-      return TEH_RESPONSE_reply_expired_denom_pub_hash (
-        connection,
-        &deposit.coin.denom_pub_hash,
-        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
-        "DEPOSIT");
-    }
-    if (dk->recoup_possible)
-    {
-      /* This denomination has been revoked */
-      GNUNET_JSON_parse_free (spec);
-      return TEH_RESPONSE_reply_expired_denom_pub_hash (
-        connection,
-        &deposit.coin.denom_pub_hash,
-        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
-        "DEPOSIT");
-    }
-    if (dk->denom_pub.cipher != deposit.coin.denom_sig.cipher)
-    {
-      /* denomination cipher and denomination signature cipher not the same */
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
-                                         NULL);
-    }
-
-    deposit.deposit_fee = dk->meta.fees.deposit;
-    /* check coin signature */
-    switch (dk->denom_pub.cipher)
-    {
-    case TALER_DENOMINATION_RSA:
-      TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
-      break;
-    case TALER_DENOMINATION_CS:
-      TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
-      break;
-    default:
-      break;
-    }
-    if (GNUNET_YES !=
-        TALER_test_coin_valid (&deposit.coin,
-                               &dk->denom_pub))
-    {
-      TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_FORBIDDEN,
-                                         
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
-                                         NULL);
-    }
-  }
-  if (0 < TALER_amount_cmp (&deposit.deposit_fee,
-                            &deposit.amount_with_fee))
-  {
-    GNUNET_break_op (0);
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
-                                       NULL);
-  }
-
-  /* Check policy input and create policy details */
-  if (dc.has_policy)
-  {
-    const char *error_hint = NULL;
-
-    if (GNUNET_OK !=
-        TALER_extensions_create_policy_details (
-          dc.policy_details.policy_json,
-          &dc.policy_details,
-          &error_hint))
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_BAD_REQUEST,
-                                         
TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
-                                         error_hint);
-
-    TALER_deposit_policy_hash (dc.policy_details.policy_json,
-                               &dc.h_policy);
-    ph_policy = &dc.h_policy;
-  }
-
-  TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
-  if (GNUNET_OK !=
-      TALER_wallet_deposit_verify (&deposit.amount_with_fee,
-                                   &deposit.deposit_fee,
-                                   &h_wire,
-                                   &deposit.h_contract_terms,
-                                   &deposit.coin.h_age_commitment,
-                                   ph_policy,
-                                   &deposit.coin.denom_pub_hash,
-                                   deposit.timestamp,
-                                   &deposit.merchant_pub,
-                                   deposit.refund_deadline,
-                                   &deposit.coin.coin_pub,
-                                   &deposit.csig))
-  {
-    TALER_LOG_WARNING ("Invalid signature on /deposit request\n");
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_FORBIDDEN,
-                                       
TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
-                                       NULL);
-  }
-
-  if (GNUNET_SYSERR ==
-      TEH_plugin->preflight (TEH_plugin->cls))
-  {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_START_FAILED,
-                                       "preflight failure");
-  }
-
-  /* execute transaction */
-  {
-    MHD_RESULT mhd_ret;
-
-    if (GNUNET_OK !=
-        TEH_DB_run_transaction (connection,
-                                "execute deposit",
-                                TEH_MT_REQUEST_DEPOSIT,
-                                &mhd_ret,
-                                &deposit_transaction,
-                                &dc))
-    {
-      GNUNET_JSON_parse_free (spec);
-      return mhd_ret;
-    }
-  }
-
-  /* generate regular response */
-  {
-    struct TALER_Amount amount_without_fee;
-    MHD_RESULT res;
-
-    GNUNET_assert (0 <=
-                   TALER_amount_subtract (&amount_without_fee,
-                                          &deposit.amount_with_fee,
-                                          &deposit.deposit_fee));
-    res = reply_deposit_success (connection,
-                                 &deposit.coin.coin_pub,
-                                 &h_wire,
-                                 ph_policy,
-                                 &deposit.h_contract_terms,
-                                 dc.exchange_timestamp,
-                                 deposit.refund_deadline,
-                                 deposit.wire_deadline,
-                                 &deposit.merchant_pub,
-                                 &amount_without_fee);
-    GNUNET_JSON_parse_free (spec);
-    return res;
-  }
-}
-
-
-/* end of taler-exchange-httpd_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_deposit.h 
b/src/exchange/taler-exchange-httpd_deposit.h
deleted file mode 100644
index a4d598a6..00000000
--- a/src/exchange/taler-exchange-httpd_deposit.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-  This file is part of TALER
-  Copyright (C) 2014 Taler Systems SA
-
-  TALER is free software; you can redistribute it and/or modify it under the
-  terms of the GNU Affero General Public License as published by the Free 
Software
-  Foundation; either version 3, or (at your option) any later version.
-
-  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
-
-  You should have received a copy of the GNU Affero General Public License 
along with
-  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_deposit.h
- * @brief Handle /deposit requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_DEPOSIT_H
-#define TALER_EXCHANGE_HTTPD_DEPOSIT_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/coins/$COIN_PUB/deposit" request.  Parses the JSON, and, if
- * successful, passes the JSON data to #deposit_transaction() to
- * further check the details of the operation specified.  If everything checks
- * out, this will ultimately lead to the "/deposit" being executed, or
- * rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
-  */
-MHD_RESULT
-TEH_handler_deposit (struct MHD_Connection *connection,
-                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                     const json_t *root);
-
-
-#endif
diff --git a/src/exchangedb/0002-aggregation_tracking.sql 
b/src/exchangedb/0002-aggregation_tracking.sql
index 7df495a4..d0796024 100644
--- a/src/exchangedb/0002-aggregation_tracking.sql
+++ b/src/exchangedb/0002-aggregation_tracking.sql
@@ -26,11 +26,11 @@ BEGIN
   PERFORM create_partitioned_table(
     'CREATE TABLE %I'
       '(aggregation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
-      ',deposit_serial_id INT8 PRIMARY KEY'
+      ',batch_deposit_serial_id INT8 PRIMARY KEY'
       ',wtid_raw BYTEA NOT NULL'
     ') %s ;'
     ,table_name
-    ,'PARTITION BY HASH (deposit_serial_id)'
+    ,'PARTITION BY HASH (batch_deposit_serial_id)'
     ,partition_suffix
   );
   PERFORM comment_partitioned_table(
@@ -86,8 +86,9 @@ BEGIN
   EXECUTE FORMAT (
     'ALTER TABLE ' || table_name ||
     ' ADD CONSTRAINT ' || table_name || '_foreign_deposit'
-    ' FOREIGN KEY (deposit_serial_id) '
-    ' REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE' -- FIXME 
change to coin_pub + deposit_serial_id for more efficient deposit???
+    ' FOREIGN KEY (batch_deposit_serial_id)'
+    ' REFERENCES batch_deposits (batch_deposit_serial_id)'
+    ' ON DELETE CASCADE'
   );
 END
 $$;
diff --git a/src/exchangedb/0002-batch_deposits.sql 
b/src/exchangedb/0002-batch_deposits.sql
new file mode 100644
index 00000000..af0764aa
--- /dev/null
+++ b/src/exchangedb/0002-batch_deposits.sql
@@ -0,0 +1,154 @@
+--
+-- 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/>
+--
+
+CREATE FUNCTION create_table_batch_deposits(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(batch_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+      ',shard INT8 NOT NULL'
+      ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
+      ',wallet_timestamp INT8 NOT NULL'
+      ',exchange_timestamp INT8 NOT NULL'
+      ',refund_deadline INT8 NOT NULL'
+      ',wire_deadline INT8 NOT NULL'
+      ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+      ',wallet_data_hash BYTEA CHECK (LENGTH(wallet_data_hash)=64) DEFAULT 
NULL'
+      ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)'
+      ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+      ',policy_details_serial_id INT8'
+      ',policy_blocked BOOLEAN NOT NULL DEFAULT FALSE'
+      ',done BOOLEAN NOT NULL DEFAULT FALSE'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (shard)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+    'Information about the contracts for which we have received (batch) 
deposits.'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Used for load sharding in the materialized indices. Should be set based 
on merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.'
+    ,'shard'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Unsalted hash of the target bank account; also used to lookup the KYC 
status'
+    ,'wire_target_h_payto'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'hash over data provided by the wallet upon payment to select a more 
specific contract'
+    ,'wallet_data_hash'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Salt used when hashing the payto://-URI to get the h_wire that was used 
by the coin deposit signatures; not used to calculate wire_target_h_payto (as 
that one is unsalted)'
+    ,'wire_salt'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Set to TRUE once we have included this (batch) deposit (and all 
associated coins) in some aggregate wire transfer to the merchant'
+    ,'done'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'True if the aggregation of the (batch) deposit is currently blocked by 
some policy extension mechanism. Used to filter out deposits that must not be 
processed by the canonical deposit logic.'
+    ,'policy_blocked'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'References policy extensions table, NULL if extensions are not used'
+    ,'policy_details_serial_id'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_batch_deposits(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_batch_deposit_serial_id_pkey'
+    ' PRIMARY KEY (batch_deposit_serial_id) '
+    ',ADD CONSTRAINT ' || table_name || '_merchant_pub_h_contract_terms'
+    ' UNIQUE (shard, merchant_pub, h_contract_terms)'
+    ',ADD CONSTRAINT ' || table_name || '_foreign_policy_details'
+    ' FOREIGN KEY (policy_details_serial_id) '
+    ' REFERENCES policy_details (policy_details_serial_id) ON DELETE RESTRICT'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_ready '
+    'ON ' || table_name || ' '
+    '(shard ASC'
+    ',wire_deadline ASC'
+    ') WHERE NOT (done OR policy_blocked);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_for_matching '
+    'ON ' || table_name || ' '
+    '(shard ASC'
+    ',refund_deadline ASC'
+    ',wire_target_h_payto'
+    ') WHERE NOT (done OR policy_blocked);'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('batch_deposits'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('batch_deposits'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE)
+    ;
diff --git a/src/exchangedb/0002-refunds.sql 
b/src/exchangedb/0002-coin_deposits.sql
similarity index 50%
copy from src/exchangedb/0002-refunds.sql
copy to src/exchangedb/0002-coin_deposits.sql
index e82dad93..c3eef6e5 100644
--- a/src/exchangedb/0002-refunds.sql
+++ b/src/exchangedb/0002-coin_deposits.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 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 General Public License as published by the Free Software
@@ -14,22 +14,21 @@
 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 --
 
-CREATE FUNCTION create_table_refunds(
+CREATE FUNCTION create_table_coin_deposits(
   IN partition_suffix TEXT DEFAULT NULL
 )
 RETURNS VOID
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_deposits';
 BEGIN
   PERFORM create_partitioned_table(
     'CREATE TABLE %I'
-      '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      '(coin_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+      ',batch_deposit_serial_id INT8 NOT NULL'
       ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
-      ',deposit_serial_id INT8 NOT NULL'
-      ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
-      ',rtransaction_id INT8 NOT NULL'
+      ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
       ',amount_with_fee taler_amount NOT NULL'
     ') %s ;'
     ,table_name
@@ -37,19 +36,13 @@ BEGIN
     ,partition_suffix
   );
   PERFORM comment_partitioned_table(
-     'Data on coins that were refunded. Technically, refunds always apply 
against specific deposit operations involving a coin. The combination of 
coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, 
and we usually select by coin_pub so that one goes first.'
+    'Coins which have been deposited with the respective per-coin signatures.'
     ,table_name
     ,partition_suffix
   );
   PERFORM comment_partitioned_column(
-     'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. 
Multiple deposits may match a refund, this only identifies one of them.'
-    ,'deposit_serial_id'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'used by the merchant to make refunds unique in case the same coin for 
the same deposit gets a subsequent (higher) refund'
-    ,'rtransaction_id'
+     'Link to information about the batch deposit this coin was used for'
+    ,'batch_deposit_serial_id'
     ,table_name
     ,partition_suffix
   );
@@ -57,51 +50,83 @@ END
 $$;
 
 
-CREATE FUNCTION constrain_table_refunds (
-  IN partition_suffix TEXT DEFAULT NULL
+CREATE FUNCTION constrain_table_coin_deposits(
+  IN partition_suffix TEXT
 )
 RETURNS void
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_deposits';
 BEGIN
   table_name = concat_ws('_', table_name, partition_suffix);
   EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_by_coin_pub_index '
-    'ON ' || table_name || ' '
-    '(coin_pub);'
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_coin_deposit_serial_id_pkey'
+    ' PRIMARY KEY (coin_deposit_serial_id) '
+    ',ADD CONSTRAINT ' || table_name || '_unique_coin_sig'
+    ' UNIQUE (coin_pub, coin_sig)'
   );
   EXECUTE FORMAT (
-    'ALTER TABLE ' || table_name ||
-    ' ADD CONSTRAINT ' || table_name || '_refund_serial_id_key'
-    ' UNIQUE (refund_serial_id) '
-    ',ADD PRIMARY KEY (deposit_serial_id, rtransaction_id) '
+    'CREATE INDEX ' || table_name || '_by_batch '
+    'ON ' || table_name || ' '
+    '(batch_deposit_serial_id);'
   );
 END
 $$;
 
 
-CREATE FUNCTION foreign_table_refunds ()
+CREATE FUNCTION foreign_table_coin_deposits()
 RETURNS void
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_deposits';
 BEGIN
   EXECUTE FORMAT (
     'ALTER TABLE ' || table_name ||
     ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
     ' FOREIGN KEY (coin_pub) '
     ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
-    ',ADD CONSTRAINT ' || table_name || '_foreign_deposit'
-    ' FOREIGN KEY (deposit_serial_id) '
-    ' REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE'
+    ',ADD CONSTRAINT ' || table_name || '_foreign_batch_deposits_id'
+    ' FOREIGN KEY (batch_deposit_serial_id) '
+    ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
   );
 END
 $$;
 
 
+CREATE OR REPLACE FUNCTION coin_deposits_insert_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  INSERT INTO exchange.coin_history
+    (coin_pub
+    ,table_name
+    ,serial_id)
+ VALUES
+     (NEW.coin_pub
+    ,'coin_deposits'
+    ,NEW.coin_deposit_serial_id);
+  RETURN NEW;
+END $$;
+COMMENT ON FUNCTION coin_deposits_insert_trigger()
+  IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_coin_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  CREATE TRIGGER coin_deposits_on_insert
+    AFTER INSERT
+     ON coin_deposits
+     FOR EACH ROW EXECUTE FUNCTION coin_deposits_insert_trigger();
+END $$;
+
+
 INSERT INTO exchange_tables
     (name
     ,version
@@ -109,18 +134,24 @@ INSERT INTO exchange_tables
     ,partitioned
     ,by_range)
   VALUES
-    ('refunds'
+    ('coin_deposits'
     ,'exchange-0002'
     ,'create'
     ,TRUE
     ,FALSE),
-    ('refunds'
+    ('coin_deposits'
     ,'exchange-0002'
     ,'constrain'
     ,TRUE
     ,FALSE),
-    ('refunds'
+    ('coin_deposits'
     ,'exchange-0002'
     ,'foreign'
     ,TRUE
-    ,FALSE);
+    ,FALSE),
+    ('coin_deposits'
+    ,'exchange-0002'
+    ,'master'
+    ,TRUE
+    ,FALSE)
+    ;
diff --git a/src/exchangedb/0002-refunds.sql 
b/src/exchangedb/0002-coin_history.sql
similarity index 54%
copy from src/exchangedb/0002-refunds.sql
copy to src/exchangedb/0002-coin_history.sql
index e82dad93..9b5efdcb 100644
--- a/src/exchangedb/0002-refunds.sql
+++ b/src/exchangedb/0002-coin_history.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 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 General Public License as published by the Free Software
@@ -14,42 +14,52 @@
 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 --
 
-CREATE FUNCTION create_table_refunds(
+CREATE FUNCTION create_table_coin_history (
   IN partition_suffix TEXT DEFAULT NULL
 )
 RETURNS VOID
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_history';
 BEGIN
   PERFORM create_partitioned_table(
     'CREATE TABLE %I'
-      '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      '(coin_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
       ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
-      ',deposit_serial_id INT8 NOT NULL'
-      ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
-      ',rtransaction_id INT8 NOT NULL'
-      ',amount_with_fee taler_amount NOT NULL'
+      ',table_name TEXT NOT NULL'
+      ',serial_id INT8 NOT NULL'
     ') %s ;'
     ,table_name
     ,'PARTITION BY HASH (coin_pub)'
     ,partition_suffix
   );
   PERFORM comment_partitioned_table(
-     'Data on coins that were refunded. Technically, refunds always apply 
against specific deposit operations involving a coin. The combination of 
coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, 
and we usually select by coin_pub so that one goes first.'
+    'Links to tables with entries that affected the transaction history of a 
coin.'
     ,table_name
     ,partition_suffix
   );
   PERFORM comment_partitioned_column(
-     'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. 
Multiple deposits may match a refund, this only identifies one of them.'
-    ,'deposit_serial_id'
+     'For which coin is this a history entry'
+    ,'coin_pub'
     ,table_name
     ,partition_suffix
   );
   PERFORM comment_partitioned_column(
-     'used by the merchant to make refunds unique in case the same coin for 
the same deposit gets a subsequent (higher) refund'
-    ,'rtransaction_id'
+     'In which table is the history entry'
+    ,'table_name'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Which is the generated serial ID of the entry in the table'
+    ,'serial_id'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Monotonic counter, used to generate Etags for caching'
+    ,'coin_history_serial_id'
     ,table_name
     ,partition_suffix
   );
@@ -57,51 +67,52 @@ END
 $$;
 
 
-CREATE FUNCTION constrain_table_refunds (
-  IN partition_suffix TEXT DEFAULT NULL
+CREATE FUNCTION constrain_table_coin_history(
+  IN partition_suffix TEXT
 )
 RETURNS void
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_history';
 BEGIN
   table_name = concat_ws('_', table_name, partition_suffix);
   EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_by_coin_pub_index '
-    'ON ' || table_name || ' '
-    '(coin_pub);'
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_coin_history_serial_id_pkey'
+    ' PRIMARY KEY (coin_history_serial_id) '
+    ',ADD CONSTRAINT ' || table_name || '_coin_entry_key'
+    ' UNIQUE (coin_pub, table_name, serial_id)'
   );
   EXECUTE FORMAT (
-    'ALTER TABLE ' || table_name ||
-    ' ADD CONSTRAINT ' || table_name || '_refund_serial_id_key'
-    ' UNIQUE (refund_serial_id) '
-    ',ADD PRIMARY KEY (deposit_serial_id, rtransaction_id) '
+    'CREATE INDEX ' || table_name || '_coin_by_time'
+    ' ON ' || table_name || ' '
+    '(coin_pub'
+    ',coin_history_serial_id DESC'
+    ');'
   );
 END
 $$;
 
 
-CREATE FUNCTION foreign_table_refunds ()
+CREATE FUNCTION foreign_table_coin_history()
 RETURNS void
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  table_name TEXT DEFAULT 'refunds';
+  table_name TEXT DEFAULT 'coin_history';
 BEGIN
   EXECUTE FORMAT (
     'ALTER TABLE ' || table_name ||
     ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
     ' FOREIGN KEY (coin_pub) '
     ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
-    ',ADD CONSTRAINT ' || table_name || '_foreign_deposit'
-    ' FOREIGN KEY (deposit_serial_id) '
-    ' REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE'
   );
 END
 $$;
 
 
+
 INSERT INTO exchange_tables
     (name
     ,version
@@ -109,18 +120,19 @@ INSERT INTO exchange_tables
     ,partitioned
     ,by_range)
   VALUES
-    ('refunds'
+    ('coin_history'
     ,'exchange-0002'
     ,'create'
     ,TRUE
     ,FALSE),
-    ('refunds'
+    ('coin_history'
     ,'exchange-0002'
     ,'constrain'
     ,TRUE
     ,FALSE),
-    ('refunds'
+    ('coin_history'
     ,'exchange-0002'
     ,'foreign'
     ,TRUE
-    ,FALSE);
+    ,FALSE)
+    ;
diff --git a/src/exchangedb/0002-deposits.sql b/src/exchangedb/0002-deposits.sql
deleted file mode 100644
index f15b869c..00000000
--- a/src/exchangedb/0002-deposits.sql
+++ /dev/null
@@ -1,426 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2023 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
--- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
---
-
-CREATE FUNCTION create_table_deposits(
-  IN partition_suffix TEXT DEFAULT NULL
-)
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits';
-BEGIN
-  PERFORM create_partitioned_table(
-    'CREATE TABLE %I'
-      '(deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
-      ',shard INT8 NOT NULL'
-      ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
-      ',known_coin_id INT8 NOT NULL' -- FIXME: column needed!?
-      ',amount_with_fee taler_amount NOT NULL'
-      ',wallet_timestamp INT8 NOT NULL'
-      ',exchange_timestamp INT8 NOT NULL'
-      ',refund_deadline INT8 NOT NULL'
-      ',wire_deadline INT8 NOT NULL'
-      ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
-      ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
-      ',wallet_data_hash BYTEA CHECK (LENGTH(wallet_data_hash)=64) DEFAULT 
NULL'
-      ',subcontract_id INT4 NOT NULL DEFAULT 0'
-      ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
-      ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)'
-      ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
-      ',done BOOLEAN NOT NULL DEFAULT FALSE'
-      ',policy_blocked BOOLEAN NOT NULL DEFAULT FALSE'
-      ',policy_details_serial_id INT8'
-    ') %s ;'
-    ,table_name
-    ,'PARTITION BY HASH (coin_pub)'
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_table(
-    'Deposits we have received and for which we need to make (aggregate) wire 
transfers (and manage refunds).'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'Used for load sharding in the materialized indices. Should be set based 
on merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.'
-    ,'shard'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'Used for garbage collection'
-    ,'known_coin_id'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'Identifies the target bank account and KYC status'
-    ,'wire_target_h_payto'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'hash over data provided by the wallet upon payment to select a more 
specific contract'
-    ,'wallet_data_hash'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'Salt used when hashing the payto://-URI to get the h_wire'
-    ,'wire_salt'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'Set to TRUE once we have included this deposit in some aggregate wire 
transfer to the merchant'
-    ,'done'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'True if the aggregation of the deposit is currently blocked by some 
policy extension mechanism. Used to filter out deposits that must not be 
processed by the canonical deposit logic.'
-    ,'policy_blocked'
-    ,table_name
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_column(
-     'References policy extensions table, NULL if extensions are not used'
-    ,'policy_details_serial_id'
-    ,table_name
-    ,partition_suffix
-  );
-END
-$$;
-
-
-CREATE FUNCTION constrain_table_deposits(
-  IN partition_suffix TEXT
-)
-RETURNS void
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits';
-BEGIN
-  table_name = concat_ws('_', table_name, partition_suffix);
-  EXECUTE FORMAT (
-    'ALTER TABLE ' || table_name ||
-    ' ADD CONSTRAINT ' || table_name || '_deposit_serial_id_pkey'
-    ' PRIMARY KEY (deposit_serial_id) '
-    ',ADD CONSTRAINT ' || table_name || 
'_coin_pub_merchant_pub_h_contract_terms_key'
-    ' UNIQUE (coin_pub, merchant_pub, h_contract_terms)'
-  );
-  EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_by_ready '
-    'ON ' || table_name || ' '
-    '(wire_deadline ASC'
-    ',shard ASC'
-    ',coin_pub'
-    ') WHERE NOT (done OR policy_blocked);'
-  );
-  EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_for_matching '
-    'ON ' || table_name || ' '
-    '(refund_deadline ASC'
-    ',merchant_pub'
-    ',coin_pub'
-    ') WHERE NOT (done OR policy_blocked);'
-  );
-END
-$$;
-
-
-CREATE FUNCTION foreign_table_deposits()
-RETURNS void
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits';
-BEGIN
-  EXECUTE FORMAT (
-    'ALTER TABLE ' || table_name ||
-    ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
-    ' FOREIGN KEY (coin_pub) '
-    ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
-    ',ADD CONSTRAINT ' || table_name || '_foreign_coin_id'
-    ' FOREIGN KEY (known_coin_id) '
-    ' REFERENCES known_coins (known_coin_id) ON DELETE CASCADE'
-    ',ADD CONSTRAINT ' || table_name || '_foreign_policy_details'
-    ' FOREIGN KEY (policy_details_serial_id) '
-    ' REFERENCES policy_details (policy_details_serial_id) ON DELETE CASCADE'
-  );
-END
-$$;
-
-
-CREATE FUNCTION create_table_deposits_by_ready(
-  IN partition_suffix TEXT DEFAULT NULL
-)
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits_by_ready';
-BEGIN
-  PERFORM create_partitioned_table(
-  'CREATE TABLE %I'
-    '(wire_deadline INT8 NOT NULL'
-    ',shard INT8 NOT NULL'
-    ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
-    ',deposit_serial_id INT8'
-    ') %s ;'
-    ,table_name
-    ,'PARTITION BY RANGE (wire_deadline)'
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_table(
-    'Enables fast lookups for deposits_get_ready, auto-populated via TRIGGER 
below'
-    ,table_name
-    ,partition_suffix
-  );
-END
-$$;
-
-
-CREATE FUNCTION constrain_table_deposits_by_ready(
-  IN partition_suffix TEXT DEFAULT NULL
-)
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits_by_ready';
-BEGIN
-  table_name = concat_ws('_', table_name, partition_suffix);
-  EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_main_index '
-    'ON ' || table_name || ' '
-    '(wire_deadline ASC, shard ASC, coin_pub);'
-  );
-END
-$$;
-
-
-CREATE FUNCTION create_table_deposits_for_matching(
-  IN partition_suffix TEXT DEFAULT NULL
-)
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits_for_matching';
-BEGIN
-  PERFORM create_partitioned_table(
-  'CREATE TABLE %I'
-    '(refund_deadline INT8 NOT NULL'
-    ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
-    ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)' -- REFERENCES 
known_coins (coin_pub) ON DELETE CASCADE
-    ',deposit_serial_id INT8'
-    ') %s ;'
-    ,table_name
-    ,'PARTITION BY RANGE (refund_deadline)'
-    ,partition_suffix
-  );
-  PERFORM comment_partitioned_table(
-     'Enables fast lookups for deposits_iterate_matching, auto-populated via 
TRIGGER below'
-    ,table_name
-    ,partition_suffix
-  );
-END
-$$;
-
-
-CREATE FUNCTION constrain_table_deposits_for_matching(
-  IN partition_suffix TEXT DEFAULT NULL
-)
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  table_name TEXT DEFAULT 'deposits_for_matching';
-BEGIN
-  table_name = concat_ws('_', table_name, partition_suffix);
-  EXECUTE FORMAT (
-    'CREATE INDEX ' || table_name || '_main_index'
-    ' ON ' || table_name || ' '
-    '(refund_deadline ASC, merchant_pub, coin_pub);'
-  );
-END
-$$;
-
-
-CREATE OR REPLACE FUNCTION deposits_insert_trigger()
-  RETURNS trigger
-  LANGUAGE plpgsql
-  AS $$
-DECLARE
-  is_ready BOOLEAN;
-BEGIN
-  is_ready  = NOT (NEW.done OR NEW.policy_blocked);
-
-  IF (is_ready)
-  THEN
-    INSERT INTO exchange.deposits_by_ready
-      (wire_deadline
-      ,shard
-      ,coin_pub
-      ,deposit_serial_id)
-    VALUES
-      (NEW.wire_deadline
-      ,NEW.shard
-      ,NEW.coin_pub
-      ,NEW.deposit_serial_id);
-    INSERT INTO exchange.deposits_for_matching
-      (refund_deadline
-      ,merchant_pub
-      ,coin_pub
-      ,deposit_serial_id)
-    VALUES
-      (NEW.refund_deadline
-      ,NEW.merchant_pub
-      ,NEW.coin_pub
-      ,NEW.deposit_serial_id);
-  END IF;
-  RETURN NEW;
-END $$;
-COMMENT ON FUNCTION deposits_insert_trigger()
-  IS 'Replicate deposit inserts into materialized indices.';
-
-
-CREATE OR REPLACE FUNCTION deposits_update_trigger()
-  RETURNS trigger
-  LANGUAGE plpgsql
-  AS $$
-DECLARE
-  was_ready BOOLEAN;
-DECLARE
-  is_ready BOOLEAN;
-BEGIN
-  was_ready = NOT (OLD.done OR OLD.policy_blocked);
-  is_ready  = NOT (NEW.done OR NEW.policy_blocked);
-  IF (was_ready AND NOT is_ready)
-  THEN
-    DELETE FROM exchange.deposits_by_ready
-     WHERE wire_deadline = OLD.wire_deadline
-       AND shard = OLD.shard
-       AND coin_pub = OLD.coin_pub
-       AND deposit_serial_id = OLD.deposit_serial_id;
-    DELETE FROM exchange.deposits_for_matching
-     WHERE refund_deadline = OLD.refund_deadline
-       AND merchant_pub = OLD.merchant_pub
-       AND coin_pub = OLD.coin_pub
-       AND deposit_serial_id = OLD.deposit_serial_id;
-  END IF;
-  IF (is_ready AND NOT was_ready)
-  THEN
-    INSERT INTO exchange.deposits_by_ready
-      (wire_deadline
-      ,shard
-      ,coin_pub
-      ,deposit_serial_id)
-    VALUES
-      (NEW.wire_deadline
-      ,NEW.shard
-      ,NEW.coin_pub
-      ,NEW.deposit_serial_id);
-    INSERT INTO exchange.deposits_for_matching
-      (refund_deadline
-      ,merchant_pub
-      ,coin_pub
-      ,deposit_serial_id)
-    VALUES
-      (NEW.refund_deadline
-      ,NEW.merchant_pub
-      ,NEW.coin_pub
-      ,NEW.deposit_serial_id);
-  END IF;
-  RETURN NEW;
-END $$;
-COMMENT ON FUNCTION deposits_update_trigger()
-  IS 'Replicate deposits changes into materialized indices.';
-
-
-CREATE OR REPLACE FUNCTION deposits_delete_trigger()
-  RETURNS trigger
-  LANGUAGE plpgsql
-  AS $$
-DECLARE
-  was_ready BOOLEAN;
-BEGIN
-  was_ready  = NOT (OLD.done OR OLD.policy_blocked);
-
-  IF (was_ready)
-  THEN
-    DELETE FROM exchange.deposits_by_ready
-     WHERE wire_deadline = OLD.wire_deadline
-       AND shard = OLD.shard
-       AND coin_pub = OLD.coin_pub
-       AND deposit_serial_id = OLD.deposit_serial_id;
-    DELETE FROM exchange.deposits_for_matching
-     WHERE refund_deadline = OLD.refund_deadline
-       AND merchant_pub = OLD.merchant_pub
-       AND coin_pub = OLD.coin_pub
-       AND deposit_serial_id = OLD.deposit_serial_id;
-  END IF;
-  RETURN NEW;
-END $$;
-COMMENT ON FUNCTION deposits_delete_trigger()
-  IS 'Replicate deposit deletions into materialized indices.';
-
-
-CREATE FUNCTION master_table_deposits()
-RETURNS VOID
-LANGUAGE plpgsql
-AS $$
-BEGIN
-  CREATE TRIGGER deposits_on_insert
-    AFTER INSERT
-     ON deposits
-     FOR EACH ROW EXECUTE FUNCTION deposits_insert_trigger();
-  CREATE TRIGGER deposits_on_update
-    AFTER UPDATE
-      ON deposits
-     FOR EACH ROW EXECUTE FUNCTION deposits_update_trigger();
-  CREATE TRIGGER deposits_on_delete
-    AFTER DELETE
-     ON deposits
-     FOR EACH ROW EXECUTE FUNCTION deposits_delete_trigger();
-END $$;
-
-
-INSERT INTO exchange_tables
-    (name
-    ,version
-    ,action
-    ,partitioned
-    ,by_range)
-  VALUES
-    ('deposits'
-    ,'exchange-0002'
-    ,'create'
-    ,TRUE
-    ,FALSE),
-    ('deposits'
-    ,'exchange-0002'
-    ,'constrain'
-    ,TRUE
-    ,FALSE),
-    ('deposits'
-    ,'exchange-0002'
-    ,'foreign'
-    ,TRUE
-    ,FALSE)
-    ;
diff --git a/src/exchangedb/0002-refunds.sql b/src/exchangedb/0002-refunds.sql
index e82dad93..6eae5532 100644
--- a/src/exchangedb/0002-refunds.sql
+++ b/src/exchangedb/0002-refunds.sql
@@ -27,7 +27,7 @@ BEGIN
     'CREATE TABLE %I'
       '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
       ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
-      ',deposit_serial_id INT8 NOT NULL'
+      ',batch_deposit_serial_id INT8 NOT NULL'
       ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
       ',rtransaction_id INT8 NOT NULL'
       ',amount_with_fee taler_amount NOT NULL'
@@ -43,7 +43,7 @@ BEGIN
   );
   PERFORM comment_partitioned_column(
      'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. 
Multiple deposits may match a refund, this only identifies one of them.'
-    ,'deposit_serial_id'
+    ,'batch_deposit_serial_id'
     ,table_name
     ,partition_suffix
   );
@@ -76,7 +76,7 @@ BEGIN
     'ALTER TABLE ' || table_name ||
     ' ADD CONSTRAINT ' || table_name || '_refund_serial_id_key'
     ' UNIQUE (refund_serial_id) '
-    ',ADD PRIMARY KEY (deposit_serial_id, rtransaction_id) '
+    ',ADD PRIMARY KEY (batch_deposit_serial_id, rtransaction_id) '
   );
 END
 $$;
@@ -95,8 +95,8 @@ BEGIN
     ' FOREIGN KEY (coin_pub) '
     ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
     ',ADD CONSTRAINT ' || table_name || '_foreign_deposit'
-    ' FOREIGN KEY (deposit_serial_id) '
-    ' REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE'
+    ' FOREIGN KEY (batch_deposit_serial_id) '
+    ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
   );
 END
 $$;
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index cbe06ce0..4ffc574c 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -74,7 +74,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_compute_shard.h pg_compute_shard.c \
   plugin_exchangedb_postgres.c pg_helper.h \
   pg_reserves_update.h pg_reserves_update.c \
-  pg_insert_aggregation_tracking.h pg_insert_aggregation_tracking.c \
   pg_select_aggregation_amounts_for_kyc_check.h 
pg_select_aggregation_amounts_for_kyc_check.c \
   pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \
   pg_select_satisfied_kyc_processes.h pg_select_satisfied_kyc_processes.c \
@@ -144,7 +143,6 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_find_aggregation_transient.h pg_find_aggregation_transient.c \
   pg_update_aggregation_transient.h pg_update_aggregation_transient.c \
   pg_get_ready_deposit.h pg_get_ready_deposit.c \
-  pg_insert_deposit.h pg_insert_deposit.c \
   pg_insert_refund.h pg_insert_refund.c \
   pg_select_refunds_by_coin.h pg_select_refunds_by_coin.c \
   pg_get_melt.h pg_get_melt.c \
@@ -165,7 +163,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_start_deferred_wire_out.h pg_start_deferred_wire_out.c \
   pg_store_wire_transfer_out.h pg_store_wire_transfer_out.c \
   pg_gc.h pg_gc.c \
-  pg_select_deposits_above_serial_id.h pg_select_deposits_above_serial_id.c \
+  pg_select_coin_deposits_above_serial_id.h 
pg_select_coin_deposits_above_serial_id.c \
   pg_select_history_requests_above_serial_id.h 
pg_select_history_requests_above_serial_id.c \
   pg_select_purse_decisions_above_serial_id.h 
pg_select_purse_decisions_above_serial_id.c \
   pg_select_purse_deposits_by_purse.h pg_select_purse_deposits_by_purse.c \
@@ -182,7 +180,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_get_old_coin_by_h_blind.h pg_get_old_coin_by_h_blind.c \
   pg_insert_denomination_revocation.h pg_insert_denomination_revocation.c \
   pg_get_denomination_revocation.h pg_get_denomination_revocation.c \
-  pg_select_deposits_missing_wire.h pg_select_deposits_missing_wire.c \
+  pg_select_batch_deposits_missing_wire.h 
pg_select_batch_deposits_missing_wire.c \
   pg_lookup_auditor_timestamp.h pg_lookup_auditor_timestamp.c \
   pg_lookup_auditor_status.h pg_lookup_auditor_status.c \
   pg_insert_auditor.h pg_insert_auditor.c \
diff --git a/src/exchangedb/exchange-0002.sql.in 
b/src/exchangedb/exchange-0002.sql.in
index 9dd62a3d..9a810aa2 100644
--- a/src/exchangedb/exchange-0002.sql.in
+++ b/src/exchangedb/exchange-0002.sql.in
@@ -58,10 +58,12 @@ COMMENT ON TYPE exchange_do_array_reserve_insert_return_type
 #include "0002-reserves_open_requests.sql"
 #include "0002-reserves_out.sql"
 #include "0002-known_coins.sql"
+#include "0002-coin_history.sql"
 #include "0002-refresh_commitments.sql"
 #include "0002-refresh_revealed_coins.sql"
 #include "0002-refresh_transfer_keys.sql"
-#include "0002-deposits.sql"
+#include "0002-batch_deposits.sql"
+#include "0002-coin_deposits.sql"
 #include "0002-refunds.sql"
 #include "0002-wire_out.sql"
 #include "0002-aggregation_transient.sql"
diff --git a/src/exchangedb/exchange_do_deposit.sql 
b/src/exchangedb/exchange_do_deposit.sql
index 5a5e2c6f..f2828cb5 100644
--- a/src/exchangedb/exchange_do_deposit.sql
+++ b/src/exchangedb/exchange_do_deposit.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 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 General Public License as published by the Free Software
@@ -14,156 +14,194 @@
 -- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 --
 CREATE OR REPLACE FUNCTION exchange_do_deposit(
-  IN in_amount_with_fee taler_amount,
-  IN in_h_contract_terms BYTEA,
-  IN in_wire_salt BYTEA,
+  -- For batch_deposits
+  IN in_shard INT8,
+  IN in_merchant_pub BYTEA,
   IN in_wallet_timestamp INT8,
   IN in_exchange_timestamp INT8,
   IN in_refund_deadline INT8,
   IN in_wire_deadline INT8,
-  IN in_merchant_pub BYTEA,
-  IN in_receiver_wire_account TEXT,
-  IN in_h_payto BYTEA,
-  IN in_known_coin_id INT8,
-  IN in_coin_pub BYTEA,
-  IN in_coin_sig BYTEA,
-  IN in_shard INT8,
+  IN in_h_contract_terms BYTEA,
+  IN in_wallet_data_hash BYTEA, -- can be NULL
+  IN in_wire_salt BYTEA,
+  IN in_wire_target_h_payto BYTEA,
+  IN in_policy_details_serial_id INT8, -- can be NULL
   IN in_policy_blocked BOOLEAN,
-  IN in_policy_details_serial_id INT8,
+  -- For wire_targets
+  IN in_receiver_wire_account TEXT,
+  -- For coin_deposits
+  IN ina_coin_pub BYTEA[],
+  IN ina_coin_sig BYTEA[],
+  IN ina_amount_with_fee taler_amount[],
   OUT out_exchange_timestamp INT8,
-  OUT out_balance_ok BOOLEAN,
-  OUT out_conflict BOOLEAN)
+  OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad 
balance, NULL if none
+  OUT out_conflict BOOL
+ )
 LANGUAGE plpgsql
 AS $$
 DECLARE
   wtsi INT8; -- wire target serial id
+  bdsi INT8; -- batch_deposits serial id
+  curs REFCURSOR;
+  i INT4;
+  ini_amount_with_fee taler_amount;
+  ini_coin_pub BYTEA;
+  ini_coin_sig BYTEA;
 BEGIN
--- Shards: INSERT policy_details (by policy_details_serial_id)
---         INSERT wire_targets (by h_payto), on CONFLICT DO NOTHING;
---         INSERT deposits (by coin_pub, shard), ON CONFLICT DO NOTHING;
---         UPDATE known_coins (by coin_pub)
+-- Shards:
+--         INSERT wire_targets (by h_payto), ON CONFLICT DO NOTHING;
+--         INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT 
idempotency check;
+--         INSERT[] coin_deposits (by coin_pub), ON CONFLICT idempotency check;
+--         UPDATE[] known_coins (by coin_pub)
+
 
-INSERT INTO exchange.wire_targets
-  (wire_target_h_payto
-  ,payto_uri)
+-- First, get or create the 'wtsi'
+INSERT INTO wire_targets
+    (wire_target_h_payto
+    ,payto_uri)
   VALUES
-  (in_h_payto
-  ,in_receiver_wire_account)
-ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
-  RETURNING wire_target_serial_id INTO wtsi;
+    (in_wire_target_h_payto
+    ,in_receiver_wire_account)
+  ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
+  RETURNING
+    wire_target_serial_id
+  INTO
+    wtsi;
 
 IF NOT FOUND
 THEN
-  SELECT wire_target_serial_id
-  INTO wtsi
-  FROM exchange.wire_targets
-  WHERE wire_target_h_payto=in_h_payto;
+  SELECT
+    wire_target_serial_id
+  INTO
+    wtsi
+  FROM wire_targets
+  WHERE
+    wire_target_h_payto=in_wire_target_h_payto;
 END IF;
 
 
-INSERT INTO exchange.deposits
+-- Second, create the batch_deposits entry
+INSERT INTO batch_deposits
   (shard
-  ,coin_pub
-  ,known_coin_id
-  ,amount_with_fee
+  ,merchant_pub
   ,wallet_timestamp
   ,exchange_timestamp
   ,refund_deadline
   ,wire_deadline
-  ,merchant_pub
   ,h_contract_terms
-  ,coin_sig
+  ,wallet_data_hash
   ,wire_salt
   ,wire_target_h_payto
-  ,policy_blocked
   ,policy_details_serial_id
+  ,policy_blocked
   )
   VALUES
   (in_shard
-  ,in_coin_pub
-  ,in_known_coin_id
-  ,in_amount_with_fee
+  ,in_merchant_pub
   ,in_wallet_timestamp
   ,in_exchange_timestamp
   ,in_refund_deadline
   ,in_wire_deadline
-  ,in_merchant_pub
   ,in_h_contract_terms
-  ,in_coin_sig
+  ,in_wallet_data_hash
   ,in_wire_salt
-  ,in_h_payto
-  ,in_policy_blocked
-  ,in_policy_details_serial_id)
-  ON CONFLICT DO NOTHING;
+  ,in_wire_target_h_payto
+  ,in_policy_details_serial_id
+  ,in_policy_blocked)
+  ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms)
+  RETURNING
+    batch_deposit_serial_id
+  INTO
+    bdsi;
 
 IF NOT FOUND
 THEN
   -- Idempotency check: see if an identical record exists.
-  -- Note that by checking 'coin_sig', we implicitly check
-  -- identity over everything that the signature covers.
-  -- We do select over merchant_pub and wire_target_h_payto
-  -- primarily here to maximally use the existing index.
+  -- We do select over merchant_pub, h_contract_terms and wire_target_h_payto
+  -- first to maximally increase the chance of using the existing index.
   SELECT
-     exchange_timestamp
+      exchange_timestamp
+     ,batch_deposit_serial_id
    INTO
-     out_exchange_timestamp
-   FROM exchange.deposits
+      out_exchange_timestamp
+     ,bdsi
+   FROM batch_deposits
    WHERE shard=in_shard
      AND merchant_pub=in_merchant_pub
-     AND wire_target_h_payto=in_h_payto
-     AND coin_pub=in_coin_pub
-     AND coin_sig=in_coin_sig;
-     -- AND policy_details_serial_id=in_policy_details_serial_id; -- FIXME: is 
this required for idempotency?
-
+     AND h_contract_terms=in_h_contract_terms
+     AND wire_target_h_payto=in_wire_target_h_payto
+     -- now check the rest, too
+     AND ( (wallet_data_hash=in_wallet_data_hash) OR
+           (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
+     AND wire_salt=in_wire_salt
+     AND wallet_timestamp=in_wallet_timestamp
+     AND refund_deadline=in_refund_deadline
+     AND wire_deadline=in_wire_deadline
+     AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
+           (policy_details_serial_id IS NULL AND in_policy_details_serial_id 
IS NULL) );
   IF NOT FOUND
   THEN
-    -- Deposit exists, but with differences. Not allowed.
-    out_balance_ok=FALSE;
+    -- Deposit exists, but with *strange* differences. Not allowed.
     out_conflict=TRUE;
-    out_exchange_timestamp=0;
     RETURN;
   END IF;
+END IF;
 
-  -- Idempotent request known, return success.
-  out_balance_ok=TRUE;
-  out_conflict=FALSE;
+out_conflict=FALSE;
 
-  RETURN;
-END IF;
+-- Deposit each coin
 
+FOR i IN 1..array_length(ina_coin_pub,1)
+LOOP
+  ini_coin_pub = ina_coin_pub[i];
+  ini_coin_sig = ina_coin_sig[i];
+  ini_amount_with_fee = ina_amount_with_fee[i];
 
-out_exchange_timestamp=in_exchange_timestamp;
+  INSERT INTO coin_deposits
+    (batch_deposit_serial_id
+    ,coin_pub
+    ,coin_sig
+    ,amount_with_fee
+    )
+    VALUES
+    (bdsi
+    ,ini_coin_pub
+    ,ini_coin_sig
+    ,ini_amount_with_fee
+    )
+    ON CONFLICT DO NOTHING;
 
--- Check and update balance of the coin.
-UPDATE known_coins kc
-  SET
-    remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
-       + CASE
-         WHEN (kc.remaining).frac < in_amount_with_fee.frac
-         THEN 100000000
-         ELSE 0
-         END,
-    remaining.val=(kc.remaining).val-in_amount_with_fee.val
-       - CASE
-         WHEN (kc.remaining).frac < in_amount_with_fee.frac
-         THEN 1
-         ELSE 0
-         END
-  WHERE coin_pub=in_coin_pub
-    AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
-          ( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
-            ((kc.remaining).val >= in_amount_with_fee.val) ) );
+  IF FOUND
+  THEN
+    -- Insert did happen, update balance in known_coins!
 
-IF NOT FOUND
-THEN
-  -- Insufficient balance.
-  out_balance_ok=FALSE;
-  out_conflict=FALSE;
-  RETURN;
-END IF;
+    UPDATE known_coins kc
+      SET
+        remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac
+          + CASE
+              WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+              THEN 100000000
+              ELSE 0
+            END,
+        remaining.val=(kc.remaining).val-ini_amount_with_fee.val
+          - CASE
+              WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+              THEN 1
+              ELSE 0
+            END
+      WHERE coin_pub=ini_coin_pub
+        AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR
+              ( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND
+                ((kc.remaining).val >= ini_amount_with_fee.val) ) );
 
--- Everything fine, return success!
-out_balance_ok=TRUE;
-out_conflict=FALSE;
+    IF NOT FOUND
+    THEN
+      -- Insufficient balance.
+      -- Note: C arrays are 0 indexed, but i started at 1
+      out_insufficient_balance_coin_index=i-i;
+      RETURN;
+    END IF;
+  END IF;
+END LOOP; -- end FOR all coins
 
 END $$;
diff --git a/src/exchangedb/exchange_do_gc.sql 
b/src/exchangedb/exchange_do_gc.sql
index 75dd120d..5758cb2d 100644
--- a/src/exchangedb/exchange_do_gc.sql
+++ b/src/exchangedb/exchange_do_gc.sql
@@ -23,7 +23,7 @@ DECLARE
   reserve_uuid_min INT8; -- minimum reserve UUID still alive
   melt_min INT8; -- minimum melt still alive
   coin_min INT8; -- minimum known_coin still alive
-  deposit_min INT8; -- minimum deposit still alive
+  batch_deposit_min INT8; -- minimum deposit still alive
   reserve_out_min INT8; -- minimum reserve_out still alive
   denom_min INT8; -- minimum denomination still alive
 BEGIN
@@ -104,22 +104,25 @@ SELECT
   ORDER BY known_coin_id ASC
   LIMIT 1;
 
-DELETE FROM exchange.deposits
-  WHERE known_coin_id < coin_min;
+DELETE FROM exchange.batch_deposits
+  WHERE wire_deadline < in_ancient_date;
 
 SELECT
-     deposit_serial_id
+     batch_deposit_serial_id
   INTO
-     deposit_min
-  FROM exchange.deposits
-  ORDER BY deposit_serial_id ASC
+     batch_deposit_min
+  FROM exchange.coin_deposits
+  ORDER BY batch_deposit_serial_id ASC
   LIMIT 1;
 
 DELETE FROM exchange.refunds
-  WHERE deposit_serial_id < deposit_min;
-
+  WHERE batch_deposit_serial_id < batch_deposit_min;
 DELETE FROM exchange.aggregation_tracking
-  WHERE deposit_serial_id < deposit_min;
+  WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM exchange.coin_deposits
+  WHERE batch_deposit_serial_id < batch_deposit_min;
+
+
 
 SELECT
      denominations_serial
@@ -133,6 +136,3 @@ DELETE FROM exchange.cs_nonce_locks
   WHERE max_denomination_serial <= denom_min;
 
 END $$;
-
-
-
diff --git a/src/exchangedb/exchange_do_get_ready_deposit.sql 
b/src/exchangedb/exchange_do_get_ready_deposit.sql
deleted file mode 100644
index 7b10c205..00000000
--- a/src/exchangedb/exchange_do_get_ready_deposit.sql
+++ /dev/null
@@ -1,69 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
--- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
---
-CREATE OR REPLACE FUNCTION exchange_do_get_ready_deposit(
-  IN in_now INT8,
-  IN in_start_shard_now INT8,
-  IN in_end_shard_now INT8,
-  OUT out_payto_uri TEXT,
-  OUT out_merchant_pub BYTEA
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE
- var_wire_target_h_payto BYTEA;
-DECLARE
- var_coin_pub BYTEA;
-DECLARE
- var_deposit_serial_id INT8;
-DECLARE
- curs CURSOR
- FOR
- SELECT
-   coin_pub
-  ,deposit_serial_id
-  ,wire_deadline
-  ,shard
- FROM deposits_by_ready
- WHERE wire_deadline <= in_now
- AND shard >=in_start_shard_now
- AND shard <=in_end_shard_now
- LIMIT 1;
-DECLARE
- i RECORD;
-BEGIN
-OPEN curs;
-FETCH FROM curs INTO i;
-IF NOT FOUND
-THEN
-  RETURN;
-END IF;
-SELECT
-   payto_uri
-  ,merchant_pub
-  INTO
-   out_payto_uri
-  ,out_merchant_pub
-  FROM deposits dep
-  JOIN wire_targets wt
-  ON (wt.wire_target_h_payto=dep.wire_target_h_payto)
-  WHERE dep.coin_pub=i.coin_pub
-  AND dep.deposit_serial_id=i.deposit_serial_id
-  ORDER BY
-   i.wire_deadline ASC
-  ,i.shard ASC;
-
-RETURN;
-END $$;
diff --git a/src/exchangedb/exchange_do_refund.sql 
b/src/exchangedb/exchange_do_refund.sql
index 7cb50e9f..a9574612 100644
--- a/src/exchangedb/exchange_do_refund.sql
+++ b/src/exchangedb/exchange_do_refund.sql
@@ -1,6 +1,6 @@
 --
 -- This file is part of TALER
--- Copyright (C) 2014--2022 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 General Public License as published by the Free Software
@@ -32,7 +32,7 @@ CREATE OR REPLACE FUNCTION exchange_do_refund(
 LANGUAGE plpgsql
 AS $$
 DECLARE
-  dsi INT8; -- ID of deposit being refunded
+  bdsi INT8; -- ID of deposit being refunded
 DECLARE
   tmp_val INT8; -- total amount refunded
 DECLARE
@@ -48,17 +48,19 @@ BEGIN
 --         UPDATE known_coins (by coin_pub)
 
 SELECT
-   deposit_serial_id
-  ,(dep.amount_with_fee).val
-  ,(dep.amount_with_fee).frac
-  ,done
-INTO
-   dsi
+   bdep.batch_deposit_serial_id
+  ,(cdep.amount_with_fee).val
+  ,(cdep.amount_with_fee).frac
+  ,bdep.done
+ INTO
+   bdsi
   ,deposit.val
   ,deposit.frac
   ,out_gone
-FROM exchange.deposits dep
- WHERE coin_pub=in_coin_pub
+ FROM batch_deposits bdep
+ JOIN coin_deposits cdep
+   USING (batch_deposit_serial_id)
+ WHERE cdep.coin_pub=in_coin_pub
   AND shard=in_deposit_shard
   AND merchant_pub=in_merchant_pub
   AND h_contract_terms=in_h_contract_terms;
@@ -73,15 +75,15 @@ THEN
   RETURN;
 END IF;
 
-INSERT INTO exchange.refunds
-  (deposit_serial_id
+INSERT INTO refunds
+  (batch_deposit_serial_id
   ,coin_pub
   ,merchant_sig
   ,rtransaction_id
   ,amount_with_fee
   )
   VALUES
-  (dsi
+  (bdsi
   ,in_coin_pub
   ,in_merchant_sig
   ,in_rtransaction_id
@@ -99,7 +101,7 @@ THEN
    PERFORM
    FROM exchange.refunds
    WHERE coin_pub=in_coin_pub
-     AND deposit_serial_id=dsi
+     AND batch_deposit_serial_id=bdsi
      AND rtransaction_id=in_rtransaction_id
      AND amount_with_fee=in_amount_with_fee;
 
@@ -131,14 +133,14 @@ END IF;
 
 -- Check refund balance invariant.
 SELECT
-   SUM((refunds.amount_with_fee).val) -- overflow here is not plausible
-  ,SUM(CAST((refunds.amount_with_fee).frac AS INT8)) -- compute using 64 bits
+   SUM((refs.amount_with_fee).val) -- overflow here is not plausible
+  ,SUM(CAST((refs.amount_with_fee).frac AS INT8)) -- compute using 64 bits
   INTO
    tmp_val
   ,tmp_frac
-  FROM exchange.refunds refunds
+  FROM refunds refs
   WHERE coin_pub=in_coin_pub
-    AND deposit_serial_id=dsi;
+    AND batch_deposit_serial_id=bdsi;
 IF tmp_val IS NULL
 THEN
   RAISE NOTICE 'failed to sum up existing refunds';
@@ -199,5 +201,5 @@ out_not_found=FALSE;
 
 END $$;
 
--- COMMENT ON FUNCTION exchange_do_refund(taler_amount, BYTEA, BOOLEAN, 
BOOLEAN)
---  IS 'Executes a refund operation, checking that the corresponding deposit 
was sufficient to cover the refunded amount';
+COMMENT ON FUNCTION exchange_do_refund(taler_amount, taler_amount, 
taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
+  IS 'Executes a refund operation, checking that the corresponding deposit was 
sufficient to cover the refunded amount';
diff --git a/src/exchangedb/exchange_do_refund_by_coin.sql 
b/src/exchangedb/exchange_do_refund_by_coin.sql
deleted file mode 100644
index d5f99e6a..00000000
--- a/src/exchangedb/exchange_do_refund_by_coin.sql
+++ /dev/null
@@ -1,86 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
--- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
---
-
-CREATE OR REPLACE FUNCTION exchange_do_refund_by_coin(
-  IN in_coin_pub BYTEA,
-  IN in_merchant_pub BYTEA,
-  IN in_h_contract BYTEA
-)
-RETURNS SETOF record
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  curs CURSOR
-  FOR
-  SELECT
-    amount_with_fee
-   ,deposit_serial_id
-  FROM refunds
-  WHERE coin_pub=in_coin_pub;
-DECLARE
-  i RECORD;
-BEGIN
-OPEN curs;
-LOOP
-    FETCH NEXT FROM curs INTO i;
-    EXIT WHEN NOT FOUND;
-    RETURN QUERY
-      SELECT
-        i.amount_with_fee
-       FROM deposits
-       WHERE
-         coin_pub=in_coin_pub
-         AND merchant_pub=in_merchant_pub
-         AND h_contract_terms=in_h_contract
-         AND i.deposit_serial_id = deposit_serial_id;
-END LOOP;
-CLOSE curs;
-END $$;
-
-/*RETURNS TABLE(amount_with_fee taler_amount)
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  curs CURSOR
-  FOR
-  SELECT
-    r.amount_with_fee
-   ,r.deposit_serial_id
-  FROM refunds r
-  WHERE r.coin_pub=in_coin_pub;
-DECLARE
-  i RECORD;
-BEGIN
-OPEN curs;
-LOOP
-    FETCH NEXT FROM curs INTO i;
-    IF FOUND
-    THEN
-      RETURN QUERY
-      SELECT
-        i.amount_with_fee
-       FROM deposits
-       WHERE
-         merchant_pub=in_merchant_pub
-         AND h_contract_terms=in_h_contract
-         AND i.deposit_serial_id = deposit_serial_id;
-    END IF;
-    EXIT WHEN NOT FOUND;
-END LOOP;
-CLOSE curs;
-
-END $$;
-*/
diff --git a/src/exchangedb/exchange_get_ready_deposit.sql 
b/src/exchangedb/exchange_get_ready_deposit.sql
deleted file mode 100644
index 2186f561..00000000
--- a/src/exchangedb/exchange_get_ready_deposit.sql
+++ /dev/null
@@ -1,60 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2022 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
--- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
---
-CREATE OR REPLACE FUNCTION exchange_do_get_ready_deposit(
-  IN in_now INT8,
-  IN in_start_shard_now INT8,
-  IN in_end_shard_now INT8,
-  OUT out_payto_uri TEXT,
-  OUT out_merchant_pub BYTEA
-)
-LANGUAGE plpgsql
-AS $$
-DECLARE
-  curs CURSOR
-  FOR
-  SELECT
-    coin_pub
-   ,deposit_serial_id
-   ,wire_deadline
-   ,shard
-  FROM deposits_by_ready dbr
-  WHERE wire_deadline <= in_now
-  AND shard >= in_start_shard_now
-  AND shard <=in_end_shard_now
-  ORDER BY
-     wire_deadline ASC
-    ,shard ASC
-  LIMIT 1;
-DECLARE
-  i RECORD;
-BEGIN
-OPEN curs;
-FETCH FROM curs INTO i;
-SELECT
-   payto_uri
-  ,merchant_pub
-  INTO
-   out_payto_uri
-  ,out_merchant_pub
-  FROM deposits
-  JOIN wire_targets wt
-  USING (wire_target_h_payto)
-  WHERE
-  i.coin_pub = coin_pub
-  AND i.deposit_serial_id=deposit_serial_id;
-CLOSE curs;
-RETURN;
-END $$;
diff --git a/src/exchangedb/perf_deposits_get_ready.c 
b/src/exchangedb/perf_deposits_get_ready.c
index 89699da2..2effee73 100644
--- a/src/exchangedb/perf_deposits_get_ready.c
+++ b/src/exchangedb/perf_deposits_get_ready.c
@@ -16,7 +16,7 @@
 /**
  * @file exchangedb/perf_deposits_get_ready.c
  * @brief benchmark for deposits_get_ready
- * @author Joseph Xu
+git  * @author Joseph Xu
  */
 #include "platform.h"
 #include "taler_exchangedb_lib.h"
@@ -196,8 +196,9 @@ run (void *cls)
   struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
   struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
   unsigned long long sqrs = 0;
-  struct TALER_EXCHANGEDB_Deposit *depos = NULL;
-  struct TALER_EXCHANGEDB_Refund *ref = NULL;
+  struct TALER_EXCHANGEDB_CoinDepositInformation *depos;
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
+  struct TALER_EXCHANGEDB_Refund *ref;
   unsigned int *perm;
   unsigned long long duration_sq;
   struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
@@ -208,7 +209,7 @@ run (void *cls)
   ref = GNUNET_new_array (ROUNDS + 1,
                           struct TALER_EXCHANGEDB_Refund);
   depos = GNUNET_new_array (ROUNDS + 1,
-                            struct TALER_EXCHANGEDB_Deposit);
+                            struct TALER_EXCHANGEDB_CoinDepositInformation);
 
   if (NULL ==
       (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
@@ -321,9 +322,9 @@ run (void *cls)
     struct TALER_CoinPubHashP c_hash;
     unsigned int k = (unsigned int) rand () % 5;
     unsigned int i = perm[j];
+
     if (i >= ROUNDS)
       i = ROUNDS;   /* throw-away slot, do not keep around */
-    depos[i].deposit_fee = fees.deposit;
     RND_BLK (&coin_pub);
     RND_BLK (&c_hash);
     RND_BLK (&reserve_pub);
@@ -331,7 +332,7 @@ run (void *cls)
     TALER_denom_pub_hash (&new_dkp[k]->pub,
                           &cbc.denom_pub_hash);
     deadline = GNUNET_TIME_timestamp_get ();
-    RND_BLK (&depos[i].coin.coin_pub);
+    depos[i].coin.coin_pub = coin_pub;
     TALER_denom_pub_hash (&new_dkp[k]->pub,
                           &depos[i].coin.denom_pub_hash);
     GNUNET_assert (GNUNET_OK ==
@@ -341,19 +342,21 @@ run (void *cls)
                                             &c_hash,
                                             &alg_values,
                                             &new_dkp[k]->pub));
-    RND_BLK (&depos[i].merchant_pub);
+    RND_BLK (&bd.merchant_pub);
     RND_BLK (&depos[i].csig);
-    RND_BLK (&depos[i].h_contract_terms);
-    RND_BLK (&depos[i].wire_salt);
+    RND_BLK (&bd.h_contract_terms);
+    RND_BLK (&bd.wire_salt);
     depos[i].amount_with_fee = value;
-    depos[i].refund_deadline = deadline;
-    depos[i].wire_deadline = deadline;
-    depos[i].receiver_wire_account =
+    bd.refund_deadline = deadline;
+    bd.wire_deadline = deadline;
+    bd.receiver_wire_account =
       "payto://iban/DE67830654080004822650?receiver-name=Test";
     TALER_merchant_wire_signature_hash (
-      "payto://iban/DE67830654080004822650?receiver-name=Test",
-      &depos[i].wire_salt,
+      bd.receiver_wire_account,
+      &bd.wire_salt,
       &h_wire_wt);
+    bd.num_cdis = 1;
+    bd.cdis = &depos[i];
     cbc.reserve_pub = reserve_pub;
     cbc.amount_with_fee = value;
     GNUNET_assert (GNUNET_OK ==
@@ -401,12 +404,18 @@ run (void *cls)
     }
     {
       struct GNUNET_TIME_Timestamp now;
+      bool balance_ok;
+      uint32_t bad_idx;
+      bool ctr_conflict;
 
       now = GNUNET_TIME_timestamp_get ();
       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-              plugin->insert_deposit (plugin->cls,
-                                      now,
-                                      &depos[i]));
+              plugin->do_deposit (plugin->cls,
+                                  &bd,
+                                  &now,
+                                  &balance_ok,
+                                  &bad_idx,
+                                  &ctr_conflict));
     }
     if (ROUNDS == i)
       TALER_denom_sig_free (&depos[i].coin.denom_sig);
diff --git a/src/exchangedb/perf_select_refunds_by_coin.c 
b/src/exchangedb/perf_select_refunds_by_coin.c
index 85c92f4b..fdb44d2c 100644
--- a/src/exchangedb/perf_select_refunds_by_coin.c
+++ b/src/exchangedb/perf_select_refunds_by_coin.c
@@ -211,11 +211,10 @@ run (void *cls)
   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
   const uint32_t num_partitions = 10;
   struct GNUNET_TIME_Timestamp ts;
-  struct TALER_EXCHANGEDB_Deposit *depos = NULL;
+  struct TALER_EXCHANGEDB_CoinDepositInformation *depos = NULL;
   struct GNUNET_TIME_Timestamp deadline;
   struct TALER_Amount value;
   union TALER_DenominationBlindingKeyP bks;
-  struct TALER_CoinPubHashP c_hash;
   struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
   struct TALER_ExchangeWithdrawValues alg_values = {
     .cipher = TALER_DENOMINATION_RSA
@@ -225,7 +224,6 @@ run (void *cls)
   struct TALER_EXCHANGEDB_Refund *ref = NULL;
   unsigned int *perm;
   unsigned long long duration_sq;
-  struct TALER_CoinSpendPublicKeyP coin_pub;
   struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
   struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
   unsigned int count = 0;
@@ -233,7 +231,7 @@ run (void *cls)
   ref = GNUNET_new_array (ROUNDS + 1,
                           struct TALER_EXCHANGEDB_Refund);
   depos = GNUNET_new_array (ROUNDS + 1,
-                            struct TALER_EXCHANGEDB_Deposit);
+                            struct TALER_EXCHANGEDB_CoinDepositInformation);
   ZR_BLK (&cbc);
 
   if (NULL ==
@@ -344,55 +342,68 @@ run (void *cls)
   {
     unsigned int i = perm[j];
     unsigned int k = (unsigned int) rand () % 5;
+    struct TALER_CoinPubHashP c_hash;
+    uint64_t known_coin_id;
+    struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &depos[i];
+    struct TALER_EXCHANGEDB_BatchDeposit bd = {
+      .cdis = cdi,
+      .num_cdis = 1,
+      .wallet_timestamp = ts,
+      .refund_deadline = deadline,
+      .wire_deadline = deadline,
+      .receiver_wire_account
+        = "payto://iban/DE67830654080004822650?receiver-name=Test"
+    };
+
     if (i >= ROUNDS)
       i = ROUNDS; /* throw-away slot, do not keep around */
-    RND_BLK (&coin_pub);
+    RND_BLK (&bd.merchant_pub);
+    RND_BLK (&bd.h_contract_terms);
+    RND_BLK (&bd.wire_salt);
+    TALER_merchant_wire_signature_hash (
+      bd.receiver_wire_account,
+      &bd.wire_salt,
+      &h_wire_wt);
+    RND_BLK (&cdi->coin.coin_pub);
+    RND_BLK (&cdi->csig);
     RND_BLK (&c_hash);
-    depos[i].deposit_fee = fees.deposit;
-    RND_BLK (&depos[i].coin.coin_pub);
     TALER_denom_pub_hash (&new_dkp[k]->pub,
-                          &depos[i].coin.denom_pub_hash);
+                          &cdi->coin.denom_pub_hash);
     GNUNET_assert (GNUNET_OK ==
-                   TALER_denom_sig_unblind (&depos[i].coin.denom_sig,
+                   TALER_denom_sig_unblind (&cdi->coin.denom_sig,
                                             &cbc.sig,
                                             &bks,
                                             &c_hash,
                                             &alg_values,
                                             &new_dkp[k]->pub));
-    RND_BLK (&depos[i].merchant_pub);
-    RND_BLK (&depos[i].csig);
-    RND_BLK (&depos[i].h_contract_terms);
-    RND_BLK (&depos[i].wire_salt);
-    depos[i].amount_with_fee = value;
-    depos[i].refund_deadline = deadline;
-    depos[i].wire_deadline = deadline;
-    depos[i].receiver_wire_account =
-      "payto://iban/DE67830654080004822650?receiver-name=Test";
-    TALER_merchant_wire_signature_hash (
-      "payto://iban/DE67830654080004822650?receiver-name=Test",
-      &depos[i].wire_salt,
-      &h_wire_wt);
-    depos[i].timestamp = ts;
-    uint64_t known_coin_id;
+    cdi->amount_with_fee = value;
+
     {
       struct TALER_DenominationHashP dph;
       struct TALER_AgeCommitmentHash agh;
 
       FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
               plugin->ensure_coin_known (plugin->cls,
-                                         &depos[i].coin,
+                                         &cdi->coin,
                                          &known_coin_id,
                                          &dph,
                                          &agh));
     }
     {
       struct GNUNET_TIME_Timestamp now;
+      bool balance_ok;
+      uint32_t bad_idx;
+      bool in_conflict;
 
       now = GNUNET_TIME_timestamp_get ();
       FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-              plugin->insert_deposit (plugin->cls,
-                                      now,
-                                      &depos[i]));
+              plugin->do_deposit (plugin->cls,
+                                  &bd,
+                                  &now,
+                                  &balance_ok,
+                                  &bad_idx,
+                                  &in_conflict));
     }
     {
       bool not_found;
@@ -404,9 +415,9 @@ run (void *cls)
       {
       case 2: // 100% refund
         ref[i].coin = depos[i].coin;
-        ref[i].details.merchant_pub = depos[i].merchant_pub;
+        ref[i].details.merchant_pub = bd.merchant_pub;
         RND_BLK (&ref[i].details.merchant_sig);
-        ref[i].details.h_contract_terms = depos[i].h_contract_terms;
+        ref[i].details.h_contract_terms = bd.h_contract_terms;
         ref[i].coin.coin_pub = depos[i].coin.coin_pub;
         ref[i].details.rtransaction_id = i;
         ref[i].details.refund_amount = value;
@@ -425,9 +436,9 @@ run (void *cls)
         if (count < (NUM_ROWS / 10))
         {
           ref[i].coin = depos[i].coin;
-          ref[i].details.merchant_pub = depos[i].merchant_pub;
+          ref[i].details.merchant_pub = bd.merchant_pub;
           RND_BLK (&ref[i].details.merchant_sig);
-          ref[i].details.h_contract_terms = depos[i].h_contract_terms;
+          ref[i].details.h_contract_terms = bd.h_contract_terms;
           ref[i].coin.coin_pub = depos[i].coin.coin_pub;
           ref[i].details.rtransaction_id = i;
           ref[i].details.refund_amount = value;
diff --git a/src/exchangedb/pg_aggregate.c b/src/exchangedb/pg_aggregate.c
index 82f73192..6f143e94 100644
--- a/src/exchangedb/pg_aggregate.c
+++ b/src/exchangedb/pg_aggregate.c
@@ -52,54 +52,61 @@ TEH_PG_aggregate (
                                          pg->aggregator_shift);
   PREPARE (pg,
            "aggregate",
-           "WITH dep AS (" /* restrict to our merchant and account and mark as 
done */
-           "  UPDATE deposits"
+           "WITH bdep AS (" /* restrict to our merchant and account and mark 
as done */
+           "  UPDATE batch_deposits"
            "     SET done=TRUE"
            "   WHERE NOT (done OR policy_blocked)" /* only actually executable 
deposits */
-           "     AND refund_deadline<$1" /* filter by shard */
+           "     AND refund_deadline<$1"
+           /* FIXME: maybe more efficient to add shard here, too? */
            "     AND merchant_pub=$2" /* filter by target merchant */
            "     AND wire_target_h_payto=$3" /* merchant could have a 2nd bank 
account */
            "   RETURNING"
-           "     deposit_serial_id"
+           "     batch_deposit_serial_id)"
+           " ,cdep AS ("
+           "   SELECT"
+           "     coin_deposit_serial_id"
+           "    ,batch_deposit_serial_id"
            "    ,coin_pub"
-           "    ,amount_with_fee AS amount)"
+           "    ,amount_with_fee AS amount"
+           "   FROM coin_deposits"
+           "   WHERE batch_deposit_serial_id IN (SELECT 
batch_deposit_serial_id FROM bdep))"
            " ,ref AS (" /* find applicable refunds -- NOTE: may do a full join 
on the master, maybe find a left-join way to integrate with query above to push 
it to the shards? */
            "  SELECT"
            "    amount_with_fee AS refund"
            "   ,coin_pub"
-           "   ,deposit_serial_id" /* theoretically, coin could be in multiple 
refunded transactions */
+           "   ,batch_deposit_serial_id" /* theoretically, coin could be in 
multiple refunded transactions */
            "    FROM refunds"
-           "   WHERE coin_pub IN (SELECT coin_pub FROM dep)"
-           "     AND deposit_serial_id IN (SELECT deposit_serial_id FROM dep))"
+           "   WHERE coin_pub IN (SELECT coin_pub FROM cdep)"
+           "     AND batch_deposit_serial_id IN (SELECT 
batch_deposit_serial_id FROM bdep))"
            " ,ref_by_coin AS (" /* total up refunds by coin */
            "  SELECT"
            "    SUM((ref.refund).val) AS sum_refund_val"
            "   ,SUM((ref.refund).frac) AS sum_refund_frac"
            "   ,coin_pub"
-           "   ,deposit_serial_id" /* theoretically, coin could be in multiple 
refunded transactions */
+           "   ,batch_deposit_serial_id" /* theoretically, coin could be in 
multiple refunded transactions */
            "    FROM ref"
-           "   GROUP BY coin_pub, deposit_serial_id)"
+           "   GROUP BY coin_pub, batch_deposit_serial_id)"
            " ,norm_ref_by_coin AS (" /* normalize */
            "  SELECT"
            "    sum_refund_val + sum_refund_frac / 100000000 AS 
norm_refund_val"
            "   ,sum_refund_frac % 100000000 AS norm_refund_frac"
            "   ,coin_pub"
-           "   ,deposit_serial_id" /* theoretically, coin could be in multiple 
refunded transactions */
+           "   ,batch_deposit_serial_id" /* theoretically, coin could be in 
multiple refunded transactions */
            "    FROM ref_by_coin)"
            " ,fully_refunded_coins AS (" /* find applicable refunds -- NOTE: 
may do a full join on the master, maybe find a left-join way to integrate with 
query above to push it to the shards? */
            "  SELECT"
-           "    dep.coin_pub"
+           "    cdep.coin_pub"
            "    FROM norm_ref_by_coin norm"
-           "    JOIN dep"
-           "      ON (norm.coin_pub = dep.coin_pub"
-           "      AND norm.deposit_serial_id = dep.deposit_serial_id"
-           "      AND norm.norm_refund_val = (dep.amount).val"
-           "      AND norm.norm_refund_frac = (dep.amount).frac))"
+           "    JOIN cdep"
+           "      ON (norm.coin_pub = cdep.coin_pub"
+           "      AND norm.batch_deposit_serial_id = 
cdep.batch_deposit_serial_id"
+           "      AND norm.norm_refund_val = (cdep.amount).val"
+           "      AND norm.norm_refund_frac = (cdep.amount).frac))"
            " ,fees AS (" /* find deposit fees for not fully refunded deposits 
*/
            "  SELECT"
            "    denom.fee_deposit AS fee"
-           "   ,cs.deposit_serial_id" /* ensures we get the fee for each coin, 
not once per denomination */
-           "    FROM dep cs"
+           "   ,cs.batch_deposit_serial_id" /* ensures we get the fee for each 
coin, not once per denomination */
+           "    FROM cdep cs"
            "    JOIN known_coins kc" /* NOTE: may do a full join on the 
master, maybe find a left-join way to integrate with query above to push it to 
the shards? */
            "      USING (coin_pub)"
            "    JOIN denominations denom"
@@ -107,18 +114,18 @@ TEH_PG_aggregate (
            "    WHERE coin_pub NOT IN (SELECT coin_pub FROM 
fully_refunded_coins))"
            " ,dummy AS (" /* add deposits to aggregation_tracking */
            "    INSERT INTO aggregation_tracking"
-           "    (deposit_serial_id"
+           "    (batch_deposit_serial_id"
            "    ,wtid_raw)"
-           "    SELECT deposit_serial_id,$4"
-           "      FROM dep)"
+           "    SELECT batch_deposit_serial_id,$4"
+           "      FROM bdep)"
            "SELECT" /* calculate totals (deposits, refunds and fees) */
-           "  CAST(COALESCE(SUM((dep.amount).val),0) AS INT8) AS 
sum_deposit_value" /* cast needed, otherwise we get NUMBER */
-           " ,COALESCE(SUM((dep.amount).frac),0) AS sum_deposit_fraction" /* 
SUM over INT returns INT8 */
+           "  CAST(COALESCE(SUM((cdep.amount).val),0) AS INT8) AS 
sum_deposit_value" /* cast needed, otherwise we get NUMBER */
+           " ,COALESCE(SUM((cdep.amount).frac),0) AS sum_deposit_fraction" /* 
SUM over INT returns INT8 */
            " ,CAST(COALESCE(SUM((ref.refund).val),0) AS INT8) AS 
sum_refund_value"
            " ,COALESCE(SUM((ref.refund).frac),0) AS sum_refund_fraction"
            " ,CAST(COALESCE(SUM((fees.fee).val),0) AS INT8) AS sum_fee_value"
            " ,COALESCE(SUM((fees.fee).frac),0) AS sum_fee_fraction"
-           " FROM dep "
+           " FROM cdep "
            "   FULL OUTER JOIN ref ON (FALSE)"    /* We just want all sums */
            "   FULL OUTER JOIN fees ON (FALSE);");
 
diff --git a/src/exchangedb/pg_do_deposit.c b/src/exchangedb/pg_do_deposit.c
index ce09fb72..11720d7d 100644
--- a/src/exchangedb/pg_do_deposit.c
+++ b/src/exchangedb/pg_do_deposit.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -30,53 +30,78 @@
 enum GNUNET_DB_QueryStatus
 TEH_PG_do_deposit (
   void *cls,
-  const struct TALER_EXCHANGEDB_Deposit *deposit,
-  uint64_t known_coin_id,
-  const struct TALER_PaytoHashP *h_payto,
-  uint64_t *policy_details_serial_id,
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd,
   struct GNUNET_TIME_Timestamp *exchange_timestamp,
   bool *balance_ok,
-  bool *in_conflict)
+  uint32_t *bad_balance_index,
+  bool *ctr_conflict)
 {
   struct PostgresClosure *pg = cls;
-  uint64_t deposit_shard = TEH_PG_compute_shard (&deposit->merchant_pub);
+  uint64_t deposit_shard = TEH_PG_compute_shard (&bd->merchant_pub);
+  const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (bd->num_cdis)];
+  const struct TALER_CoinSpendSignatureP *coin_sigs[GNUNET_NZL (bd->num_cdis)];
+  struct TALER_Amount amounts_with_fee[GNUNET_NZL (bd->num_cdis)];
   struct GNUNET_PQ_QueryParam params[] = {
-    TALER_PQ_query_param_amount (pg->conn,
-                                 &deposit->amount_with_fee),
-    GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (&deposit->wire_salt),
-    GNUNET_PQ_query_param_timestamp (&deposit->timestamp),
-    GNUNET_PQ_query_param_timestamp (exchange_timestamp),
-    GNUNET_PQ_query_param_timestamp (&deposit->refund_deadline),
-    GNUNET_PQ_query_param_timestamp (&deposit->wire_deadline),
-    GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
-    GNUNET_PQ_query_param_string (deposit->receiver_wire_account),
-    GNUNET_PQ_query_param_auto_from_type (h_payto),
-    GNUNET_PQ_query_param_uint64 (&known_coin_id),
-    GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
-    GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
+    /* data for batch_deposits */
     GNUNET_PQ_query_param_uint64 (&deposit_shard),
-    GNUNET_PQ_query_param_bool (deposit->has_policy),
-    (NULL == policy_details_serial_id)
+    GNUNET_PQ_query_param_auto_from_type (&bd->merchant_pub),
+    GNUNET_PQ_query_param_timestamp (&bd->wallet_timestamp),
+    GNUNET_PQ_query_param_timestamp (exchange_timestamp),
+    GNUNET_PQ_query_param_timestamp (&bd->refund_deadline),
+    GNUNET_PQ_query_param_timestamp (&bd->wire_deadline),
+    GNUNET_PQ_query_param_auto_from_type (&bd->h_contract_terms),
+    (0 == bd->has_wallet_data_hash)
     ? GNUNET_PQ_query_param_null ()
-    : GNUNET_PQ_query_param_uint64 (policy_details_serial_id),
+    : GNUNET_PQ_query_param_auto_from_type (&bd->wallet_data_hash),
+    GNUNET_PQ_query_param_auto_from_type (&bd->wire_salt),
+    GNUNET_PQ_query_param_auto_from_type (&bd->wire_target_h_payto),
+    (0 == bd->policy_details_serial_id)
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_uint64 (&bd->policy_details_serial_id),
+    GNUNET_PQ_query_param_bool (bd->policy_blocked),
+    /* to create entry in wire_targets */
+    GNUNET_PQ_query_param_string (bd->receiver_wire_account),
+    /* arrays for coin_deposits */
+    GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+                                                     coin_pubs,
+                                                     pg->conn),
+    GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+                                                     coin_sigs,
+                                                     pg->conn),
+    TALER_PQ_query_param_array_amount (bd->num_cdis,
+                                       amounts_with_fee,
+                                       pg->conn),
     GNUNET_PQ_query_param_end
   };
+  bool no_time;
   struct GNUNET_PQ_ResultSpec rs[] = {
-    GNUNET_PQ_result_spec_bool ("balance_ok",
-                                balance_ok),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+                                       exchange_timestamp),
+      &no_time),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_uint32 ("insufficient_balance_coin_index",
+                                    bad_balance_index),
+      balance_ok),
     GNUNET_PQ_result_spec_bool ("conflicted",
-                                in_conflict),
-    GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
-                                     exchange_timestamp),
+                                ctr_conflict),
     GNUNET_PQ_result_spec_end
   };
 
+  for (unsigned int i = 0; i < bd->num_cdis; i++)
+  {
+    const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &bd->cdis[i];
+
+    amounts_with_fee[i] = cdi->amount_with_fee;
+    coin_pubs[i] = &cdi->coin.coin_pub;
+    coin_sigs[i] = &cdi->csig;
+  }
   PREPARE (pg,
            "call_deposit",
            "SELECT "
            " out_exchange_timestamp AS exchange_timestamp"
-           ",out_balance_ok AS balance_ok"
+           ",out_insufficient_balance_coin_index AS 
insufficient_balance_coin_index"
            ",out_conflict AS conflicted"
            " FROM exchange_do_deposit"
            " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);");
diff --git a/src/exchangedb/pg_do_deposit.h b/src/exchangedb/pg_do_deposit.h
index e71cf0e4..449ec04b 100644
--- a/src/exchangedb/pg_do_deposit.h
+++ b/src/exchangedb/pg_do_deposit.h
@@ -24,29 +24,28 @@
 #include "taler_util.h"
 #include "taler_json_lib.h"
 #include "taler_exchangedb_plugin.h"
+
+
 /**
  * Perform deposit operation, checking for sufficient balance
- * of the coin and possibly persisting the deposit details.
+ * of the coins and possibly persisting the deposit details.
  *
  * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param deposit deposit operation details
- * @param known_coin_id row of the coin in the known_coins table
- * @param h_payto hash of the merchant's bank account details
- * @param policy_details_serial_id pointer to the ID of the entry in 
policy_details, maybe NULL
+ * @param bd batch deposit operation details
  * @param[in,out] exchange_timestamp time to use for the deposit (possibly 
updated)
  * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] bad_balance_index set to the first index of a coin for which 
the balance was insufficient,
+ *             only used if @a balance_ok is set to false.
  * @param[out] in_conflict set to true if the deposit conflicted
  * @return query execution status
  */
 enum GNUNET_DB_QueryStatus
 TEH_PG_do_deposit (
   void *cls,
-  const struct TALER_EXCHANGEDB_Deposit *deposit,
-  uint64_t known_coin_id,
-  const struct TALER_PaytoHashP *h_payto,
-  uint64_t *policy_details_serial_id,
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd,
   struct GNUNET_TIME_Timestamp *exchange_timestamp,
   bool *balance_ok,
+  uint32_t *bad_balance_index,
   bool *in_conflict);
 
 #endif
diff --git a/src/exchangedb/pg_get_coin_transactions.c 
b/src/exchangedb/pg_get_coin_transactions.c
index 807c4d8f..c2f0f822 100644
--- a/src/exchangedb/pg_get_coin_transactions.c
+++ b/src/exchangedb/pg_get_coin_transactions.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -116,7 +116,7 @@ add_coin_deposit (void *cls,
                                       &deposit->receiver_wire_account),
         GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
                                               &deposit->csig),
-        GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
+        GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
                                       &serial_id),
         GNUNET_PQ_result_spec_auto_from_type ("done",
                                               &deposit->done),
@@ -726,28 +726,30 @@ TEH_PG_get_coin_transactions (
   PREPARE (pg,
            "get_deposit_with_coin_pub",
            "SELECT"
-           " dep.amount_with_fee"
+           " cdep.amount_with_fee"
            ",denoms.fee_deposit"
            ",denoms.denom_pub_hash"
            ",kc.age_commitment_hash"
-           ",dep.wallet_timestamp"
-           ",dep.refund_deadline"
-           ",dep.wire_deadline"
-           ",dep.merchant_pub"
-           ",dep.h_contract_terms"
-           ",dep.wire_salt"
+           ",bdep.wallet_timestamp"
+           ",bdep.refund_deadline"
+           ",bdep.wire_deadline"
+           ",bdep.merchant_pub"
+           ",bdep.h_contract_terms"
+           ",bdep.wire_salt"
            ",wt.payto_uri"
-           ",dep.coin_sig"
-           ",dep.deposit_serial_id"
-           ",dep.done"
-           " FROM deposits dep"
+           ",cdep.coin_sig"
+           ",cdep.coin_deposit_serial_id"
+           ",bdep.done"
+           " FROM coin_deposits cdep"
+           "    JOIN batch_deposits bdep"
+           "      USING (batch_deposit_serial_id)"
            "    JOIN wire_targets wt"
            "      USING (wire_target_h_payto)"
            "    JOIN known_coins kc"
-           "      ON (kc.coin_pub = dep.coin_pub)"
+           "      ON (kc.coin_pub = cdep.coin_pub)"
            "    JOIN denominations denoms"
            "      USING (denominations_serial)"
-           " WHERE dep.coin_pub=$1;");
+           " WHERE cdep.coin_pub=$1;");
   PREPARE (pg,
            "get_refresh_session_by_coin",
            "SELECT"
@@ -792,16 +794,18 @@ TEH_PG_get_coin_transactions (
   PREPARE (pg,
            "get_refunds_by_coin",
            "SELECT"
-           " dep.merchant_pub"
+           " bdep.merchant_pub"
            ",ref.merchant_sig"
-           ",dep.h_contract_terms"
+           ",bdep.h_contract_terms"
            ",ref.rtransaction_id"
            ",ref.amount_with_fee"
            ",denom.fee_refund"
            ",ref.refund_serial_id"
            " FROM refunds ref"
-           " JOIN deposits dep"
-           "   ON (ref.coin_pub = dep.coin_pub AND ref.deposit_serial_id = 
dep.deposit_serial_id)"
+           " JOIN coin_deposits cdep"
+           "   ON (ref.coin_pub = cdep.coin_pub AND 
ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
+           " JOIN batch_deposits bdep"
+           "   ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
            " JOIN known_coins kc"
            "   ON (ref.coin_pub = kc.coin_pub)"
            " JOIN denominations denom"
diff --git a/src/exchangedb/pg_get_ready_deposit.c 
b/src/exchangedb/pg_get_ready_deposit.c
index 91151c61..d8344faf 100644
--- a/src/exchangedb/pg_get_ready_deposit.c
+++ b/src/exchangedb/pg_get_ready_deposit.c
@@ -33,7 +33,6 @@ TEH_PG_get_ready_deposit (void *cls,
                           struct TALER_MerchantPublicKeyP *merchant_pub,
                           char **payto_uri)
 {
-  static int choose_mode = -2;
   struct PostgresClosure *pg = cls;
   struct GNUNET_TIME_Absolute now
     = GNUNET_TIME_absolute_get ();
@@ -50,76 +49,24 @@ TEH_PG_get_ready_deposit (void *cls,
                                   payto_uri),
     GNUNET_PQ_result_spec_end
   };
-  const char *query;
-
-  if (-2 == choose_mode)
-  {
-    const char *mode = getenv ("TALER_POSTGRES_GET_READY_LOGIC");
-    char dummy;
-
-    if ( (NULL==mode) ||
-         (1 != sscanf (mode,
-                       "%d%c",
-                       &choose_mode,
-                       &dummy)) )
-    {
-      if (NULL != mode)
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Bad mode `%s' specified\n",
-                    mode);
-      choose_mode = 0;
-    }
-  }
-  switch (choose_mode)
-  {
-  case 0:
-    query = "deposits_get_ready-v5";
-    PREPARE (pg,
-             query,
-             "SELECT"
-             " payto_uri"
-             ",merchant_pub"
-             " FROM deposits dep"
-             " JOIN wire_targets wt"
-             "   USING (wire_target_h_payto)"
-             " WHERE NOT (done OR policy_blocked)"
-             "   AND dep.wire_deadline<=$1"
-             "   AND dep.shard >= $2"
-             "   AND dep.shard <= $3"
-             " ORDER BY "
-             "   dep.wire_deadline ASC"
-             "  ,dep.shard ASC"
-             " LIMIT 1;");
-    break;
-  case 1:
-    query = "deposits_get_ready-v6";
-    PREPARE (pg,
-             query,
-             "WITH rc AS MATERIALIZED ("
-             " SELECT"
-             " merchant_pub"
-             ",wire_target_h_payto"
-             " FROM deposits"
-             " WHERE NOT (done OR policy_blocked)"
-             "   AND wire_deadline<=$1"
-             "   AND shard >= $2"
-             "   AND shard <= $3"
-             " ORDER BY wire_deadline ASC"
-             "  ,shard ASC"
-             "  LIMIT 1"
-             ")"
-             "SELECT"
-             " wt.payto_uri"
-             ",rc.merchant_pub"
-             " FROM wire_targets wt"
-             " JOIN rc"
-             "   USING (wire_target_h_payto);");
-    break;
-  default:
-    GNUNET_break (0);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
+  const char *query = "deposits_get_ready";
 
+  PREPARE (pg,
+           query,
+           "SELECT"
+           " wts.payto_uri"
+           ",bdep.merchant_pub"
+           " FROM batch_deposits bdep"
+           " JOIN wire_targets wts"
+           "   USING (wire_target_h_payto)"
+           " WHERE NOT (bdep.done OR bdep.policy_blocked)"
+           "   AND bdep.wire_deadline<=$1"
+           "   AND bdep.shard >= $2"
+           "   AND bdep.shard <= $3"
+           " ORDER BY "
+           "   bdep.wire_deadline ASC"
+           "  ,bdep.shard ASC"
+           " LIMIT 1;");
   return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                    query,
                                                    params,
diff --git a/src/exchangedb/pg_have_deposit2.c 
b/src/exchangedb/pg_have_deposit2.c
index ccb4f5c9..e00ad749 100644
--- a/src/exchangedb/pg_have_deposit2.c
+++ b/src/exchangedb/pg_have_deposit2.c
@@ -73,23 +73,25 @@ TEH_PG_have_deposit2 (
   PREPARE (pg,
            "get_deposit",
            "SELECT"
-           " dep.amount_with_fee"
+           " cdep.amount_with_fee"
            ",denominations.fee_deposit"
-           ",dep.wallet_timestamp"
-           ",dep.exchange_timestamp"
-           ",dep.refund_deadline"
-           ",dep.wire_deadline"
-           ",dep.h_contract_terms"
-           ",dep.wire_salt"
+           ",bdep.wallet_timestamp"
+           ",bdep.exchange_timestamp"
+           ",bdep.refund_deadline"
+           ",bdep.wire_deadline"
+           ",bdep.h_contract_terms"
+           ",bdep.wire_salt"
            ",wt.payto_uri AS receiver_wire_account"
-           " FROM deposits dep"
-           " JOIN known_coins kc ON (kc.coin_pub = dep.coin_pub)"
+           " FROM coin_deposits cdep"
+           " JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
+           " JOIN known_coins kc ON (kc.coin_pub = cdep.coin_pub)"
            " JOIN denominations USING (denominations_serial)"
            " JOIN wire_targets wt USING (wire_target_h_payto)"
-           " WHERE dep.coin_pub=$1"
-           "   AND dep.merchant_pub=$3"
-           "   AND dep.h_contract_terms=$2;");
-
+           " WHERE cdep.coin_pub=$1"
+           "   AND bdep.merchant_pub=$3"
+           "   AND bdep.h_contract_terms=$2;");
+  /* Note: query might be made more efficient if we computed the 'shard'
+     from merchant_pub and included that as a constraint on bdep! */
   qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                  "get_deposit",
                                                  params,
diff --git a/src/exchangedb/pg_insert_aggregation_tracking.c 
b/src/exchangedb/pg_insert_aggregation_tracking.c
deleted file mode 100644
index fe61b841..00000000
--- a/src/exchangedb/pg_insert_aggregation_tracking.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   TALER is free software; you can redistribute it and/or modify it under the
-   terms of the GNU General Public License as published by the Free Software
-   Foundation; either version 3, or (at your option) any later version.
-
-   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along with
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_insert_aggregation_tracking.c
- * @brief Implementation of the insert_aggregation_tracking function for 
Postgres
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_error_codes.h"
-#include "taler_dbevents.h"
-#include "taler_pq_lib.h"
-#include "pg_insert_aggregation_tracking.h"
-#include "pg_helper.h"
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_insert_aggregation_tracking (
-  void *cls,
-  const struct TALER_WireTransferIdentifierRawP *wtid,
-  unsigned long long deposit_serial_id)
-{
-  struct PostgresClosure *pg = cls;
-  uint64_t rid = deposit_serial_id;
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_uint64 (&rid),
-    GNUNET_PQ_query_param_auto_from_type (wtid),
-    GNUNET_PQ_query_param_end
-  };
-
-  PREPARE (pg,
-           "insert_aggregation_tracking",
-           "INSERT INTO aggregation_tracking "
-           "(deposit_serial_id"
-           ",wtid_raw"
-           ") VALUES "
-           "($1, $2);");
-  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_aggregation_tracking",
-                                             params);
-}
diff --git a/src/exchangedb/pg_insert_aggregation_tracking.h 
b/src/exchangedb/pg_insert_aggregation_tracking.h
deleted file mode 100644
index e67c0e8e..00000000
--- a/src/exchangedb/pg_insert_aggregation_tracking.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   TALER is free software; you can redistribute it and/or modify it under the
-   terms of the GNU General Public License as published by the Free Software
-   Foundation; either version 3, or (at your option) any later version.
-
-   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along with
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_insert_aggregation_tracking.h
- * @brief implementation of the insert_aggregation_tracking function for 
Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_INSERT_AGGREGATION_TRACKING_H
-#define PG_INSERT_AGGREGATION_TRACKING_H
-
-#include "taler_util.h"
-#include "taler_json_lib.h"
-#include "taler_exchangedb_plugin.h"
-
-/**
- * Function called to insert aggregation information into the DB.
- *
- * @param cls closure
- * @param wtid the raw wire transfer identifier we used
- * @param deposit_serial_id row in the deposits table for which this is 
aggregation data
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-TEH_PG_insert_aggregation_tracking (
-  void *cls,
-  const struct TALER_WireTransferIdentifierRawP *wtid,
-  unsigned long long deposit_serial_id);
-
-#endif
diff --git a/src/exchangedb/pg_insert_deposit.c 
b/src/exchangedb/pg_insert_deposit.c
deleted file mode 100644
index a5535525..00000000
--- a/src/exchangedb/pg_insert_deposit.c
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   TALER is free software; you can redistribute it and/or modify it under the
-   terms of the GNU General Public License as published by the Free Software
-   Foundation; either version 3, or (at your option) any later version.
-
-   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along with
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_insert_deposit.c
- * @brief Implementation of the insert_deposit function for Postgres
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_error_codes.h"
-#include "taler_dbevents.h"
-#include "taler_pq_lib.h"
-#include "pg_insert_deposit.h"
-#include "pg_helper.h"
-#include "pg_setup_wire_target.h"
-#include "pg_compute_shard.h"
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_insert_deposit (void *cls,
-                       struct GNUNET_TIME_Timestamp exchange_timestamp,
-                       const struct TALER_EXCHANGEDB_Deposit *deposit)
-{
-  struct PostgresClosure *pg = cls;
-  struct TALER_PaytoHashP h_payto;
-  enum GNUNET_DB_QueryStatus qs;
-
-  qs = TEH_PG_setup_wire_target (pg,
-                                 deposit->receiver_wire_account,
-                                 &h_payto);
-  if (qs < 0)
-    return qs;
-  if (GNUNET_TIME_timestamp_cmp (deposit->wire_deadline,
-                                 <,
-                                 deposit->refund_deadline))
-  {
-    GNUNET_break (0);
-  }
-  {
-    uint64_t shard = TEH_PG_compute_shard (&deposit->merchant_pub);
-    struct GNUNET_PQ_QueryParam params[] = {
-      GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
-      TALER_PQ_query_param_amount (pg->conn,
-                                   &deposit->amount_with_fee),
-      GNUNET_PQ_query_param_timestamp (&deposit->timestamp),
-      GNUNET_PQ_query_param_timestamp (&deposit->refund_deadline),
-      GNUNET_PQ_query_param_timestamp (&deposit->wire_deadline),
-      GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
-      GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
-      GNUNET_PQ_query_param_auto_from_type (&deposit->wire_salt),
-      GNUNET_PQ_query_param_auto_from_type (&h_payto),
-      GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
-      GNUNET_PQ_query_param_timestamp (&exchange_timestamp),
-      GNUNET_PQ_query_param_uint64 (&shard),
-      GNUNET_PQ_query_param_end
-    };
-
-    GNUNET_assert (shard <= INT32_MAX);
-    GNUNET_log (
-      GNUNET_ERROR_TYPE_INFO,
-      "Inserting deposit to be executed at %s (%llu/%llu)\n",
-      GNUNET_TIME_timestamp2s (deposit->wire_deadline),
-      (unsigned long long) deposit->wire_deadline.abs_time.abs_value_us,
-      (unsigned long long) deposit->refund_deadline.abs_time.abs_value_us);
-
-    PREPARE (pg,
-             "insert_deposit",
-             "INSERT INTO deposits "
-             "(known_coin_id"
-             ",coin_pub"
-             ",amount_with_fee"
-             ",wallet_timestamp"
-             ",refund_deadline"
-             ",wire_deadline"
-             ",merchant_pub"
-             ",h_contract_terms"
-             ",wire_salt"
-             ",wire_target_h_payto"
-             ",coin_sig"
-             ",exchange_timestamp"
-             ",shard"
-             ") SELECT known_coin_id, $1, $2, $3, $4, $5, $6, "
-             " $7, $8, $9, $10, $11, $12"
-             "    FROM known_coins"
-             "   WHERE coin_pub=$1"
-             " ON CONFLICT DO NOTHING;");
-    return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                               "insert_deposit",
-                                               params);
-  }
-}
diff --git a/src/exchangedb/pg_insert_deposit.h 
b/src/exchangedb/pg_insert_deposit.h
deleted file mode 100644
index 82cbcd54..00000000
--- a/src/exchangedb/pg_insert_deposit.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-   This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
-
-   TALER is free software; you can redistribute it and/or modify it under the
-   terms of the GNU General Public License as published by the Free Software
-   Foundation; either version 3, or (at your option) any later version.
-
-   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
-   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
-   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License along with
-   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-/**
- * @file exchangedb/pg_insert_deposit.h
- * @brief implementation of the insert_deposit function for Postgres
- * @author Christian Grothoff
- */
-#ifndef PG_INSERT_DEPOSIT_H
-#define PG_INSERT_DEPOSIT_H
-
-#include "taler_util.h"
-#include "taler_json_lib.h"
-#include "taler_exchangedb_plugin.h"
-/**
- * Insert information about deposited coin into the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param exchange_timestamp time the exchange received the deposit request
- * @param deposit deposit information to store
- * @return query result status
- */
-enum GNUNET_DB_QueryStatus
-TEH_PG_insert_deposit (void *cls,
-                       struct GNUNET_TIME_Timestamp exchange_timestamp,
-                       const struct TALER_EXCHANGEDB_Deposit *deposit);
-
-#endif
diff --git a/src/exchangedb/pg_insert_records_by_table.c 
b/src/exchangedb/pg_insert_records_by_table.c
index f7994e53..6ecec5bc 100644
--- a/src/exchangedb/pg_insert_records_by_table.c
+++ b/src/exchangedb/pg_insert_records_by_table.c
@@ -855,68 +855,106 @@ irbt_cb_table_refresh_transfer_keys (
 
 
 /**
- * Function called with deposits records to insert into table.
+ * Function called with batch deposits records to insert into table.
  *
  * @param pg plugin context
  * @param td record to insert
  */
 static enum GNUNET_DB_QueryStatus
-irbt_cb_table_deposits (struct PostgresClosure *pg,
-                        const struct TALER_EXCHANGEDB_TableData *td)
+irbt_cb_table_batch_deposits (struct PostgresClosure *pg,
+                              const struct TALER_EXCHANGEDB_TableData *td)
 {
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_uint64 (&td->serial),
-    GNUNET_PQ_query_param_uint64 (&td->details.deposits.shard),
-    GNUNET_PQ_query_param_uint64 (&td->details.deposits.known_coin_id),
+    GNUNET_PQ_query_param_uint64 (&td->details.batch_deposits.shard),
     GNUNET_PQ_query_param_auto_from_type (
-      &td->details.deposits.coin_pub),
-    TALER_PQ_query_param_amount (
-      pg->conn,
-      &td->details.deposits.amount_with_fee),
-    GNUNET_PQ_query_param_timestamp (&td->details.deposits.wallet_timestamp),
+      &td->details.batch_deposits.merchant_pub),
+    GNUNET_PQ_query_param_timestamp (
+      &td->details.batch_deposits.wallet_timestamp),
+    GNUNET_PQ_query_param_timestamp (
+      &td->details.batch_deposits.exchange_timestamp),
     GNUNET_PQ_query_param_timestamp (
-      &td->details.deposits.exchange_timestamp),
-    GNUNET_PQ_query_param_timestamp (&td->details.deposits.refund_deadline),
-    GNUNET_PQ_query_param_timestamp (&td->details.deposits.wire_deadline),
-    GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.merchant_pub),
-    GNUNET_PQ_query_param_auto_from_type (
-      &td->details.deposits.h_contract_terms),
-    GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.coin_sig),
-    GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.wire_salt),
-    GNUNET_PQ_query_param_auto_from_type (
-      &td->details.deposits.wire_target_h_payto),
-    GNUNET_PQ_query_param_bool (td->details.deposits.policy_blocked),
-    0 == td->details.deposits.policy_details_serial_id
+      &td->details.batch_deposits.refund_deadline),
+    GNUNET_PQ_query_param_timestamp 
(&td->details.batch_deposits.wire_deadline),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.batch_deposits.h_contract_terms),
+    td->details.batch_deposits.no_wallet_data_hash
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (
+      &td->details.batch_deposits.wallet_data_hash),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.batch_deposits.wire_salt),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.batch_deposits.wire_target_h_payto),
+    GNUNET_PQ_query_param_bool (td->details.batch_deposits.policy_blocked),
+    td->details.batch_deposits.no_policy_details
     ? GNUNET_PQ_query_param_null ()
     : GNUNET_PQ_query_param_uint64 (
-      &td->details.deposits.policy_details_serial_id),
+      &td->details.batch_deposits.policy_details_serial_id),
     GNUNET_PQ_query_param_end
   };
 
   PREPARE (pg,
-           "insert_into_table_deposits",
-           "INSERT INTO deposits"
-           "(deposit_serial_id"
+           "insert_into_table_batch_deposits",
+           "INSERT INTO batch_deposits"
+           "(batch_deposit_serial_id"
            ",shard"
-           ",known_coin_id"
-           ",coin_pub"
-           ",amount_with_fee"
+           ",merchant_pub"
            ",wallet_timestamp"
            ",exchange_timestamp"
            ",refund_deadline"
            ",wire_deadline"
-           ",merchant_pub"
            ",h_contract_terms"
-           ",coin_sig"
+           ",wallet_data_hash"
            ",wire_salt"
            ",wire_target_h_payto"
-           ",policy_blocked"
            ",policy_details_serial_id"
+           ",policy_blocked"
            ") VALUES "
            "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
-           " $11, $12, $13, $14, $15, $16);");
+           " $11, $12, $13);");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "insert_into_table_deposits",
+                                             
"insert_into_table_batch_deposits",
+                                             params);
+}
+
+
+/**
+ * Function called with deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_coin_deposits (struct PostgresClosure *pg,
+                             const struct TALER_EXCHANGEDB_TableData *td)
+{
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_uint64 (&td->serial),
+    GNUNET_PQ_query_param_uint64 (
+      &td->details.coin_deposits.batch_deposit_serial_id),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.coin_deposits.coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (
+      &td->details.coin_deposits.coin_sig),
+    TALER_PQ_query_param_amount (
+      pg->conn,
+      &td->details.coin_deposits.amount_with_fee),
+    GNUNET_PQ_query_param_end
+  };
+
+  PREPARE (pg,
+           "insert_into_table_coin_deposits",
+           "INSERT INTO coin_deposits"
+           "(coin_deposit_serial_id"
+           ",batch_deposit_serial_id"
+           ",coin_pub"
+           ",coin_sig"
+           ",amount_with_fee"
+           ") VALUES "
+           "($1, $2, $3, $4, $5);");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_into_table_coin_deposits",
                                              params);
 }
 
@@ -939,7 +977,8 @@ irbt_cb_table_refunds (struct PostgresClosure *pg,
     TALER_PQ_query_param_amount (
       pg->conn,
       &td->details.refunds.amount_with_fee),
-    GNUNET_PQ_query_param_uint64 (&td->details.refunds.deposit_serial_id),
+    GNUNET_PQ_query_param_uint64 (
+      &td->details.refunds.batch_deposit_serial_id),
     GNUNET_PQ_query_param_end
   };
 
@@ -951,7 +990,7 @@ irbt_cb_table_refunds (struct PostgresClosure *pg,
            ",merchant_sig"
            ",rtransaction_id"
            ",amount_with_fee"
-           ",deposit_serial_id"
+           ",batch_deposit_serial_id"
            ") VALUES "
            "($1, $2, $3, $4, $5, $6);");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
@@ -1014,7 +1053,7 @@ irbt_cb_table_aggregation_tracking (struct 
PostgresClosure *pg,
   struct GNUNET_PQ_QueryParam params[] = {
     GNUNET_PQ_query_param_uint64 (&td->serial),
     GNUNET_PQ_query_param_uint64 (
-      &td->details.aggregation_tracking.deposit_serial_id),
+      &td->details.aggregation_tracking.batch_deposit_serial_id),
     GNUNET_PQ_query_param_auto_from_type (
       &td->details.aggregation_tracking.wtid_raw),
     GNUNET_PQ_query_param_end
@@ -1024,7 +1063,7 @@ irbt_cb_table_aggregation_tracking (struct 
PostgresClosure *pg,
            "insert_into_table_aggregation_tracking",
            "INSERT INTO aggregation_tracking"
            "(aggregation_serial_id"
-           ",deposit_serial_id"
+           ",batch_deposit_serial_id"
            ",wtid_raw"
            ") VALUES "
            "($1, $2, $3);");
@@ -2194,8 +2233,11 @@ TEH_PG_insert_records_by_table (void *cls,
   case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
     rh = &irbt_cb_table_refresh_transfer_keys;
     break;
-  case TALER_EXCHANGEDB_RT_DEPOSITS:
-    rh = &irbt_cb_table_deposits;
+  case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+    rh = &irbt_cb_table_batch_deposits;
+    break;
+  case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+    rh = &irbt_cb_table_coin_deposits;
     break;
   case TALER_EXCHANGEDB_RT_REFUNDS:
     rh = &irbt_cb_table_refunds;
diff --git a/src/exchangedb/pg_insert_refund.c 
b/src/exchangedb/pg_insert_refund.c
index 05f8a25b..e989c91b 100644
--- a/src/exchangedb/pg_insert_refund.c
+++ b/src/exchangedb/pg_insert_refund.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -49,12 +49,13 @@ TEH_PG_insert_refund (void *cls,
            "insert_refund",
            "INSERT INTO refunds "
            "(coin_pub"
-           ",deposit_serial_id"
+           ",batch_deposit_serial_id"
            ",merchant_sig"
            ",rtransaction_id"
            ",amount_with_fee"
-           ") SELECT $1, deposit_serial_id, $3, $5, $6"
-           "    FROM deposits"
+           ") SELECT $1, cdep.batch_deposit_serial_id, $3, $5, $6"
+           "    FROM coin_deposits cdep"
+           "    JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
            "   WHERE coin_pub=$1"
            "     AND h_contract_terms=$4"
            "     AND merchant_pub=$2");
diff --git a/src/exchangedb/pg_lookup_records_by_table.c 
b/src/exchangedb/pg_lookup_records_by_table.c
index 92378c3f..b1656aaf 100644
--- a/src/exchangedb/pg_lookup_records_by_table.c
+++ b/src/exchangedb/pg_lookup_records_by_table.c
@@ -1135,77 +1135,125 @@ lrbt_cb_table_refresh_transfer_keys (void *cls,
 
 
 /**
- * Function called with deposits table entries.
+ * Function called with batch deposits table entries.
  *
  * @param cls closure
  * @param result the postgres result
  * @param num_results the number of results in @a result
  */
 static void
-lrbt_cb_table_deposits (void *cls,
-                        PGresult *result,
-                        unsigned int num_results)
+lrbt_cb_table_batch_deposits (void *cls,
+                              PGresult *result,
+                              unsigned int num_results)
 {
   struct LookupRecordsByTableContext *ctx = cls;
   struct PostgresClosure *pg = ctx->pg;
   struct TALER_EXCHANGEDB_TableData td = {
-    .table = TALER_EXCHANGEDB_RT_DEPOSITS
+    .table = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS
   };
 
   for (unsigned int i = 0; i<num_results; i++)
   {
-    bool no_policy;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_uint64 (
         "serial",
         &td.serial),
       GNUNET_PQ_result_spec_uint64 (
         "shard",
-        &td.details.deposits.shard),
-      GNUNET_PQ_result_spec_uint64 (
-        "known_coin_id",
-        &td.details.deposits.known_coin_id),
+        &td.details.batch_deposits.shard),
       GNUNET_PQ_result_spec_auto_from_type (
-        "coin_pub",
-        &td.details.deposits.coin_pub),
-      TALER_PQ_RESULT_SPEC_AMOUNT (
-        "amount_with_fee",
-        &td.details.deposits.amount_with_fee),
+        "merchant_pub",
+        &td.details.batch_deposits.merchant_pub),
       GNUNET_PQ_result_spec_timestamp (
         "wallet_timestamp",
-        &td.details.deposits.wallet_timestamp),
+        &td.details.batch_deposits.wallet_timestamp),
       GNUNET_PQ_result_spec_timestamp (
         "exchange_timestamp",
-        &td.details.deposits.exchange_timestamp),
+        &td.details.batch_deposits.exchange_timestamp),
       GNUNET_PQ_result_spec_timestamp (
         "refund_deadline",
-        &td.details.deposits.refund_deadline),
+        &td.details.batch_deposits.refund_deadline),
       GNUNET_PQ_result_spec_timestamp (
         "wire_deadline",
-        &td.details.deposits.wire_deadline),
-      GNUNET_PQ_result_spec_auto_from_type (
-        "merchant_pub",
-        &td.details.deposits.merchant_pub),
+        &td.details.batch_deposits.wire_deadline),
       GNUNET_PQ_result_spec_auto_from_type (
         "h_contract_terms",
-        &td.details.deposits.h_contract_terms),
-      GNUNET_PQ_result_spec_auto_from_type (
-        "coin_sig",
-        &td.details.deposits.coin_sig),
+        &td.details.batch_deposits.h_contract_terms),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_auto_from_type (
+          "wallet_data_hash",
+          &td.details.batch_deposits.wallet_data_hash),
+        &td.details.batch_deposits.no_wallet_data_hash),
       GNUNET_PQ_result_spec_auto_from_type (
         "wire_salt",
-        &td.details.deposits.wire_salt),
+        &td.details.batch_deposits.wire_salt),
       GNUNET_PQ_result_spec_auto_from_type (
         "wire_target_h_payto",
-        &td.details.deposits.wire_target_h_payto),
+        &td.details.batch_deposits.wire_target_h_payto),
       GNUNET_PQ_result_spec_auto_from_type (
         "policy_blocked",
-        &td.details.deposits.policy_blocked),
+        &td.details.batch_deposits.policy_blocked),
       GNUNET_PQ_result_spec_allow_null (
         GNUNET_PQ_result_spec_uint64 (
           "policy_details_serial_id",
-          &td.details.deposits.policy_details_serial_id),
-        &no_policy),
+          &td.details.batch_deposits.policy_details_serial_id),
+        &td.details.batch_deposits.no_policy_details),
+      GNUNET_PQ_result_spec_end
+    };
+
+    td.details.batch_deposits.policy_details_serial_id = 0;
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ctx->error = true;
+      return;
+    }
+    ctx->cb (ctx->cb_cls,
+             &td);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+/**
+ * Function called with coin deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_coin_deposits (void *cls,
+                             PGresult *result,
+                             unsigned int num_results)
+{
+  struct LookupRecordsByTableContext *ctx = cls;
+  struct PostgresClosure *pg = ctx->pg;
+  struct TALER_EXCHANGEDB_TableData td = {
+    .table = TALER_EXCHANGEDB_RT_COIN_DEPOSITS
+  };
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 (
+        "serial",
+        &td.serial),
+      GNUNET_PQ_result_spec_uint64 (
+        "batch_deposit_serial_id",
+        &td.details.coin_deposits.batch_deposit_serial_id),
+      GNUNET_PQ_result_spec_auto_from_type (
+        "coin_pub",
+        &td.details.coin_deposits.coin_pub),
+      GNUNET_PQ_result_spec_auto_from_type (
+        "coin_sig",
+        &td.details.coin_deposits.coin_sig),
+      TALER_PQ_RESULT_SPEC_AMOUNT (
+        "amount_with_fee",
+        &td.details.coin_deposits.amount_with_fee),
       GNUNET_PQ_result_spec_end
     };
 
@@ -1262,8 +1310,8 @@ lrbt_cb_table_refunds (void *cls,
         "amount_with_fee",
         &td.details.refunds.amount_with_fee),
       GNUNET_PQ_result_spec_uint64 (
-        "deposit_serial_id",
-        &td.details.refunds.deposit_serial_id),
+        "batch_deposit_serial_id",
+        &td.details.refunds.batch_deposit_serial_id),
       GNUNET_PQ_result_spec_end
     };
 
@@ -1364,8 +1412,8 @@ lrbt_cb_table_aggregation_tracking (void *cls,
         "serial",
         &td.serial),
       GNUNET_PQ_result_spec_uint64 (
-        "deposit_serial_id",
-        &td.details.aggregation_tracking.deposit_serial_id),
+        "batch_deposit_serial_id",
+        &td.details.aggregation_tracking.batch_deposit_serial_id),
       GNUNET_PQ_result_spec_auto_from_type (
         "wtid_raw",
         &td.details.aggregation_tracking.wtid_raw),
@@ -3124,30 +3172,40 @@ TEH_PG_lookup_records_by_table (void *cls,
               " ORDER BY rtc_serial ASC;");
     rh = &lrbt_cb_table_refresh_transfer_keys;
     break;
-  case TALER_EXCHANGEDB_RT_DEPOSITS:
-    XPREPARE ("select_above_serial_by_table_deposits",
+  case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+    XPREPARE ("select_above_serial_by_table_batch_deposits",
               "SELECT"
-              " deposit_serial_id AS serial"
+              " batch_deposit_serial_id AS serial"
               ",shard"
-              ",coin_pub"
-              ",known_coin_id"
-              ",amount_with_fee"
+              ",merchant_pub"
               ",wallet_timestamp"
               ",exchange_timestamp"
               ",refund_deadline"
               ",wire_deadline"
-              ",merchant_pub"
               ",h_contract_terms"
-              ",coin_sig"
+              ",wallet_data_hash"
               ",wire_salt"
               ",wire_target_h_payto"
               ",done"
               ",policy_blocked"
               ",policy_details_serial_id"
-              " FROM deposits"
-              " WHERE deposit_serial_id > $1"
-              " ORDER BY deposit_serial_id ASC;");
-    rh = &lrbt_cb_table_deposits;
+              " FROM batch_deposits"
+              " WHERE batch_deposit_serial_id > $1"
+              " ORDER BY batch_deposit_serial_id ASC;");
+    rh = &lrbt_cb_table_batch_deposits;
+    break;
+  case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+    XPREPARE ("select_above_serial_by_table_coin_deposits",
+              "SELECT"
+              " coin_deposit_serial_id AS serial"
+              ",batch_deposit_serial_id"
+              ",coin_pub"
+              ",coin_sig"
+              ",amount_with_fee"
+              " FROM coin_deposits"
+              " WHERE coin_deposit_serial_id > $1"
+              " ORDER BY coin_deposit_serial_id ASC;");
+    rh = &lrbt_cb_table_coin_deposits;
     break;
   case TALER_EXCHANGEDB_RT_REFUNDS:
     XPREPARE ("select_above_serial_by_table_refunds",
@@ -3157,7 +3215,7 @@ TEH_PG_lookup_records_by_table (void *cls,
               ",merchant_sig"
               ",rtransaction_id"
               ",amount_with_fee"
-              ",deposit_serial_id"
+              ",batch_deposit_serial_id"
               " FROM refunds"
               " WHERE refund_serial_id > $1"
               " ORDER BY refund_serial_id ASC;");
@@ -3181,7 +3239,7 @@ TEH_PG_lookup_records_by_table (void *cls,
     XPREPARE ("select_above_serial_by_table_aggregation_tracking",
               "SELECT"
               " aggregation_serial_id AS serial"
-              ",deposit_serial_id"
+              ",batch_deposit_serial_id"
               ",wtid_raw"
               " FROM aggregation_tracking"
               " WHERE aggregation_serial_id > $1"
diff --git a/src/exchangedb/pg_lookup_serial_by_table.c 
b/src/exchangedb/pg_lookup_serial_by_table.c
index 0bf0b971..9fda7ddf 100644
--- a/src/exchangedb/pg_lookup_serial_by_table.c
+++ b/src/exchangedb/pg_lookup_serial_by_table.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -205,12 +205,20 @@ TEH_PG_lookup_serial_by_table (void *cls,
               " ORDER BY rtc_serial DESC"
               " LIMIT 1;");
     break;
-  case TALER_EXCHANGEDB_RT_DEPOSITS:
-    XPREPARE ("select_serial_by_table_deposits",
+  case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+    XPREPARE ("select_serial_by_table_batch_deposits",
               "SELECT"
-              " deposit_serial_id AS serial"
-              " FROM deposits"
-              " ORDER BY deposit_serial_id DESC"
+              " batch_deposit_serial_id AS serial"
+              " FROM batch_deposits"
+              " ORDER BY batch_deposit_serial_id DESC"
+              " LIMIT 1;");
+    break;
+  case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+    XPREPARE ("select_serial_by_table_coin_deposits",
+              "SELECT"
+              " coin_deposit_serial_id AS serial"
+              " FROM coin_deposits"
+              " ORDER BY coin_deposit_serial_id DESC"
               " LIMIT 1;");
     break;
   case TALER_EXCHANGEDB_RT_REFUNDS:
diff --git a/src/exchangedb/pg_lookup_transfer_by_deposit.c 
b/src/exchangedb/pg_lookup_transfer_by_deposit.c
index 5a304358..425a11d3 100644
--- a/src/exchangedb/pg_lookup_transfer_by_deposit.c
+++ b/src/exchangedb/pg_lookup_transfer_by_deposit.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -74,27 +74,30 @@ TEH_PG_lookup_transfer_by_deposit (
   PREPARE (pg,
            "lookup_deposit_wtid",
            "SELECT"
-           " aggregation_tracking.wtid_raw"
+           " atr.wtid_raw"
            ",wire_out.execution_date"
-           ",dep.amount_with_fee"
-           ",dep.wire_salt"
+           ",cdep.amount_with_fee"
+           ",bdep.wire_salt"
            ",wt.payto_uri"
            ",denom.fee_deposit"
-           " FROM deposits dep"
+           " FROM coin_deposits cdep"
+           "    JOIN batch_deposits bdep"
+           "      USING (batch_deposit_serial_id)"
            "    JOIN wire_targets wt"
            "      USING (wire_target_h_payto)"
-           "    JOIN aggregation_tracking"
-           "      USING (deposit_serial_id)"
+           "    JOIN aggregation_tracking atr"
+           "      ON (cdep.batch_deposit_serial_id = 
atr.batch_deposit_serial_id)"
            "    JOIN known_coins kc"
-           "      ON (kc.coin_pub = dep.coin_pub)"
+           "      ON (kc.coin_pub = cdep.coin_pub)"
            "    JOIN denominations denom"
            "      USING (denominations_serial)"
            "    JOIN wire_out"
            "      USING (wtid_raw)"
-           " WHERE dep.coin_pub=$1"
-           "   AND dep.merchant_pub=$3"
-           "   AND dep.h_contract_terms=$2");
-
+           " WHERE cdep.coin_pub=$1"
+           "   AND bdep.merchant_pub=$3"
+           "   AND bdep.h_contract_terms=$2");
+  /* NOTE: above query might be more efficient if we computed the shard
+     from the merchant_pub and included that in the query */
   qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                  "lookup_deposit_wtid",
                                                  params,
@@ -163,29 +166,33 @@ TEH_PG_lookup_transfer_by_deposit (
              "get_deposit_without_wtid",
              "SELECT"
              " agt.legitimization_requirement_serial_id"
-             ",dep.wire_salt"
+             ",bdep.wire_salt"
              ",wt.payto_uri"
-             ",dep.amount_with_fee"
+             ",cdep.amount_with_fee"
              ",denom.fee_deposit"
-             ",dep.wire_deadline"
+             ",bdep.wire_deadline"
              ",aml.status"
              ",aml.kyc_requirement"
-             " FROM deposits dep"
+             " FROM coin_deposits cdep"
+             " JOIN batch_deposits bdep"
+             "   USING (batch_deposit_serial_id)"
              " JOIN wire_targets wt"
              "   USING (wire_target_h_payto)"
              " JOIN known_coins kc"
-             "   ON (kc.coin_pub = dep.coin_pub)"
+             "   ON (kc.coin_pub = cdep.coin_pub)"
              " JOIN denominations denom"
              "   USING (denominations_serial)"
              " LEFT JOIN aggregation_transient agt "
-             "   ON ( (dep.wire_target_h_payto = agt.wire_target_h_payto) AND"
-             "        (dep.merchant_pub = agt.merchant_pub) )"
+             "   ON ( (bdep.wire_target_h_payto = agt.wire_target_h_payto) AND"
+             "        (bdep.merchant_pub = agt.merchant_pub) )"
              " LEFT JOIN aml_status aml"
              "   ON (wt.wire_target_h_payto = aml.h_payto)"
-             " WHERE dep.coin_pub=$1"
-             "   AND dep.merchant_pub=$3"
-             "   AND dep.h_contract_terms=$2"
+             " WHERE cdep.coin_pub=$1"
+             "   AND bdep.merchant_pub=$3"
+             "   AND bdep.h_contract_terms=$2"
              " LIMIT 1;");
+    /* NOTE: above query might be more efficient if we computed the shard
+       from the merchant_pub and included that in the query */
     qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                    "get_deposit_without_wtid",
                                                    params,
diff --git a/src/exchangedb/pg_lookup_wire_transfer.c 
b/src/exchangedb/pg_lookup_wire_transfer.c
index 500ec289..7ab023fe 100644
--- a/src/exchangedb/pg_lookup_wire_transfer.c
+++ b/src/exchangedb/pg_lookup_wire_transfer.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -82,7 +82,8 @@ handle_wt_result (void *cls,
     struct TALER_DenominationPublicKey denom_pub;
     char *payto_uri;
     struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id", &rowid),
+      GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id",
+                                    &rowid),
       GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
                                             &h_contract_terms),
       GNUNET_PQ_result_spec_string ("payto_uri",
@@ -153,18 +154,20 @@ TEH_PG_lookup_wire_transfer (
            "lookup_transactions",
            "SELECT"
            " aggregation_serial_id"
-           ",deposits.h_contract_terms"
+           ",bdep.h_contract_terms"
            ",payto_uri"
            ",wire_targets.wire_target_h_payto"
            ",kc.coin_pub"
-           ",deposits.merchant_pub"
+           ",bdep.merchant_pub"
            ",wire_out.execution_date"
-           ",deposits.amount_with_fee"
+           ",cdep.amount_with_fee"
            ",denom.fee_deposit"
            ",denom.denom_pub"
            " FROM aggregation_tracking"
-           "    JOIN deposits"
-           "      USING (deposit_serial_id)"
+           "    JOIN batch_deposits bdep"
+           "      USING (batch_deposit_serial_id)"
+           "    JOIN coin_deposits cdep"
+           "      USING (batch_deposit_serial_id)"
            "    JOIN wire_targets"
            "      USING (wire_target_h_payto)"
            "    JOIN known_coins kc"
diff --git a/src/exchangedb/pg_persist_policy_details.h 
b/src/exchangedb/pg_persist_policy_details.h
index ed9fd95d..4fe709d9 100644
--- a/src/exchangedb/pg_persist_policy_details.h
+++ b/src/exchangedb/pg_persist_policy_details.h
@@ -24,6 +24,8 @@
 #include "taler_util.h"
 #include "taler_json_lib.h"
 #include "taler_exchangedb_plugin.h"
+
+
 /* Persist the details to a policy in the policy_details table.  If there
  * already exists a policy, update the fields accordingly.
  *
diff --git a/src/exchangedb/pg_select_deposits_missing_wire.c 
b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
similarity index 74%
rename from src/exchangedb/pg_select_deposits_missing_wire.c
rename to src/exchangedb/pg_select_batch_deposits_missing_wire.c
index a18a7abf..7118135a 100644
--- a/src/exchangedb/pg_select_deposits_missing_wire.c
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -14,15 +14,15 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_deposits_missing_wire.c
- * @brief Implementation of the select_deposits_missing_wire function for 
Postgres
+ * @file exchangedb/pg_select_batch_deposits_missing_wire.c
+ * @brief Implementation of the select_batch_deposits_missing_wire function 
for Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include "taler_error_codes.h"
 #include "taler_dbevents.h"
 #include "taler_pq_lib.h"
-#include "pg_select_deposits_missing_wire.h"
+#include "pg_select_batch_deposits_missing_wire.h"
 #include "pg_helper.h"
 
 /**
@@ -76,7 +76,7 @@ missing_wire_cb (void *cls,
     struct GNUNET_TIME_Timestamp deadline;
     bool done;
     struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
+      GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
                                     &rowid),
       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
                                             &coin_pub),
@@ -113,11 +113,12 @@ missing_wire_cb (void *cls,
 
 
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_deposits_missing_wire (void *cls,
-                                     struct GNUNET_TIME_Timestamp start_date,
-                                     struct GNUNET_TIME_Timestamp end_date,
-                                     TALER_EXCHANGEDB_WireMissingCallback cb,
-                                     void *cb_cls)
+TEH_PG_select_batch_deposits_missing_wire (
+  void *cls,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  TALER_EXCHANGEDB_WireMissingCallback cb,
+  void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
@@ -133,36 +134,39 @@ TEH_PG_select_deposits_missing_wire (void *cls,
   };
   enum GNUNET_DB_QueryStatus qs;
 
-  /* Used in #postgres_select_deposits_missing_wire */
   // FIXME: used by the auditor; can probably be done
   // smarter by checking if 'done' or 'blocked'
   // are set correctly when going over deposits, instead
   // of JOINing with refunds.
+  // Also unclear why we return by coin_pub here;
+  // Also fails to check overdue in case of PARTIAL refunds.
+
   PREPARE (pg,
            "deposits_get_overdue",
            "SELECT"
-           " deposit_serial_id"
-           ",coin_pub"
-           ",amount_with_fee"
-           ",payto_uri"
-           ",wire_deadline"
-           ",done"
-           " FROM deposits d"
+           " bdep.batch_deposit_serial_id"
+           ",cdep.coin_pub"
+           ",cdep.amount_with_fee"
+           ",wt.payto_uri"
+           ",bdep.wire_deadline"
+           ",bdep.done"
+           " FROM batch_deposits bdep"
+           "   JOIN coin_deposits cdep"
+           "     USING (batch_deposit_serial_id)"
            "   JOIN known_coins"
            "     USING (coin_pub)"
-           "   JOIN wire_targets"
+           "   JOIN wire_targets wt"
            "     USING (wire_target_h_payto)"
-           " WHERE wire_deadline >= $1"
-           " AND wire_deadline < $2"
+           " WHERE bdep.wire_deadline >= $1"
+           " AND bdep.wire_deadline < $2"
            " AND NOT (EXISTS (SELECT 1"
            "            FROM refunds r"
-           "            WHERE (r.coin_pub = d.coin_pub) AND 
(r.deposit_serial_id = d.deposit_serial_id))"
+           "            WHERE (r.coin_pub = cdep.coin_pub)"
+           "               AND (r.batch_deposit_serial_id = 
bdep.batch_deposit_serial_id))"
            "       OR EXISTS (SELECT 1"
-           "            FROM aggregation_tracking"
-           "            WHERE (aggregation_tracking.deposit_serial_id = 
d.deposit_serial_id)))"
-           " ORDER BY wire_deadline ASC");
-
-
+           "            FROM aggregation_tracking atr"
+           "            WHERE (atr.batch_deposit_serial_id = 
bdep.batch_deposit_serial_id)))"
+           " ORDER BY bdep.wire_deadline ASC");
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
                                              "deposits_get_overdue",
                                              params,
diff --git a/src/exchangedb/pg_select_deposits_missing_wire.h 
b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
similarity index 68%
rename from src/exchangedb/pg_select_deposits_missing_wire.h
rename to src/exchangedb/pg_select_batch_deposits_missing_wire.h
index 40c592ce..697baa83 100644
--- a/src/exchangedb/pg_select_deposits_missing_wire.h
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
@@ -14,8 +14,8 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_deposits_missing_wire.h
- * @brief implementation of the select_deposits_missing_wire function for 
Postgres
+ * @file exchangedb/pg_select_batch_deposits_missing_wire.h
+ * @brief implementation of the select_batch_deposits_missing_wire function 
for Postgres
  * @author Christian Grothoff
  */
 #ifndef PG_SELECT_DEPOSITS_MISSING_WIRE_H
@@ -37,10 +37,13 @@
  * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_deposits_missing_wire (void *cls,
-                                     struct GNUNET_TIME_Timestamp start_date,
-                                     struct GNUNET_TIME_Timestamp end_date,
-                                     TALER_EXCHANGEDB_WireMissingCallback cb,
-                                     void *cb_cls);
+TEH_PG_select_batch_deposits_missing_wire (void *cls,
+                                           struct GNUNET_TIME_Timestamp
+                                           start_date,
+                                           struct GNUNET_TIME_Timestamp
+                                           end_date,
+                                           TALER_EXCHANGEDB_WireMissingCallback
+                                           cb,
+                                           void *cb_cls);
 
 #endif
diff --git a/src/exchangedb/pg_select_deposits_above_serial_id.c 
b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
similarity index 74%
rename from src/exchangedb/pg_select_deposits_above_serial_id.c
rename to src/exchangedb/pg_select_coin_deposits_above_serial_id.c
index 102604f2..49065c17 100644
--- a/src/exchangedb/pg_select_deposits_above_serial_id.c
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -14,21 +14,21 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_deposits_above_serial_id.c
- * @brief Implementation of the select_deposits_above_serial_id function for 
Postgres
+ * @file exchangedb/pg_select_coin_deposits_above_serial_id.c
+ * @brief Implementation of the select_coin_deposits_above_serial_id function 
for Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include "taler_error_codes.h"
 #include "taler_dbevents.h"
 #include "taler_pq_lib.h"
-#include "pg_select_deposits_above_serial_id.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
 #include "pg_helper.h"
 
 /**
  * Closure for #deposit_serial_helper_cb().
  */
-struct DepositSerialContext
+struct CoinDepositSerialContext
 {
 
   /**
@@ -57,16 +57,16 @@ struct DepositSerialContext
  * Helper function to be called with the results of a SELECT statement
  * that has returned @a num_results results.
  *
- * @param cls closure of type `struct DepositSerialContext`
+ * @param cls closure of type `struct CoinDepositSerialContext`
  * @param result the postgres result
  * @param num_results the number of results in @a result
  */
 static void
-deposit_serial_helper_cb (void *cls,
-                          PGresult *result,
-                          unsigned int num_results)
+coin_deposit_serial_helper_cb (void *cls,
+                               PGresult *result,
+                               unsigned int num_results)
 {
-  struct DepositSerialContext *dsc = cls;
+  struct CoinDepositSerialContext *dsc = cls;
   struct PostgresClosure *pg = dsc->pg;
 
   for (unsigned int i = 0; i<num_results; i++)
@@ -107,7 +107,7 @@ deposit_serial_helper_cb (void *cls,
                                     &deposit.receiver_wire_account),
       GNUNET_PQ_result_spec_bool ("done",
                                   &done),
-      GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
+      GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
                                     &rowid),
       GNUNET_PQ_result_spec_end
     };
@@ -139,7 +139,7 @@ deposit_serial_helper_cb (void *cls,
 
 
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_deposits_above_serial_id (
+TEH_PG_select_coin_deposits_above_serial_id (
   void *cls,
   uint64_t serial_id,
   TALER_EXCHANGEDB_DepositCallback cb,
@@ -150,7 +150,7 @@ TEH_PG_select_deposits_above_serial_id (
     GNUNET_PQ_query_param_uint64 (&serial_id),
     GNUNET_PQ_query_param_end
   };
-  struct DepositSerialContext dsc = {
+  struct CoinDepositSerialContext dsc = {
     .cb = cb,
     .cb_cls = cb_cls,
     .pg = pg,
@@ -160,36 +160,38 @@ TEH_PG_select_deposits_above_serial_id (
 
   /* Fetch deposits with rowid '\geq' the given parameter */
   PREPARE (pg,
-           "audit_get_deposits_incr",
+           "audit_get_coin_deposits_incr",
            "SELECT"
-           " amount_with_fee"
-           ",wallet_timestamp"
-           ",exchange_timestamp"
-           ",merchant_pub"
+           " cdep.amount_with_fee"
+           ",bdep.wallet_timestamp"
+           ",bdep.exchange_timestamp"
+           ",bdep.merchant_pub"
            ",denom.denom_pub"
            ",kc.coin_pub"
            ",kc.age_commitment_hash"
-           ",coin_sig"
-           ",refund_deadline"
-           ",wire_deadline"
-           ",h_contract_terms"
-           ",wire_salt"
-           ",payto_uri AS receiver_wire_account"
-           ",done"
-           ",deposit_serial_id"
-           " FROM deposits"
-           "    JOIN wire_targets USING (wire_target_h_payto)"
-           "    JOIN known_coins kc USING (coin_pub)"
-           "    JOIN denominations denom USING (denominations_serial)"
-           " WHERE ("
-           "  (deposit_serial_id>=$1)"
-           " )"
-           " ORDER BY deposit_serial_id ASC;");
-
+           ",cdep.coin_sig"
+           ",bdep.refund_deadline"
+           ",bdep.wire_deadline"
+           ",bdep.h_contract_terms"
+           ",bdep.wire_salt"
+           ",wt.payto_uri AS receiver_wire_account"
+           ",bdep.done"
+           ",cdep.coin_deposit_serial_id"
+           " FROM coin_deposits cdep"
+           " JOIN batch_deposits bdep"
+           "   USING (batch_deposit_serial_id)"
+           " JOIN wire_targets wt"
+           "   USING (wire_target_h_payto)"
+           " JOIN known_coins kc"
+           "   USING (coin_pub)"
+           " JOIN denominations denom"
+           "   USING (denominations_serial)"
+           " WHERE (coin_deposit_serial_id>=$1)"
+           " ORDER BY coin_deposit_serial_id ASC;");
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "audit_get_deposits_incr",
+                                             "audit_get_coin_deposits_incr",
                                              params,
-                                             &deposit_serial_helper_cb,
+                                             &coin_deposit_serial_helper_cb,
                                              &dsc);
   if (GNUNET_OK != dsc.status)
     return GNUNET_DB_STATUS_HARD_ERROR;
diff --git a/src/exchangedb/pg_select_deposits_above_serial_id.h 
b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
similarity index 87%
rename from src/exchangedb/pg_select_deposits_above_serial_id.h
rename to src/exchangedb/pg_select_coin_deposits_above_serial_id.h
index e29937e0..5202336a 100644
--- a/src/exchangedb/pg_select_deposits_above_serial_id.h
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
@@ -14,8 +14,8 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_deposits_above_serial_id.h
- * @brief implementation of the select_deposits_above_serial_id function for 
Postgres
+ * @file exchangedb/pg_select_coin_deposits_above_serial_id.h
+ * @brief implementation of the select_coin_deposits_above_serial_id function 
for Postgres
  * @author Christian Grothoff
  */
 #ifndef PG_SELECT_DEPOSITS_ABOVE_SERIAL_ID_H
@@ -35,7 +35,7 @@
  * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_deposits_above_serial_id (
+TEH_PG_select_coin_deposits_above_serial_id (
   void *cls,
   uint64_t serial_id,
   TALER_EXCHANGEDB_DepositCallback cb,
diff --git a/src/exchangedb/pg_select_refunds_above_serial_id.c 
b/src/exchangedb/pg_select_refunds_above_serial_id.c
index 34e6ba8b..396e8d3d 100644
--- a/src/exchangedb/pg_select_refunds_above_serial_id.c
+++ b/src/exchangedb/pg_select_refunds_above_serial_id.c
@@ -182,19 +182,21 @@ TEH_PG_select_refunds_above_serial_id (
   PREPARE (pg,
            "audit_get_refunds_incr",
            "SELECT"
-           " dep.merchant_pub"
+           " bdep.merchant_pub"
            ",ref.merchant_sig"
-           ",dep.h_contract_terms"
+           ",bdep.h_contract_terms"
            ",ref.rtransaction_id"
            ",denom.denom_pub"
            ",kc.coin_pub"
            ",ref.amount_with_fee"
            ",ref.refund_serial_id"
            " FROM refunds ref"
-           "   JOIN deposits dep"
-           "     ON (ref.coin_pub=dep.coin_pub AND 
ref.deposit_serial_id=dep.deposit_serial_id)"
+           "   JOIN batch_deposits bdep"
+           "     ON (ref.batch_deposit_serial_id=bdep.batch_deposit_serial_id)"
+           "   JOIN coin_deposits cdep"
+           "     ON (ref.coin_pub=cdep.coin_pub AND 
ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
            "   JOIN known_coins kc"
-           "     ON (dep.coin_pub=kc.coin_pub)"
+           "     ON (cdep.coin_pub=kc.coin_pub)"
            "   JOIN denominations denom"
            "     ON (kc.denominations_serial=denom.denominations_serial)"
            " WHERE ref.refund_serial_id>=$1"
@@ -204,12 +206,12 @@ TEH_PG_select_refunds_above_serial_id (
            "SELECT"
            " CAST(SUM(CAST((ref.amount_with_fee).frac AS INT8)) AS INT8) AS 
s_f"
            ",CAST(SUM((ref.amount_with_fee).val) AS INT8) AS s_v"
-           ",dep.amount_with_fee"
+           ",cdep.amount_with_fee"
            " FROM refunds ref"
-           "   JOIN deposits dep"
-           "     ON (ref.coin_pub=dep.coin_pub AND 
ref.deposit_serial_id=dep.deposit_serial_id)"
+           "   JOIN coin_deposits cdep"
+           "     ON (ref.coin_pub=cdep.coin_pub AND 
ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
            " WHERE ref.refund_serial_id=$1"
-           " GROUP BY (dep.amount_with_fee);");
+           " GROUP BY (cdep.amount_with_fee);");
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
                                              "audit_get_refunds_incr",
                                              params,
diff --git a/src/exchangedb/pg_select_refunds_by_coin.c 
b/src/exchangedb/pg_select_refunds_by_coin.c
index 44a50579..d9cd6dd3 100644
--- a/src/exchangedb/pg_select_refunds_by_coin.c
+++ b/src/exchangedb/pg_select_refunds_by_coin.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-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
@@ -49,7 +49,7 @@ struct SelectRefundContext
   /**
    * Set to #GNUNET_SYSERR on error.
    */
-  int status;
+  enum GNUNET_GenericReturnValue status;
 };
 
 
@@ -112,205 +112,29 @@ TEH_PG_select_refunds_by_coin (
     GNUNET_PQ_query_param_auto_from_type (h_contract),
     GNUNET_PQ_query_param_end
   };
-  struct GNUNET_PQ_QueryParam params5[] = {
-    GNUNET_PQ_query_param_auto_from_type (coin_pub),
-    GNUNET_PQ_query_param_end
-  };
-
   struct SelectRefundContext srctx = {
     .cb = cb,
     .cb_cls = cb_cls,
     .pg = pg,
     .status = GNUNET_OK
   };
-  static int percent_refund = -2;
-  const char *query;
-  struct GNUNET_PQ_QueryParam *xparams = params;
-
-  if (-2 == percent_refund)
-  {
-    const char *mode = getenv ("TALER_POSTGRES_SELECT_REFUNDS_BY_COIN_LOGIC");
-    char dummy;
-
-    if ( (NULL==mode) ||
-         (1 != sscanf (mode,
-                       "%d%c",
-                       &percent_refund,
-                       &dummy)) )
-    {
-      if (NULL != mode)
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Bad mode `%s' specified\n",
-                    mode);
-      percent_refund = 0;
-    }
-  }
-
-  switch (percent_refund)
-  {
-  case 0:
-    query = "get_refunds_by_coin_and_contract-v0";
-    PREPARE (pg,
-             query,
-             "SELECT"
-             " ref.amount_with_fee"
-             " FROM refunds ref"
-             " JOIN deposits dep"
-             "   USING (coin_pub,deposit_serial_id)"
-             " WHERE ref.coin_pub=$1"
-             "   AND dep.merchant_pub=$2"
-             "   AND dep.h_contract_terms=$3;");
-    break;
-  case 1:
-    query = "get_refunds_by_coin_and_contract-v1";
-    PREPARE (pg,
-             query,
-             "SELECT"
-             " ref.amount_with_fee"
-             " FROM refunds ref"
-             " LEFT JOIN deposits dep"
-             "   ON dep.coin_pub = ref.coin_pub"
-             "   AND ref.deposit_serial_id = dep.deposit_serial_id"
-             " WHERE ref.coin_pub=$1"
-             "   AND dep.merchant_pub=$2"
-             "   AND dep.h_contract_terms=$3;");
-    break;
-  case 2:
-    query = "get_refunds_by_coin_and_contract-v2";
-    PREPARE (pg,
-             query,
-             "WITH rc AS MATERIALIZED("
-             "SELECT"
-             " amount_with_fee"
-             ",coin_pub"
-             ",deposit_serial_id"
-             " FROM refunds ref"
-             " WHERE ref.coin_pub=$1)"
-             "SELECT"
-             "   rc.amount_with_fee"
-             "  FROM deposits dep"
-             " JOIN rc"
-             " ON rc.deposit_serial_id = dep.deposit_serial_id"
-             "  WHERE"
-             "      dep.coin_pub = $1"
-             "  AND dep.merchant_pub = $2"
-             "  AND dep.h_contract_terms = $3");
-    break;
-  case 3:
-    query = "get_refunds_by_coin_and_contract-v3";
-    PREPARE (pg,
-             query,
-             "WITH rc AS MATERIALIZED("
-             "SELECT"
-             " amount_with_fee"
-             ",deposit_serial_id"
-             " FROM refunds"
-             " WHERE coin_pub=$1)"
-             "SELECT"
-             "   rc.amount_with_fee"
-             "  FROM ("
-             "SELECT"
-             " amount_with_fee"
-             " FROM deposits depos"
-             "  WHERE"
-             "  depos.coin_pub = $1"
-             "  AND depos.merchant_pub = $2"
-             "  AND depos.h_contract_terms = $3) dep, rc;");
-    break;
-  case 4:
-    query = "get_refunds_by_coin_and_contract-v4";
-    PREPARE (pg,
-             query,
-             "WITH rc AS MATERIALIZED("
-             "SELECT"
-             " amount_with_fee"
-             ",coin_pub"
-             ",deposit_serial_id"
-             " FROM refunds ref"
-             " WHERE ref.coin_pub=$1)"
-             "SELECT"
-             "   rc.amount_with_fee"
-             "  ,deposit_serial_id"
-             "  FROM ("
-             "SELECT"
-             " amount_with_fee"
-             " FROM deposits depos"
-             "  WHERE"
-             "  depos.merchant_pub = $2"
-             "  AND depos.h_contract_terms = $3) dep JOIN rc "
-             "USING(deposit_serial_id, coin_pub);");
-    break;
-  case 5:
-    query = "get_refunds_by_coin_and_contract-v-broken";
-    xparams = params5;
-    PREPARE (pg,
-             query,
-             "SELECT"
-             " amount_with_fee"
-             ",coin_pub"
-             ",deposit_serial_id"
-             " FROM refunds"
-             " WHERE coin_pub=$1;");
-    break;
-  case 8:
-    query = "get_refunds_by_coin_and_contract-v8";
-    PREPARE (pg,
-             query,
-             "WITH"
-             " rc AS MATERIALIZED("
-             "  SELECT"
-             "   amount_with_fee"
-             "  ,coin_pub"
-             "  ,deposit_serial_id"
-             "  FROM refunds"
-             "  WHERE coin_pub=$1),"
-             " dep AS MATERIALIZED("
-             "  SELECT"
-             "   deposit_serial_id"
-             "  FROM deposits"
-             "  WHERE coin_pub = $1"
-             "    AND merchant_pub = $2"
-             "    AND h_contract_terms = $3"
-             ")"
-             "SELECT"
-             "   rc.amount_with_fee"
-             "  FROM "
-             "  rc JOIN dep USING (deposit_serial_id);");
-    break;
-  case 9:
-    query = "get_refunds_by_coin_and_contract-v9-broken";
-    PREPARE (pg,
-             query,
-             "SELECT"
-             "   ref.amount_with_fee"
-             " FROM deposits dep"
-             " JOIN refunds ref USING(deposit_serial_id)"
-             " WHERE dep.coin_pub IN ("
-             "   SELECT coin_pub"
-             "     FROM refunds"
-             "    WHERE coin_pub=$1)"
-             "  AND merchant_pub = $2"
-             "  AND h_contract_terms = $3;");
-    break;
-  case 10:
-    query = "get_refunds_by_coin_and_contract-v10-broken";
-    PREPARE (pg,
-             query,
-             "SELECT"
-             " *"
-             " FROM"
-             " exchange_do_refund_by_coin"
-             " ($1, $2, $3) "
-             " AS (amount_with_fee taler_amount);");
-    break;
-  default:
-    GNUNET_break (0);
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  }
-
+  const char *query = "get_refunds_by_coin_and_contract";
+
+  PREPARE (pg,
+           query,
+           "SELECT"
+           "   ref.amount_with_fee"
+           " FROM refunds ref"
+           " JOIN coin_deposits cdep"
+           "   USING (coin_pub,batch_deposit_serial_id)"
+           " JOIN batch_deposits bdep"
+           "   ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
+           " WHERE ref.coin_pub=$1"
+           "   AND bdep.merchant_pub=$2"
+           "   AND bdep.h_contract_terms=$3;");
   qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
                                              query,
-                                             xparams,
+                                             params,
                                              &get_refunds_cb,
                                              &srctx);
   if (GNUNET_SYSERR == srctx.status)
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
index 503a699d..067e859b 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -65,7 +65,6 @@
 #include "pg_iterate_active_signkeys.h"
 #include "pg_preflight.h"
 #include "pg_commit.h"
-#include "pg_insert_aggregation_tracking.h"
 #include "pg_drop_tables.h"
 #include "pg_select_satisfied_kyc_processes.h"
 #include "pg_select_aggregation_amounts_for_kyc_check.h"
@@ -140,7 +139,6 @@
 #include "pg_find_aggregation_transient.h"
 #include "pg_update_aggregation_transient.h"
 #include "pg_get_ready_deposit.h"
-#include "pg_insert_deposit.h"
 #include "pg_insert_refund.h"
 #include "pg_select_refunds_by_coin.h"
 #include "pg_get_melt.h"
@@ -161,7 +159,7 @@
 #include "pg_start_deferred_wire_out.h"
 #include "pg_store_wire_transfer_out.h"
 #include "pg_gc.h"
-#include "pg_select_deposits_above_serial_id.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
 #include "pg_select_history_requests_above_serial_id.h"
 #include "pg_select_purse_decisions_above_serial_id.h"
 #include "pg_select_purse_deposits_by_purse.h"
@@ -178,7 +176,7 @@
 #include "pg_get_old_coin_by_h_blind.h"
 #include "pg_insert_denomination_revocation.h"
 #include "pg_get_denomination_revocation.h"
-#include "pg_select_deposits_missing_wire.h"
+#include "pg_select_batch_deposits_missing_wire.h"
 #include "pg_lookup_auditor_timestamp.h"
 #include "pg_lookup_auditor_status.h"
 #include "pg_insert_auditor.h"
@@ -479,8 +477,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_commit;
   plugin->preflight
     = &TEH_PG_preflight;
-  plugin->insert_aggregation_tracking
-    = &TEH_PG_insert_aggregation_tracking;
   plugin->select_aggregation_amounts_for_kyc_check
     = &TEH_PG_select_aggregation_amounts_for_kyc_check;
   plugin->select_satisfied_kyc_processes
@@ -627,8 +623,6 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_update_aggregation_transient;
   plugin->get_ready_deposit
     = &TEH_PG_get_ready_deposit;
-  plugin->insert_deposit
-    = &TEH_PG_insert_deposit;
   plugin->insert_refund
     = &TEH_PG_insert_refund;
   plugin->select_refunds_by_coin
@@ -669,8 +663,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_store_wire_transfer_out;
   plugin->gc
     = &TEH_PG_gc;
-  plugin->select_deposits_above_serial_id
-    = &TEH_PG_select_deposits_above_serial_id;
+  plugin->select_coin_deposits_above_serial_id
+    = &TEH_PG_select_coin_deposits_above_serial_id;
   plugin->select_history_requests_above_serial_id
     = &TEH_PG_select_history_requests_above_serial_id;
   plugin->select_purse_decisions_above_serial_id
@@ -703,8 +697,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
     = &TEH_PG_insert_denomination_revocation;
   plugin->get_denomination_revocation
     = &TEH_PG_get_denomination_revocation;
-  plugin->select_deposits_missing_wire
-    = &TEH_PG_select_deposits_missing_wire;
+  plugin->select_batch_deposits_missing_wire
+    = &TEH_PG_select_batch_deposits_missing_wire;
   plugin->lookup_auditor_timestamp
     = &TEH_PG_lookup_auditor_timestamp;
   plugin->lookup_auditor_status
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
index 7fff2fd4..cc67249f 100644
--- a/src/exchangedb/procedures.sql.in
+++ b/src/exchangedb/procedures.sql.in
@@ -44,8 +44,6 @@ SET search_path TO exchange;
 #include "exchange_do_insert_kyc_attributes.sql"
 #include "exchange_do_reserves_in_insert.sql"
 #include "exchange_do_batch_reserves_update.sql"
-#include "exchange_do_refund_by_coin.sql"
-#include "exchange_do_get_ready_deposit.sql"
 #include "exchange_do_get_link_data.sql"
 #include "exchange_do_batch_coin_known.sql"
 
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index 659f8269..7aba4f12 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -603,7 +603,7 @@ static struct TALER_PaytoHashP wire_target_h_payto;
 
 
 /**
- * Callback for #select_deposits_above_serial_id ()
+ * Callback for #select_coin_deposits_above_serial_id ()
  *
  * @param cls closure
  * @param rowid unique serial ID for the deposit in our DB
@@ -930,14 +930,17 @@ audit_wire_cb (void *cls,
 /**
  * Test API relating to wire_out handling.
  *
+ * @param bd batch deposit to test
  * @return #GNUNET_OK on success
  */
 static enum GNUNET_GenericReturnValue
-test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
+test_wire_out (const struct TALER_EXCHANGEDB_BatchDeposit *bd)
 {
+  const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = &bd->cdis[0];
   struct TALER_PaytoHashP h_payto;
 
-  TALER_payto_hash (deposit->receiver_wire_account,
+  GNUNET_assert (0 < bd->num_cdis);
+  TALER_payto_hash (bd->receiver_wire_account,
                     &h_payto);
   auditor_row_cnt = 0;
   memset (&wire_out_wtid,
@@ -955,8 +958,8 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit 
*deposit)
           plugin->start_deferred_wire_out (plugin->cls));
 
   /* setup values for wire transfer aggregation data */
-  merchant_pub_wt = deposit->merchant_pub;
-  h_contract_terms_wt = deposit->h_contract_terms;
+  merchant_pub_wt = bd->merchant_pub;
+  h_contract_terms_wt = bd->h_contract_terms;
   coin_pub_wt = deposit->coin.coin_pub;
 
   coin_value_wt = deposit->amount_with_fee;
@@ -1202,8 +1205,10 @@ run (void *cls)
   struct TALER_EXCHANGEDB_ReserveHistory *rh_head;
   struct TALER_EXCHANGEDB_BankTransfer *bt;
   struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
-  struct TALER_EXCHANGEDB_Deposit deposit;
-  struct TALER_EXCHANGEDB_Deposit deposit2;
+  struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
+  struct TALER_CoinSpendPublicKeyP cpub2;
+  struct TALER_MerchantPublicKeyP mpub2;
   struct TALER_EXCHANGEDB_Refund refund;
   struct TALER_EXCHANGEDB_TransactionList *tl;
   struct TALER_EXCHANGEDB_TransactionList *tlp;
@@ -1233,7 +1238,12 @@ run (void *cls)
   memset (&deposit,
           0,
           sizeof (deposit));
-  deposit.receiver_wire_account = (char *) rcvr;
+  memset (&bd,
+          0,
+          sizeof (bd));
+  bd.receiver_wire_account = (char *) rcvr;
+  bd.cdis = &deposit;
+  bd.num_cdis = 1;
   memset (&salt,
           45,
           sizeof (salt));
@@ -1282,7 +1292,6 @@ run (void *cls)
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
                                          &fees.deposit));
-  deposit.deposit_fee = fees.deposit;
   GNUNET_assert (GNUNET_OK ==
                  TALER_string_to_amount (CURRENCY ":0.000010",
                                          &fees.refresh));
@@ -1516,23 +1525,22 @@ run (void *cls)
     struct GNUNET_TIME_Timestamp deposit_timestamp
       = GNUNET_TIME_timestamp_get ();
     bool balance_ok;
+    uint32_t bad_balance_idx;
     bool in_conflict;
     struct TALER_PaytoHashP h_payto;
 
     RND_BLK (&h_payto);
-    deposit.refund_deadline
+    bd.refund_deadline
       = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
-    deposit.wire_deadline
+    bd.wire_deadline
       = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
     deposit.amount_with_fee = value;
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->do_deposit (plugin->cls,
-                                &deposit,
-                                known_coin_id,
-                                &h_payto,
-                                false,
+                                &bd,
                                 &deposit_timestamp,
                                 &balance_ok,
+                                &bad_balance_idx,
                                 &in_conflict));
     FAILIF (! balance_ok);
     FAILIF (in_conflict);
@@ -1545,9 +1553,9 @@ run (void *cls)
     bool conflict;
 
     refund.coin = deposit.coin;
-    refund.details.merchant_pub = deposit.merchant_pub;
+    refund.details.merchant_pub = bd.merchant_pub;
     RND_BLK (&refund.details.merchant_sig);
-    refund.details.h_contract_terms = deposit.h_contract_terms;
+    refund.details.h_contract_terms = bd.h_contract_terms;
     refund.details.rtransaction_id = 1;
     refund.details.refund_amount = value;
     refund.details.refund_fee = fees.refund;
@@ -2002,26 +2010,24 @@ run (void *cls)
                                &deposit.csig));
         FAILIF (0 !=
                 GNUNET_memcmp (&have->merchant_pub,
-                               &deposit.merchant_pub));
+                               &bd.merchant_pub));
         FAILIF (0 !=
                 GNUNET_memcmp (&have->h_contract_terms,
-                               &deposit.h_contract_terms));
+                               &bd.h_contract_terms));
         FAILIF (0 !=
                 GNUNET_memcmp (&have->wire_salt,
-                               &deposit.wire_salt));
+                               &bd.wire_salt));
         FAILIF (GNUNET_TIME_timestamp_cmp (have->timestamp,
                                            !=,
-                                           deposit.timestamp));
+                                           bd.wallet_timestamp));
         FAILIF (GNUNET_TIME_timestamp_cmp (have->refund_deadline,
                                            !=,
-                                           deposit.refund_deadline));
+                                           bd.refund_deadline));
         FAILIF (GNUNET_TIME_timestamp_cmp (have->wire_deadline,
                                            !=,
-                                           deposit.wire_deadline));
+                                           bd.wire_deadline));
         FAILIF (0 != TALER_amount_cmp (&have->amount_with_fee,
                                        &deposit.amount_with_fee));
-        FAILIF (0 != TALER_amount_cmp (&have->deposit_fee,
-                                       &deposit.deposit_fee));
         matched |= 1;
         break;
       }
@@ -2092,7 +2098,6 @@ run (void *cls)
   memset (&deposit,
           0,
           sizeof (deposit));
-  deposit.deposit_fee = fees.deposit;
   RND_BLK (&deposit.coin.coin_pub);
   TALER_denom_pub_hash (&dkp->pub,
                         &deposit.coin.denom_pub_hash);
@@ -2104,20 +2109,18 @@ run (void *cls)
                                           &alg_values,
                                           &dkp->pub));
   RND_BLK (&deposit.csig);
-  RND_BLK (&deposit.merchant_pub);
-  RND_BLK (&deposit.h_contract_terms);
-  RND_BLK (&deposit.wire_salt);
-  deposit.receiver_wire_account =
+  RND_BLK (&bd.merchant_pub);
+  RND_BLK (&bd.h_contract_terms);
+  RND_BLK (&bd.wire_salt);
+  bd.receiver_wire_account =
     "payto://iban/DE67830654080004822650?receiver-name=Test";
   TALER_merchant_wire_signature_hash (
     "payto://iban/DE67830654080004822650?receiver-name=Test",
-    &deposit.wire_salt,
+    &bd.wire_salt,
     &h_wire_wt);
   deposit.amount_with_fee = value;
-  deposit.deposit_fee = fees.deposit;
-
-  deposit.refund_deadline = deadline;
-  deposit.wire_deadline = deadline;
+  bd.refund_deadline = deadline;
+  bd.wire_deadline = deadline;
   result = 8;
   {
     uint64_t known_coin_id;
@@ -2136,22 +2139,30 @@ run (void *cls)
     struct GNUNET_TIME_Timestamp r;
     struct TALER_Amount deposit_fee;
     struct TALER_MerchantWireHashP h_wire;
+    bool balance_ok;
+    uint32_t bad_idx;
+    bool ctr_conflict;
 
     now = GNUNET_TIME_timestamp_get ();
+    TALER_payto_hash (bd.receiver_wire_account,
+                      &bd.wire_target_h_payto);
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-            plugin->insert_deposit (plugin->cls,
-                                    now,
-                                    &deposit));
-    TALER_merchant_wire_signature_hash (deposit.receiver_wire_account,
-                                        &deposit.wire_salt,
+            plugin->do_deposit (plugin->cls,
+                                &bd,
+                                &now,
+                                &balance_ok,
+                                &bad_idx,
+                                &ctr_conflict));
+    TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+                                        &bd.wire_salt,
                                         &h_wire);
     FAILIF (1 !=
             plugin->have_deposit2 (plugin->cls,
-                                   &deposit.h_contract_terms,
+                                   &bd.h_contract_terms,
                                    &h_wire,
                                    &deposit.coin.coin_pub,
-                                   &deposit.merchant_pub,
-                                   deposit.refund_deadline,
+                                   &bd.merchant_pub,
+                                   bd.refund_deadline,
                                    &deposit_fee,
                                    &r));
     FAILIF (GNUNET_TIME_timestamp_cmp (now,
@@ -2169,19 +2180,19 @@ run (void *cls)
       GNUNET_TIME_absolute_add (deadline.abs_time,
                                 GNUNET_TIME_UNIT_SECONDS));
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-            plugin->select_deposits_missing_wire (plugin->cls,
-                                                  start_range,
-                                                  end_range,
-                                                  &wire_missing_cb,
-                                                  &deposit));
+            plugin->select_batch_deposits_missing_wire (plugin->cls,
+                                                        start_range,
+                                                        end_range,
+                                                        &wire_missing_cb,
+                                                        &deposit));
     FAILIF (8 != result);
   }
   auditor_row_cnt = 0;
   FAILIF (0 >=
-          plugin->select_deposits_above_serial_id (plugin->cls,
-                                                   0,
-                                                   &audit_deposit_cb,
-                                                   NULL));
+          plugin->select_coin_deposits_above_serial_id (plugin->cls,
+                                                        0,
+                                                        &audit_deposit_cb,
+                                                        NULL));
   FAILIF (0 == auditor_row_cnt);
   result = 8;
   sleep (2); /* give deposit time to be ready */
@@ -2196,9 +2207,9 @@ run (void *cls)
                                        &merchant_pub2,
                                        &payto_uri2));
     FAILIF (0 != GNUNET_memcmp (&merchant_pub2,
-                                &deposit.merchant_pub));
+                                &bd.merchant_pub));
     FAILIF (0 != strcmp (payto_uri2,
-                         deposit.receiver_wire_account));
+                         bd.receiver_wire_account));
     TALER_payto_hash (payto_uri2,
                       &wire_target_h_payto);
     GNUNET_free (payto_uri2);
@@ -2214,7 +2225,7 @@ run (void *cls)
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->aggregate (plugin->cls,
                                &wire_target_h_payto,
-                               &deposit.merchant_pub,
+                               &bd.merchant_pub,
                                &wtid,
                                &total));
   }
@@ -2238,7 +2249,7 @@ run (void *cls)
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->select_aggregation_transient (plugin->cls,
                                                   &wire_target_h_payto,
-                                                  &deposit.merchant_pub,
+                                                  &bd.merchant_pub,
                                                   "x-bank",
                                                   &wtid2,
                                                   &total2));
@@ -2246,14 +2257,14 @@ run (void *cls)
             plugin->create_aggregation_transient (plugin->cls,
                                                   &wire_target_h_payto,
                                                   "x-bank",
-                                                  &deposit.merchant_pub,
+                                                  &bd.merchant_pub,
                                                   &wtid,
                                                   0,
                                                   &total));
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->select_aggregation_transient (plugin->cls,
                                                   &wire_target_h_payto,
-                                                  &deposit.merchant_pub,
+                                                  &bd.merchant_pub,
                                                   "x-bank",
                                                   &wtid2,
                                                   &total2));
@@ -2275,7 +2286,7 @@ run (void *cls)
     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
             plugin->select_aggregation_transient (plugin->cls,
                                                   &wire_target_h_payto,
-                                                  &deposit.merchant_pub,
+                                                  &bd.merchant_pub,
                                                   "x-bank",
                                                   &wtid2,
                                                   &total2));
@@ -2292,7 +2303,7 @@ run (void *cls)
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->select_aggregation_transient (plugin->cls,
                                                   &wire_target_h_payto,
-                                                  &deposit.merchant_pub,
+                                                  &bd.merchant_pub,
                                                   "x-bank",
                                                   &wtid2,
                                                   &total2));
@@ -2301,37 +2312,35 @@ run (void *cls)
           plugin->commit (plugin->cls));
 
   result = 10;
-  deposit2 = deposit;
   FAILIF (GNUNET_OK !=
           plugin->start (plugin->cls,
                          "test-2"));
-  RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */
+  RND_BLK (&mpub2); /* should fail if merchant is different */
   {
     struct TALER_MerchantWireHashP h_wire;
     struct GNUNET_TIME_Timestamp r;
     struct TALER_Amount deposit_fee;
 
-    TALER_merchant_wire_signature_hash (deposit2.receiver_wire_account,
-                                        &deposit2.wire_salt,
+    TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+                                        &bd.wire_salt,
                                         &h_wire);
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->have_deposit2 (plugin->cls,
-                                   &deposit2.h_contract_terms,
+                                   &bd.h_contract_terms,
                                    &h_wire,
-                                   &deposit2.coin.coin_pub,
-                                   &deposit2.merchant_pub,
-                                   deposit2.refund_deadline,
+                                   &deposit.coin.coin_pub,
+                                   &mpub2,
+                                   bd.refund_deadline,
                                    &deposit_fee,
                                    &r));
-    deposit2.merchant_pub = deposit.merchant_pub;
-    RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
+    RND_BLK (&cpub2); /* should fail if coin is different */
     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
             plugin->have_deposit2 (plugin->cls,
-                                   &deposit2.h_contract_terms,
+                                   &bd.h_contract_terms,
                                    &h_wire,
-                                   &deposit2.coin.coin_pub,
-                                   &deposit2.merchant_pub,
-                                   deposit2.refund_deadline,
+                                   &cpub2,
+                                   &bd.merchant_pub,
+                                   bd.refund_deadline,
                                    &deposit_fee,
                                    &r));
   }
@@ -2381,7 +2390,7 @@ run (void *cls)
   FAILIF (GNUNET_OK !=
           test_wire_prepare ());
   FAILIF (GNUNET_OK !=
-          test_wire_out (&deposit));
+          test_wire_out (&bd));
   FAILIF (GNUNET_OK !=
           test_gc ());
   FAILIF (GNUNET_OK !=
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index e5107808..36ce7474 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -273,7 +273,8 @@ enum TALER_EXCHANGEDB_ReplicatedTable
   TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS,
   TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS,
   TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS,
-  TALER_EXCHANGEDB_RT_DEPOSITS,
+  TALER_EXCHANGEDB_RT_BATCH_DEPOSITS,
+  TALER_EXCHANGEDB_RT_COIN_DEPOSITS,
   TALER_EXCHANGEDB_RT_REFUNDS,
   TALER_EXCHANGEDB_RT_WIRE_OUT,
   TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING,
@@ -497,26 +498,33 @@ struct TALER_EXCHANGEDB_TableData
     struct
     {
       uint64_t shard;
-      uint64_t known_coin_id;
-      struct TALER_CoinSpendPublicKeyP coin_pub;
-      struct TALER_Amount amount_with_fee;
+      struct TALER_MerchantPublicKeyP merchant_pub;
       struct GNUNET_TIME_Timestamp wallet_timestamp;
       struct GNUNET_TIME_Timestamp exchange_timestamp;
       struct GNUNET_TIME_Timestamp refund_deadline;
       struct GNUNET_TIME_Timestamp wire_deadline;
-      struct TALER_MerchantPublicKeyP merchant_pub;
       struct TALER_PrivateContractHashP h_contract_terms;
-      struct TALER_CoinSpendSignatureP coin_sig;
+      struct GNUNET_HashCode wallet_data_hash;
+      bool no_wallet_data_hash;
       struct TALER_WireSaltP wire_salt;
       struct TALER_PaytoHashP wire_target_h_payto;
       bool policy_blocked;
       uint64_t policy_details_serial_id;
-    } deposits;
+      bool no_policy_details;
+    } batch_deposits;
+
+    struct
+    {
+      uint64_t batch_deposit_serial_id;
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_CoinSpendSignatureP coin_sig;
+      struct TALER_Amount amount_with_fee;
+    } coin_deposits;
 
     struct
     {
       struct TALER_CoinSpendPublicKeyP coin_pub;
-      uint64_t deposit_serial_id;
+      uint64_t batch_deposit_serial_id;
       struct TALER_MerchantSignatureP merchant_sig;
       uint64_t rtransaction_id;
       struct TALER_Amount amount_with_fee;
@@ -533,7 +541,7 @@ struct TALER_EXCHANGEDB_TableData
 
     struct
     {
-      uint64_t deposit_serial_id;
+      uint64_t batch_deposit_serial_id;
       struct TALER_WireTransferIdentifierRawP wtid_raw;
     } aggregation_tracking;
 
@@ -1687,6 +1695,129 @@ struct TALER_EXCHANGEDB_ReserveHistory
 };
 
 
+/**
+ * @brief Data about a coin for a deposit operation.
+ */
+struct TALER_EXCHANGEDB_CoinDepositInformation
+{
+  /**
+   * Information about the coin that is being deposited.
+   */
+  struct TALER_CoinPublicInfo coin;
+
+  /**
+   * ECDSA signature affirming that the customer intends
+   * this coin to be deposited at the merchant identified
+   * by @e h_wire in relation to the proposal data identified
+   * by @e h_contract_terms.
+   */
+  struct TALER_CoinSpendSignatureP csig;
+
+  /**
+   * Fraction of the coin's remaining value to be deposited, including
+   * depositing fee (if any).  The coin is identified by @e coin_pub.
+   */
+  struct TALER_Amount amount_with_fee;
+
+};
+
+
+/**
+ * @brief Data from a batch deposit operation.
+ */
+struct TALER_EXCHANGEDB_BatchDeposit
+{
+  /**
+   * Array about the coins that are being deposited.
+   */
+  const struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
+
+  /**
+   * Length of the @e cdis array.
+   */
+  unsigned int num_cdis;
+
+  /**
+   * Public key of the merchant.  Enables later identification
+   * of the merchant in case of a need to rollback transactions.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Hash over the proposal data between merchant and customer
+   * (remains unknown to the Exchange).
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Salt used by the merchant to compute "h_wire".
+   */
+  struct TALER_WireSaltP wire_salt;
+
+  /**
+   * Information about the receiver for executing the transaction.  URI in
+   * payto://-format.
+   */
+  const char *receiver_wire_account;
+
+  /**
+   * Unsalted hash over @e receiver_wire_account.
+   */
+  struct TALER_PaytoHashP wire_target_h_payto;
+
+  /**
+   * Time when this request was generated.  Used, for example, to
+   * assess when (roughly) the income was achieved for tax purposes.
+   * Note that the Exchange will only check that the timestamp is not "too
+   * far" into the future (i.e. several days).  The fact that the
+   * timestamp falls within the validity period of the coin's
+   * denomination key is irrelevant for the validity of the deposit
+   * request, as obviously the customer and merchant could conspire to
+   * set any timestamp.  Also, the Exchange must accept very old deposit
+   * requests, as the merchant might have been unable to transmit the
+   * deposit request in a timely fashion (so back-dating is not
+   * prevented).
+   */
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+  /**
+   * How much time does the merchant have to issue a refund request?
+   * Zero if refunds are not allowed.  After this time, the coin
+   * cannot be refunded.
+   */
+  struct GNUNET_TIME_Timestamp refund_deadline;
+
+  /**
+   * How much time does the merchant have to execute the wire transfer?
+   * This time is advisory for aggregating transactions, not a hard
+   * constraint (as the merchant can theoretically pick any time,
+   * including one in the past).
+   */
+  struct GNUNET_TIME_Timestamp wire_deadline;
+
+  /**
+   * Hash over additional inputs by the wallet.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+  /**
+   * Row ID of the policy details; 0 if no policy applies.
+   */
+  uint64_t policy_details_serial_id;
+
+  /**
+   * True if @e wallet_data_hash was provided
+   */
+  bool has_wallet_data_hash;
+
+  /**
+   * True if further processing is blocked by policy.
+   */
+  bool policy_blocked;
+
+};
+
+
 /**
  * @brief Data from a deposit operation.  The combination of
  * the coin's public key, the merchant's public key and the
@@ -3917,25 +4048,22 @@ struct TALER_EXCHANGEDB_Plugin
    * of the coin and possibly persisting the deposit details.
    *
    * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @param deposit deposit operation details
-   * @param known_coin_id row of the coin in the known_coins table
-   * @param h_payto hash of the merchant's payto URI
-   * @param policy_details_serial_id (pointer to) the row ID of the policy 
details, maybe NULL
+   * @param bd batch deposit operation details
    * @param[in,out] exchange_timestamp time to use for the deposit (possibly 
updated)
    * @param[out] balance_ok set to true if the balance was sufficient
-   * @param[out] in_conflict set to true if the deposit conflicted
+   * @param[out] bad_balance_index set to the first index of a coin for which 
the balance was insufficient,
+   *             only used if @a balance_ok is set to false.
+   * @param[out] ctr_conflict set to true if the same contract terms hash was 
previously submitted with other meta data (deadlines, wallet_data_hash, wire 
data etc.)
    * @return query execution status
    */
   enum GNUNET_DB_QueryStatus
   (*do_deposit)(
     void *cls,
-    const struct TALER_EXCHANGEDB_Deposit *deposit,
-    uint64_t known_coin_id,
-    const struct TALER_PaytoHashP *h_payto,
-    uint64_t *policy_details_serial_id,
+    const struct TALER_EXCHANGEDB_BatchDeposit *bd,
     struct GNUNET_TIME_Timestamp *exchange_timestamp,
     bool *balance_ok,
-    bool *in_conflict);
+    uint32_t *bad_balance_index,
+    bool *ctr_conflict);
 
 
   /**
@@ -4282,21 +4410,6 @@ struct TALER_EXCHANGEDB_Plugin
     struct GNUNET_TIME_Timestamp *exchange_timestamp);
 
 
-  /**
-   * Insert information about deposited coin into the database.
-   * Used in tests and for benchmarking.
-   *
-   * @param cls the @e cls of this struct with the plugin-specific state
-   * @param exchange_timestamp time the exchange received the deposit request
-   * @param deposit deposit information to store
-   * @return query result status
-   */
-  enum GNUNET_DB_QueryStatus
-  (*insert_deposit)(void *cls,
-                    struct GNUNET_TIME_Timestamp exchange_timestamp,
-                    const struct TALER_EXCHANGEDB_Deposit *deposit);
-
-
   /**
    * Insert information about refunded coin into the database.
    * Used in tests and for benchmarking.
@@ -4627,21 +4740,6 @@ struct TALER_EXCHANGEDB_Plugin
     enum TALER_AmlDecisionState *aml_decision);
 
 
-  /**
-   * Function called to insert aggregation information into the DB.
-   *
-   * @param cls closure
-   * @param wtid the raw wire transfer identifier we used
-   * @param deposit_serial_id row in the deposits table for which this is 
aggregation data
-   * @return transaction status code
-   */
-  enum GNUNET_DB_QueryStatus
-  (*insert_aggregation_tracking)(
-    void *cls,
-    const struct TALER_WireTransferIdentifierRawP *wtid,
-    unsigned long long deposit_serial_id);
-
-
   /**
    * Insert wire transfer fee into database.
    *
@@ -5037,10 +5135,10 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*select_deposits_above_serial_id)(void *cls,
-                                     uint64_t serial_id,
-                                     TALER_EXCHANGEDB_DepositCallback cb,
-                                     void *cb_cls);
+  (*select_coin_deposits_above_serial_id)(void *cls,
+                                          uint64_t serial_id,
+                                          TALER_EXCHANGEDB_DepositCallback cb,
+                                          void *cb_cls);
 
 
   /**
@@ -5396,10 +5494,11 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*get_reserve_by_h_blind)(void *cls,
-                            const struct TALER_BlindedCoinHashP *bch,
-                            struct TALER_ReservePublicKeyP *reserve_pub,
-                            uint64_t *reserve_out_serial_id);
+  (*get_reserve_by_h_blind)(
+    void *cls,
+    const struct TALER_BlindedCoinHashP *bch,
+    struct TALER_ReservePublicKeyP *reserve_pub,
+    uint64_t *reserve_out_serial_id);
 
 
   /**
@@ -5413,10 +5512,11 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*get_old_coin_by_h_blind)(void *cls,
-                             const struct TALER_BlindedCoinHashP *h_blind_ev,
-                             struct TALER_CoinSpendPublicKeyP *old_coin_pub,
-                             uint64_t *rrc_serial);
+  (*get_old_coin_by_h_blind)(
+    void *cls,
+    const struct TALER_BlindedCoinHashP *h_blind_ev,
+    struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+    uint64_t *rrc_serial);
 
 
   /**
@@ -5466,11 +5566,12 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*select_deposits_missing_wire)(void *cls,
-                                  struct GNUNET_TIME_Timestamp start_date,
-                                  struct GNUNET_TIME_Timestamp end_date,
-                                  TALER_EXCHANGEDB_WireMissingCallback cb,
-                                  void *cb_cls);
+  (*select_batch_deposits_missing_wire)(
+    void *cls,
+    struct GNUNET_TIME_Timestamp start_date,
+    struct GNUNET_TIME_Timestamp end_date,
+    TALER_EXCHANGEDB_WireMissingCallback cb,
+    void *cb_cls);
 
 
   /**
@@ -5482,9 +5583,10 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*lookup_auditor_timestamp)(void *cls,
-                              const struct TALER_AuditorPublicKeyP 
*auditor_pub,
-                              struct GNUNET_TIME_Timestamp *last_date);
+  (*lookup_auditor_timestamp)(
+    void *cls,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    struct GNUNET_TIME_Timestamp *last_date);
 
 
   /**
@@ -5498,10 +5600,11 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*lookup_auditor_status)(void *cls,
-                           const struct TALER_AuditorPublicKeyP *auditor_pub,
-                           char **auditor_url,
-                           bool *enabled);
+  (*lookup_auditor_status)(
+    void *cls,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    char **auditor_url,
+    bool *enabled);
 
 
   /**
@@ -5516,11 +5619,12 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*insert_auditor)(void *cls,
-                    const struct TALER_AuditorPublicKeyP *auditor_pub,
-                    const char *auditor_url,
-                    const char *auditor_name,
-                    struct GNUNET_TIME_Timestamp start_date);
+  (*insert_auditor)(
+    void *cls,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    const char *auditor_url,
+    const char *auditor_name,
+    struct GNUNET_TIME_Timestamp start_date);
 
 
   /**
@@ -5536,12 +5640,13 @@ struct TALER_EXCHANGEDB_Plugin
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-  (*update_auditor)(void *cls,
-                    const struct TALER_AuditorPublicKeyP *auditor_pub,
-                    const char *auditor_url,
-                    const char *auditor_name,
-                    struct GNUNET_TIME_Timestamp change_date,
-                    bool enabled);
+  (*update_auditor)(
+    void *cls,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    const char *auditor_url,
+    const char *auditor_name,
+    struct GNUNET_TIME_Timestamp change_date,
+    bool enabled);
 
 
   /**
diff --git a/src/testing/testing_api_cmd_age_withdraw.c 
b/src/testing/testing_api_cmd_age_withdraw.c
index 2a76b3c1..98d0e0f2 100644
--- a/src/testing/testing_api_cmd_age_withdraw.c
+++ b/src/testing/testing_api_cmd_age_withdraw.c
@@ -355,20 +355,23 @@ age_withdraw_cleanup (
     aws->handle = NULL;
   }
 
-  for (size_t n = 0; n < aws->num_coins; n++)
+  if (NULL != aws->coin_inputs)
   {
-    struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[n];
-    struct CoinOutputState *out = &aws->coin_outputs[n];
-
-    if (NULL != in && NULL != in->denom_pub)
+    for (size_t n = 0; n < aws->num_coins; n++)
     {
-      TALER_EXCHANGE_destroy_denomination_key (in->denom_pub);
-      in->denom_pub = NULL;
+      struct TALER_EXCHANGE_AgeWithdrawCoinInput *in = &aws->coin_inputs[n];
+      struct CoinOutputState *out = &aws->coin_outputs[n];
+
+      if (NULL != in && NULL != in->denom_pub)
+      {
+        TALER_EXCHANGE_destroy_denomination_key (in->denom_pub);
+        in->denom_pub = NULL;
+      }
+      if (NULL != out)
+        TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
     }
-    if (NULL != out)
-      TALER_age_commitment_proof_free (&out->details.age_commitment_proof);
+    GNUNET_free (aws->coin_inputs);
   }
-  GNUNET_free (aws->coin_inputs);
   GNUNET_free (aws->coin_outputs);
   GNUNET_free (aws->exchange_url);
   GNUNET_free (aws->reserve_payto_uri);
diff --git a/src/testing/testing_api_cmd_insert_deposit.c 
b/src/testing/testing_api_cmd_insert_deposit.c
index 7fa8fa79..dd89a48d 100644
--- a/src/testing/testing_api_cmd_insert_deposit.c
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -132,11 +132,13 @@ insert_deposit_run (void *cls,
                     struct TALER_TESTING_Interpreter *is)
 {
   struct InsertDepositState *ids = cls;
-  struct TALER_EXCHANGEDB_Deposit deposit;
+  struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
   struct TALER_MerchantPrivateKeyP merchant_priv;
   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
   struct TALER_DenominationPublicKey dpk;
   struct TALER_DenominationPrivateKey denom_priv;
+  char *receiver_wire_account;
 
   (void) cmd;
   if (NULL == ids->plugin)
@@ -152,7 +154,6 @@ insert_deposit_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-  // prepare and store issue first.
   fake_issue (&issue);
   GNUNET_assert (GNUNET_OK ==
                  TALER_denom_priv_create (&denom_priv,
@@ -182,6 +183,11 @@ insert_deposit_run (void *cls,
   memset (&deposit,
           0,
           sizeof (deposit));
+  memset (&bd,
+          0,
+          sizeof (bd));
+  bd.cdis = &deposit;
+  bd.num_cdis = 1;
 
   GNUNET_assert (
     GNUNET_YES ==
@@ -194,15 +200,12 @@ insert_deposit_run (void *cls,
                        NULL,
                        0));
   GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv.eddsa_priv,
-                                      &deposit.merchant_pub.eddsa_pub);
+                                      &bd.merchant_pub.eddsa_pub);
   GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
-                                    &deposit.h_contract_terms.hash);
-  if ( (GNUNET_OK !=
-        TALER_string_to_amount (ids->amount_with_fee,
-                                &deposit.amount_with_fee)) ||
-       (GNUNET_OK !=
-        TALER_string_to_amount (ids->deposit_fee,
-                                &deposit.deposit_fee)) )
+                                    &bd.h_contract_terms.hash);
+  if (GNUNET_OK !=
+      TALER_string_to_amount (ids->amount_with_fee,
+                              &deposit.amount_with_fee))
   {
     TALER_TESTING_interpreter_fail (is);
     TALER_denom_pub_free (&dpk);
@@ -250,21 +253,27 @@ insert_deposit_run (void *cls,
                                             &dpk));
     TALER_blinded_denom_sig_free (&bds);
   }
-  GNUNET_asprintf (&deposit.receiver_wire_account,
+  GNUNET_asprintf (&receiver_wire_account,
                    "payto://x-taler-bank/localhost/%s?receiver-name=%s",
                    ids->merchant_account,
                    ids->merchant_account);
-  memset (&deposit.wire_salt,
+  bd.receiver_wire_account = receiver_wire_account;
+  TALER_payto_hash (bd.receiver_wire_account,
+                    &bd.wire_target_h_payto);
+  memset (&bd.wire_salt,
           46,
-          sizeof (deposit.wire_salt));
-  deposit.timestamp = GNUNET_TIME_timestamp_get ();
-  deposit.wire_deadline = GNUNET_TIME_relative_to_timestamp (
+          sizeof (bd.wire_salt));
+  bd.wallet_timestamp = GNUNET_TIME_timestamp_get ();
+  bd.wire_deadline = GNUNET_TIME_relative_to_timestamp (
     ids->wire_deadline);
   /* finally, actually perform the DB operation */
   {
     uint64_t known_coin_id;
     struct TALER_DenominationHashP dph;
     struct TALER_AgeCommitmentHash agh;
+    bool balance_ok;
+    uint32_t bad_index;
+    bool ctr_conflict;
 
     if ( (GNUNET_OK !=
           ids->plugin->start (ids->plugin->cls,
@@ -276,15 +285,18 @@ insert_deposit_run (void *cls,
                                           &dph,
                                           &agh)) ||
          (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
-          ids->plugin->insert_deposit (ids->plugin->cls,
-                                       ids->exchange_timestamp,
-                                       &deposit)) ||
+          ids->plugin->do_deposit (ids->plugin->cls,
+                                   &bd,
+                                   &ids->exchange_timestamp,
+                                   &balance_ok,
+                                   &bad_index,
+                                   &ctr_conflict)) ||
          (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
           ids->plugin->commit (ids->plugin->cls)) )
     {
       GNUNET_break (0);
       ids->plugin->rollback (ids->plugin->cls);
-      GNUNET_free (deposit.receiver_wire_account);
+      GNUNET_free (receiver_wire_account);
       TALER_denom_pub_free (&dpk);
       TALER_denom_priv_free (&denom_priv);
       TALER_TESTING_interpreter_fail (is);
@@ -295,7 +307,7 @@ insert_deposit_run (void *cls,
   TALER_denom_sig_free (&deposit.coin.denom_sig);
   TALER_denom_pub_free (&dpk);
   TALER_denom_priv_free (&denom_priv);
-  GNUNET_free (deposit.receiver_wire_account);
+  GNUNET_free (receiver_wire_account);
   TALER_TESTING_interpreter_next (is);
 }
 

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