gnunet-svn
[Top][All Lists]
Advanced

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

[taler-donau] 01/01: initial commit based on Taler exchange as a templat


From: gnunet
Subject: [taler-donau] 01/01: initial commit based on Taler exchange as a template
Date: Tue, 12 Sep 2023 16:46:07 +0200

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

grothoff pushed a commit to branch master
in repository donau.

commit 5090b821de7b755aaa431def62a5a8802a3cdd3b
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Tue Sep 12 16:45:57 2023 +0200

    initial commit based on Taler exchange as a template
---
 ABOUT-NLS                                          | 1379 ++++
 AUTHORS                                            |    6 +
 COPYING                                            |  661 ++
 ChangeLog                                          |    0
 INSTALL                                            |  368 +
 Makefile.am                                        |   23 +
 NEWS                                               |    0
 README                                             |  119 +
 README.1st                                         |   19 +
 aclocal.m4                                         | 1518 ++++
 bootstrap                                          |   42 +
 ci/Containerfile                                   |   71 +
 ci/jobs/0-codespell/config.ini                     |    5 +
 ci/jobs/0-codespell/dictionary.txt                 |   44 +
 ci/jobs/0-codespell/job.sh                         |    6 +
 ci/jobs/1-build/build.sh                           |    9 +
 ci/jobs/1-build/job.sh                             |    6 +
 ci/jobs/2-test/job.sh                              |    6 +
 ci/jobs/2-test/test.sh                             |   37 +
 ci/jobs/3-docs/docs.sh                             |   11 +
 ci/jobs/3-docs/job.sh                              |    6 +
 ci/jobs/4-deb-package/install-fix.patch            |   13 +
 ci/jobs/4-deb-package/job.sh                       |   24 +
 configure.ac                                       |  541 ++
 contrib/.gitignore                                 |    2 +
 contrib/Makefile.am                                |   61 +
 contrib/coverage.sh                                |   10 +
 contrib/taler-exchange-dbconfig                    |  137 +
 contrib/uncrustify-mode.el                         |  161 +
 contrib/uncrustify.cfg                             |   95 +
 contrib/uncrustify.el                              |   13 +
 contrib/uncrustify.sh                              |   14 +
 contrib/uncrustify_precommit                       |   34 +
 contrib/update-pp.sh                               |   38 +
 contrib/update-tos.sh                              |   39 +
 debian/.gitignore                                  |   23 +
 debian/README-packaging.md                         |    7 +
 debian/changelog                                   |  316 +
 debian/control                                     |  155 +
 debian/copyright                                   |  699 ++
 debian/etc-libtalerexchange/taler/overrides.conf   |    1 +
 debian/etc-libtalerexchange/taler/taler.conf       |   49 +
 .../apache2/sites-available/taler-auditor.conf     |    4 +
 .../nginx/sites-available/taler-auditor            |   18 +
 .../taler/conf.d/auditor-system.conf               |   12 +
 .../taler/secrets/auditor-db.secret.conf           |   10 +
 .../apache2/sites-available/taler-exchange.conf    |    4 +
 .../nginx/sites-available/taler-exchange           |   17 +
 .../taler/conf.d/exchange-business.conf            |   50 +
 .../taler/conf.d/exchange-coins.conf               |   33 +
 .../taler/conf.d/exchange-system.conf              |   13 +
 .../exchange-accountcredentials-1.secret.conf      |   17 +
 .../taler/secrets/exchange-db.secret.conf          |   10 +
 debian/libtalerexchange-dev.install                |   35 +
 debian/libtalerexchange.dirs                       |    1 +
 debian/libtalerexchange.install                    |   10 +
 debian/libtalerexchange.tmpfiles                   |    2 +
 debian/patches/0001-Dont_copy_license_file.patch   |   22 +
 debian/patches/series                              |    1 +
 debian/po/POTFILES.in                              |    1 +
 debian/rules                                       |   66 +
 debian/source/format                               |    1 +
 debian/source/options                              |    3 +
 debian/taler-auditor.install                       |   21 +
 debian/taler-auditor.postinst                      |   41 +
 debian/taler-auditor.postrm                        |   29 +
 debian/taler-auditor.taler-auditor-httpd.service   |   12 +
 debian/taler-auditor.tmpfiles                      |    2 +
 debian/taler-exchange-database.install             |    8 +
 debian/taler-exchange-offline.install              |    2 +
 debian/taler-exchange-offline.postinst             |   38 +
 debian/taler-exchange-offline.tmpfiles             |    2 +
 debian/taler-exchange.README.Debian                |   34 +
 debian/taler-exchange.docs                         |    1 +
 debian/taler-exchange.install                      |   39 +
 debian/taler-exchange.links                        |    1 +
 debian/taler-exchange.lintan-overrides             |    3 +
 debian/taler-exchange.postinst                     |   81 +
 debian/taler-exchange.postrm                       |   48 +
 debian/taler-exchange.prerm                        |   11 +
 ...aler-exchange.taler-exchange-aggregator.service |   18 +
 ...ler-exchange.taler-exchange-aggregator@.service |   17 +
 .../taler-exchange.taler-exchange-closer.service   |   18 +
 .../taler-exchange.taler-exchange-expire.service   |   18 +
 debian/taler-exchange.taler-exchange-httpd.service |   33 +
 debian/taler-exchange.taler-exchange-httpd.socket  |   14 +
 .../taler-exchange.taler-exchange-httpd@.service   |   27 +
 debian/taler-exchange.taler-exchange-httpd@.socket |   14 +
 ...taler-exchange.taler-exchange-secmod-cs.service |   18 +
 ...er-exchange.taler-exchange-secmod-eddsa.service |   19 +
 ...aler-exchange.taler-exchange-secmod-rsa.service |   18 +
 .../taler-exchange.taler-exchange-transfer.service |   18 +
 ...taler-exchange.taler-exchange-wirewatch.service |   18 +
 ...aler-exchange.taler-exchange-wirewatch@.service |   18 +
 debian/taler-exchange.taler-exchange.slice         |    7 +
 debian/taler-exchange.taler-exchange.target        |   13 +
 debian/taler-exchange.tmpfiles                     |    8 +
 debian/upstream/metadata                           |    4 +
 debian/upstream/signing-key.asc                    |  637 ++
 debian/watch                                       |    3 +
 doc/.gitignore                                     |   29 +
 doc/Makefile.am                                    |   74 +
 doc/doxygen/.gitignore                             |    2 +
 doc/doxygen/Makefile.am                            |   18 +
 doc/doxygen/Makefile.in                            |  550 ++
 doc/doxygen/logo.svg                               |   87 +
 doc/doxygen/taler.doxy                             | 2699 ++++++++
 m4/.gitignore                                      |    6 +
 m4/ax_compare_version.m4                           |  177 +
 m4/ax_have_epoll.m4                                |  104 +
 m4/ax_lib_postgresql.m4                            |  247 +
 m4/ax_prog_doxygen.m4                              |  586 ++
 m4/codeset.m4                                      |   24 +
 m4/extern-inline.m4                                |  102 +
 m4/fcntl-o.m4                                      |  134 +
 m4/gettext.m4                                      |  420 ++
 m4/glibc2.m4                                       |   31 +
 m4/glibc21.m4                                      |   34 +
 m4/iconv.m4                                        |  271 +
 m4/intdiv0.m4                                      |   87 +
 m4/intl.m4                                         |  304 +
 m4/intldir.m4                                      |   19 +
 m4/intlmacosx.m4                                   |   56 +
 m4/intmax.m4                                       |   36 +
 m4/inttypes-pri.m4                                 |   42 +
 m4/inttypes_h.m4                                   |   29 +
 m4/lcmessage.m4                                    |   35 +
 m4/lib-ld.m4                                       |  119 +
 m4/lib-link.m4                                     |  777 +++
 m4/lib-prefix.m4                                   |  224 +
 m4/libcurl.m4                                      |  251 +
 m4/libgcrypt.m4                                    |  122 +
 m4/libgnurl.m4                                     |  266 +
 m4/lock.m4                                         |   42 +
 m4/longlong.m4                                     |  113 +
 m4/m4_ax_python_module.m4                          |   56 +
 m4/mhd.m4                                          |   49 +
 m4/nls.m4                                          |   32 +
 m4/po.m4                                           |  453 ++
 m4/printf-posix.m4                                 |   48 +
 m4/progtest.m4                                     |   91 +
 m4/size_max.m4                                     |   79 +
 m4/stdint_h.m4                                     |   27 +
 m4/threadlib.m4                                    |  389 ++
 m4/uintmax_t.m4                                    |   30 +
 m4/visibility.m4                                   |   77 +
 m4/wchar_t.m4                                      |   24 +
 m4/wint_t.m4                                       |   32 +
 m4/xsize.m4                                        |   12 +
 po/ChangeLog                                       |   12 +
 po/Makefile.in.in                                  |  483 ++
 po/Makevars                                        |   78 +
 po/Makevars.template                               |   78 +
 po/POTFILES.in                                     |    2 +
 po/Rules-quot                                      |   58 +
 po/boldquot.sed                                    |   10 +
 po/en@boldquot.header                              |   25 +
 po/en@quot.header                                  |   22 +
 po/insert-header.sin                               |   23 +
 po/quot.sed                                        |    6 +
 po/remove-potcdate.sin                             |   19 +
 po/stamp-po                                        |    1 +
 po/taler-exchange.pot                              | 2374 +++++++
 src/.gitignore                                     |    8 +
 src/Makefile.am                                    |   37 +
 src/exchange-tools/.gitignore                      |    3 +
 src/exchange-tools/Makefile.am                     |   70 +
 src/exchange-tools/coins.conf                      |   25 +
 src/exchange-tools/exchange-offline.conf           |   15 +
 src/exchange-tools/taler-exchange-dbinit.c         |  202 +
 src/exchange-tools/taler-exchange-offline.c        | 5429 +++++++++++++++
 src/exchange/.gitignore                            |   13 +
 src/exchange/Makefile.am                           |  232 +
 src/exchange/exchange.conf                         |  138 +
 src/exchange/taler-exchange-httpd.c                | 2568 +++++++
 src/exchange/taler-exchange-httpd.h                |  317 +
 src/exchange/taler-exchange-httpd_batch-deposit.c  |  729 ++
 src/exchange/taler-exchange-httpd_batch-deposit.h  |   49 +
 src/exchange/taler-exchange-httpd_batch-withdraw.c |  930 +++
 src/exchange/taler-exchange-httpd_batch-withdraw.h |   48 +
 src/exchange/taler-exchange-httpd_common_deposit.c |  267 +
 src/exchange/taler-exchange-httpd_common_deposit.h |  130 +
 src/exchange/taler-exchange-httpd_config.c         |   55 +
 src/exchange/taler-exchange-httpd_config.h         |   58 +
 src/exchange/taler-exchange-httpd_csr.c            |  340 +
 src/exchange/taler-exchange-httpd_csr.h            |   56 +
 src/exchange/taler-exchange-httpd_db.c             |  173 +
 src/exchange/taler-exchange-httpd_db.h             |  106 +
 src/exchange/taler-exchange-httpd_deposits_get.c   |  519 ++
 src/exchange/taler-exchange-httpd_deposits_get.h   |   50 +
 src/exchange/taler-exchange-httpd_keys.c           | 4377 ++++++++++++
 src/exchange/taler-exchange-httpd_keys.h           |  602 ++
 src/exchange/taler-exchange-httpd_metrics.c        |  165 +
 src/exchange/taler-exchange-httpd_metrics.h        |  136 +
 src/exchange/taler-exchange-httpd_mhd.c            |   66 +
 src/exchange/taler-exchange-httpd_mhd.h            |   57 +
 src/exchange/taler-exchange-httpd_reserves_close.c |  448 ++
 src/exchange/taler-exchange-httpd_reserves_close.h |   41 +
 src/exchange/taler-exchange-httpd_reserves_get.c   |  274 +
 src/exchange/taler-exchange-httpd_reserves_get.h   |   53 +
 .../taler-exchange-httpd_reserves_history.c        |  295 +
 .../taler-exchange-httpd_reserves_history.h        |   42 +
 src/exchange/taler-exchange-httpd_reserves_open.c  |  468 ++
 src/exchange/taler-exchange-httpd_reserves_open.h  |   41 +
 .../taler-exchange-httpd_reserves_status.c         |  243 +
 .../taler-exchange-httpd_reserves_status.h         |   43 +
 src/exchange/taler-exchange-httpd_responses.c      | 1190 ++++
 src/exchange/taler-exchange-httpd_responses.h      |  229 +
 src/exchange/taler-exchange-httpd_terms.c          |   81 +
 src/exchange/taler-exchange-httpd_terms.h          |   65 +
 src/exchange/taler-exchange-httpd_withdraw.c       |  700 ++
 src/exchange/taler-exchange-httpd_withdraw.h       |   47 +
 src/exchange/test_taler_exchange_httpd.conf        |  142 +
 src/exchange/test_taler_exchange_httpd.sh          |   81 +
 src/exchange/test_taler_exchange_unix.conf         |  140 +
 src/exchangedb/.gitignore                          |   16 +
 src/exchangedb/0002-account_merges.sql             |  133 +
 src/exchangedb/0002-batch_deposits.sql             |  154 +
 src/exchangedb/0002-coin_deposits.sql              |  157 +
 src/exchangedb/0002-cs_nonce_locks.sql             |   97 +
 src/exchangedb/0002-denomination_revocations.sql   |   23 +
 src/exchangedb/0002-denominations.sql              |   45 +
 src/exchangedb/0002-exchange_sign_keys.sql         |   36 +
 src/exchangedb/0002-known_coins.sql                |  136 +
 src/exchangedb/0002-reserves.sql                   |  152 +
 src/exchangedb/0002-reserves_in.sql                |  142 +
 src/exchangedb/0002-reserves_out.sql               |  239 +
 src/exchangedb/0002-signkey_revocations.sql        |   23 +
 src/exchangedb/0002-wad_in_entries.sql             |  185 +
 src/exchangedb/0002-wad_out_entries.sql            |  185 +
 src/exchangedb/0002-wads_in.sql                    |  107 +
 src/exchangedb/0002-wads_out.sql                   |  128 +
 src/exchangedb/Makefile.am                         |  378 +
 src/exchangedb/bench-db-postgres.conf              |   14 +
 src/exchangedb/benchmark-0001.sql                  |   55 +
 src/exchangedb/drop.sql                            |   26 +
 src/exchangedb/exchange-0001.sql                   |  296 +
 src/exchangedb/exchange-0002.sql.in                |  101 +
 src/exchangedb/exchange_do_account_merge.sql       |   15 +
 src/exchangedb/exchange_do_amount_specific.sql     |   78 +
 src/exchangedb/exchange_do_batch_coin_known.sql    |  469 ++
 .../exchange_do_batch_reserves_update.sql          |   72 +
 src/exchangedb/exchange_do_batch_withdraw.sql      |  123 +
 .../exchange_do_batch_withdraw_insert.sql          |  120 +
 src/exchangedb/exchange_do_deposit.sql             |  207 +
 src/exchangedb/exchange_do_gc.sql                  |  138 +
 src/exchangedb/exchange_do_reserves_in_insert.sql  |  123 +
 src/exchangedb/exchange_do_withdraw.sql            |  213 +
 src/exchangedb/exchange_do_withdraw.sql~           |  197 +
 src/exchangedb/exchangedb-postgres.conf            |    9 +
 src/exchangedb/exchangedb.conf                     |   36 +
 src/exchangedb/exchangedb_plugin.c                 |   73 +
 src/exchangedb/exchangedb_transactions.c           |  190 +
 src/exchangedb/list.txt                            |   20 +
 src/exchangedb/pg_activate_signing_key.c           |   58 +
 src/exchangedb/pg_activate_signing_key.h           |   44 +
 src/exchangedb/pg_add_denomination_key.c           |   86 +
 src/exchangedb/pg_add_denomination_key.h           |   46 +
 src/exchangedb/pg_batch_ensure_coin_known.c        |  470 ++
 src/exchangedb/pg_batch_ensure_coin_known.h        |   47 +
 src/exchangedb/pg_commit.c                         |   58 +
 src/exchangedb/pg_commit.h                         |   37 +
 src/exchangedb/pg_count_known_coins.c              |   63 +
 src/exchangedb/pg_count_known_coins.h              |   39 +
 src/exchangedb/pg_create_tables.c                  |   73 +
 src/exchangedb/pg_create_tables.h                  |   44 +
 src/exchangedb/pg_do_batch_withdraw.c              |   85 +
 src/exchangedb/pg_do_batch_withdraw.h              |   57 +
 src/exchangedb/pg_do_batch_withdraw_insert.c       |   77 +
 src/exchangedb/pg_do_batch_withdraw_insert.h       |   52 +
 src/exchangedb/pg_do_deposit.c                     |  116 +
 src/exchangedb/pg_do_deposit.h                     |   51 +
 src/exchangedb/pg_do_withdraw.c                    |   95 +
 src/exchangedb/pg_do_withdraw.h                    |   59 +
 src/exchangedb/pg_drop_tables.c                    |   58 +
 src/exchangedb/pg_drop_tables.h                    |   38 +
 src/exchangedb/pg_ensure_coin_known.c              |  156 +
 src/exchangedb/pg_ensure_coin_known.h              |   45 +
 src/exchangedb/pg_event_listen.c                   |   53 +
 src/exchangedb/pg_event_listen.h                   |   45 +
 src/exchangedb/pg_event_listen_cancel.c            |   36 +
 src/exchangedb/pg_event_listen_cancel.h            |   38 +
 src/exchangedb/pg_event_notify.c                   |   41 +
 src/exchangedb/pg_event_notify.h                   |   42 +
 src/exchangedb/pg_gc.c                             |   80 +
 src/exchangedb/pg_gc.h                             |   39 +
 src/exchangedb/pg_get_coin_denomination.c          |   69 +
 src/exchangedb/pg_get_coin_denomination.h          |   43 +
 src/exchangedb/pg_get_coin_transactions.c          |  948 +++
 src/exchangedb/pg_get_coin_transactions.h          |   44 +
 src/exchangedb/pg_get_denomination_info.c          |   91 +
 src/exchangedb/pg_get_denomination_info.h          |   41 +
 src/exchangedb/pg_get_denomination_revocation.c    |   63 +
 src/exchangedb/pg_get_denomination_revocation.h    |   45 +
 src/exchangedb/pg_get_expired_reserves.c           |  174 +
 src/exchangedb/pg_get_expired_reserves.h           |   45 +
 src/exchangedb/pg_get_known_coin.c                 |   71 +
 src/exchangedb/pg_get_known_coin.h                 |   40 +
 src/exchangedb/pg_get_ready_deposit.c              |   74 +
 src/exchangedb/pg_get_ready_deposit.h              |   46 +
 src/exchangedb/pg_get_reserve_balance.c            |   55 +
 src/exchangedb/pg_get_reserve_balance.h            |   40 +
 src/exchangedb/pg_get_reserve_by_h_blind.c         |   63 +
 src/exchangedb/pg_get_reserve_by_h_blind.h         |   44 +
 src/exchangedb/pg_get_withdraw_info.c              |   79 +
 src/exchangedb/pg_get_withdraw_info.h              |   43 +
 src/exchangedb/pg_have_deposit2.c                  |  117 +
 src/exchangedb/pg_have_deposit2.h                  |   53 +
 src/exchangedb/pg_helper.h                         |  149 +
 src/exchangedb/pg_insert_denomination_info.c       |  101 +
 src/exchangedb/pg_insert_denomination_info.h       |   42 +
 src/exchangedb/pg_insert_denomination_revocation.c |   54 +
 src/exchangedb/pg_insert_denomination_revocation.h |   42 +
 src/exchangedb/pg_insert_signkey_revocation.c      |   53 +
 src/exchangedb/pg_insert_signkey_revocation.h      |   41 +
 src/exchangedb/pg_iterate_active_signkeys.c        |  144 +
 src/exchangedb/pg_iterate_active_signkeys.h        |   43 +
 src/exchangedb/pg_iterate_denomination_info.c      |  180 +
 src/exchangedb/pg_iterate_denomination_info.h      |   41 +
 src/exchangedb/pg_iterate_denominations.c          |  172 +
 src/exchangedb/pg_iterate_denominations.h          |   44 +
 src/exchangedb/pg_lookup_denomination_key.c        |   83 +
 src/exchangedb/pg_lookup_denomination_key.h        |   41 +
 src/exchangedb/pg_lookup_signing_key.c             |   64 +
 src/exchangedb/pg_lookup_signing_key.h             |   42 +
 src/exchangedb/pg_lookup_signkey_revocation.c      |   59 +
 src/exchangedb/pg_lookup_signkey_revocation.h      |   42 +
 src/exchangedb/pg_preflight.c                      |   69 +
 src/exchangedb/pg_preflight.h                      |   44 +
 src/exchangedb/pg_reserves_get.c                   |   61 +
 src/exchangedb/pg_reserves_get.h                   |   40 +
 src/exchangedb/pg_reserves_get_origin.c            |   57 +
 src/exchangedb/pg_reserves_get_origin.h            |   41 +
 src/exchangedb/pg_reserves_in_insert.c             |  372 +
 src/exchangedb/pg_reserves_in_insert.h             |   47 +
 src/exchangedb/pg_reserves_update.c                |   53 +
 src/exchangedb/pg_reserves_update.h                |   40 +
 src/exchangedb/pg_rollback.c                       |   50 +
 src/exchangedb/pg_rollback.h                       |   36 +
 src/exchangedb/pg_start.c                          |   56 +
 src/exchangedb/pg_start.h                          |   40 +
 src/exchangedb/pg_start_read_committed.c           |   56 +
 src/exchangedb/pg_start_read_committed.h           |   39 +
 src/exchangedb/pg_start_read_only.c                |   57 +
 src/exchangedb/pg_start_read_only.h                |   40 +
 src/exchangedb/pg_template.c                       |   26 +
 src/exchangedb/pg_template.h                       |   29 +
 src/exchangedb/pg_template.sh                      |   21 +
 src/exchangedb/plugin_exchangedb_common.c          |  199 +
 src/exchangedb/plugin_exchangedb_common.h          |   51 +
 src/exchangedb/plugin_exchangedb_postgres.c        |  806 +++
 src/exchangedb/procedures.sql.in                   |   50 +
 src/exchangedb/test_exchangedb.c                   | 2485 +++++++
 src/exchangedb/versioning.sql                      |  294 +
 src/include/.gitignore                             |    1 +
 src/include/Makefile.am                            |   36 +
 src/include/taler_crypto_lib.h                     | 6091 ++++++++++++++++
 src/include/taler_exchange_service.h               | 7261 ++++++++++++++++++++
 src/include/taler_exchangedb_lib.h                 |  199 +
 src/include/taler_exchangedb_plugin.h              | 7133 +++++++++++++++++++
 src/include/taler_testing_lib.h                    | 2762 ++++++++
 src/include/taler_util.h                           |  689 ++
 src/lib/.gitignore                                 |    1 +
 src/lib/Makefile.am                                |  128 +
 src/lib/exchange_api_batch_deposit.c               |  784 +++
 src/lib/exchange_api_batch_withdraw.c              |  456 ++
 src/lib/exchange_api_batch_withdraw2.c             |  555 ++
 src/lib/exchange_api_common.c                      | 2448 +++++++
 src/lib/exchange_api_common.h                      |  219 +
 src/lib/exchange_api_csr_withdraw.c                |  279 +
 src/lib/exchange_api_curl_defaults.c               |   62 +
 src/lib/exchange_api_curl_defaults.h               |   40 +
 src/lib/exchange_api_deposits_get.c                |  395 ++
 src/lib/exchange_api_handle.c                      | 2316 +++++++
 src/lib/exchange_api_handle.h                      |   64 +
 src/lib/exchange_api_reserves_close.c              |  373 +
 src/lib/exchange_api_reserves_get.c                |  266 +
 src/lib/exchange_api_reserves_history.c            |  363 +
 src/lib/exchange_api_reserves_open.c               |  591 ++
 src/lib/exchange_api_reserves_status.c             |  336 +
 src/lib/exchange_api_transfers_get.c               |  399 ++
 src/lib/exchange_api_withdraw.c                    |  364 +
 src/lib/exchange_api_withdraw2.c                   |  498 ++
 src/testing/.gitignore                             |   59 +
 src/testing/Makefile.am                            |  589 ++
 src/testing/coins-cs.conf                          |  118 +
 src/testing/coins-rsa.conf                         |  128 +
 src/testing/test_exchange_api-cs.conf              |    4 +
 src/testing/test_exchange_api-rsa.conf             |    4 +
 src/testing/test_exchange_api.c                    | 1295 ++++
 src/testing/test_exchange_api.conf                 |   91 +
 src/testing/testing_api_cmd_batch.c                |  235 +
 src/testing/testing_api_cmd_batch_deposit.c        |  626 ++
 src/testing/testing_api_cmd_batch_withdraw.c       |  533 ++
 src/testing/testing_api_cmd_common.c               |  226 +
 src/testing/testing_api_cmd_deposit.c              |  791 +++
 src/testing/testing_api_cmd_deposits_get.c         |  375 +
 src/testing/testing_api_cmd_get_exchange.c         |  411 ++
 src/testing/testing_api_cmd_insert_deposit.c       |  388 ++
 .../testing_api_cmd_offline_sign_extensions.c      |  164 +
 .../testing_api_cmd_offline_sign_global_fees.c     |  230 +
 src/testing/testing_api_cmd_offline_sign_keys.c    |  165 +
 src/testing/testing_api_cmd_reserve_close.c        |  260 +
 src/testing/testing_api_cmd_reserve_get.c          |  390 ++
 src/testing/testing_api_cmd_reserve_history.c      |  455 ++
 src/testing/testing_api_cmd_reserve_open.c         |  349 +
 src/testing/testing_api_cmd_reserve_status.c       |  397 ++
 src/testing/testing_api_cmd_revoke.c               |  207 +
 src/testing/testing_api_cmd_revoke_denom_key.c     |  256 +
 src/testing/testing_api_cmd_revoke_sign_key.c      |  256 +
 src/testing/testing_api_cmd_stat.c                 |  168 +
 src/testing/testing_api_cmd_transfer_get.c         |  406 ++
 src/testing/testing_api_cmd_wait.c                 |  134 +
 src/testing/testing_api_cmd_withdraw.c             |  696 ++
 src/testing/testing_api_misc.c                     |  377 +
 src/util/.gitignore                                |   12 +
 src/util/Makefile.am                               |  196 +
 src/util/conversion.c                              |  405 ++
 src/util/crypto_confirmation.c                     |  286 +
 src/util/crypto_contract.c                         |  661 ++
 src/util/exchange_signatures.c                     | 1877 +++++
 src/util/merchant_signatures.c                     |  352 +
 src/util/offline_signatures.c                      | 1388 ++++
 src/util/os_installation.c                         |   70 +
 src/util/paths.conf                                |   29 +
 src/util/taler-config.c                            |   73 +
 src/util/taler-config.in                           |   13 +
 src/util/wallet_signatures.c                       | 1843 +++++
 taler_config.h.in                                  |  336 +
 uncrustify.cfg                                     |    1 +
 430 files changed, 113268 insertions(+)

diff --git a/ABOUT-NLS b/ABOUT-NLS
new file mode 100644
index 0000000..3cc8286
--- /dev/null
+++ b/ABOUT-NLS
@@ -0,0 +1,1379 @@
+1 Notes on the Free Translation Project
+***************************************
+
+Free software is going international!  The Free Translation Project is a
+way to get maintainers of free software, translators, and users all
+together, so that free software will gradually become able to speak many
+languages.  A few packages already provide translations for their
+messages.
+
+   If you found this 'ABOUT-NLS' file inside a distribution, you may
+assume that the distributed package does use GNU 'gettext' internally,
+itself available at your nearest GNU archive site.  But you do _not_
+need to install GNU 'gettext' prior to configuring, installing or using
+this package with messages translated.
+
+   Installers will find here some useful hints.  These notes also
+explain how users should proceed for getting the programs to use the
+available translations.  They tell how people wanting to contribute and
+work on translations can contact the appropriate team.
+
+1.1 INSTALL Matters
+===================
+
+Some packages are "localizable" when properly installed; the programs
+they contain can be made to speak your own native language.  Most such
+packages use GNU 'gettext'.  Other packages have their own ways to
+internationalization, predating GNU 'gettext'.
+
+   By default, this package will be installed to allow translation of
+messages.  It will automatically detect whether the system already
+provides the GNU 'gettext' functions.  Installers may use special
+options at configuration time for changing the default behaviour.  The
+command:
+
+     ./configure --disable-nls
+
+will _totally_ disable translation of messages.
+
+   When you already have GNU 'gettext' installed on your system and run
+configure without an option for your new package, 'configure' will
+probably detect the previously built and installed 'libintl' library and
+will decide to use it.  If not, you may have to to use the
+'--with-libintl-prefix' option to tell 'configure' where to look for it.
+
+   Internationalized packages usually have many 'po/LL.po' files, where
+LL gives an ISO 639 two-letter code identifying the language.  Unless
+translations have been forbidden at 'configure' time by using the
+'--disable-nls' switch, all available translations are installed
+together with the package.  However, the environment variable 'LINGUAS'
+may be set, prior to configuration, to limit the installed set.
+'LINGUAS' should then contain a space separated list of two-letter
+codes, stating which languages are allowed.
+
+1.2 Using This Package
+======================
+
+As a user, if your language has been installed for this package, you
+only have to set the 'LANG' environment variable to the appropriate
+'LL_CC' combination.  If you happen to have the 'LC_ALL' or some other
+'LC_xxx' environment variables set, you should unset them before setting
+'LANG', otherwise the setting of 'LANG' will not have the desired
+effect.  Here 'LL' is an ISO 639 two-letter language code, and 'CC' is
+an ISO 3166 two-letter country code.  For example, let's suppose that
+you speak German and live in Germany.  At the shell prompt, merely
+execute 'setenv LANG de_DE' (in 'csh'), 'export LANG; LANG=de_DE' (in
+'sh') or 'export LANG=de_DE' (in 'bash').  This can be done from your
+'.login' or '.profile' file, once and for all.
+
+   You might think that the country code specification is redundant.
+But in fact, some languages have dialects in different countries.  For
+example, 'de_AT' is used for Austria, and 'pt_BR' for Brazil.  The
+country code serves to distinguish the dialects.
+
+   The locale naming convention of 'LL_CC', with 'LL' denoting the
+language and 'CC' denoting the country, is the one use on systems based
+on GNU libc.  On other systems, some variations of this scheme are used,
+such as 'LL' or 'LL_CC.ENCODING'.  You can get the list of locales
+supported by your system for your language by running the command
+'locale -a | grep '^LL''.
+
+   Not all programs have translations for all languages.  By default, an
+English message is shown in place of a nonexistent translation.  If you
+understand other languages, you can set up a priority list of languages.
+This is done through a different environment variable, called
+'LANGUAGE'.  GNU 'gettext' gives preference to 'LANGUAGE' over 'LANG'
+for the purpose of message handling, but you still need to have 'LANG'
+set to the primary language; this is required by other parts of the
+system libraries.  For example, some Swedish users who would rather read
+translations in German than English for when Swedish is not available,
+set 'LANGUAGE' to 'sv:de' while leaving 'LANG' to 'sv_SE'.
+
+   Special advice for Norwegian users: The language code for Norwegian
+bokma*l changed from 'no' to 'nb' recently (in 2003).  During the
+transition period, while some message catalogs for this language are
+installed under 'nb' and some older ones under 'no', it's recommended
+for Norwegian users to set 'LANGUAGE' to 'nb:no' so that both newer and
+older translations are used.
+
+   In the 'LANGUAGE' environment variable, but not in the 'LANG'
+environment variable, 'LL_CC' combinations can be abbreviated as 'LL' to
+denote the language's main dialect.  For example, 'de' is equivalent to
+'de_DE' (German as spoken in Germany), and 'pt' to 'pt_PT' (Portuguese
+as spoken in Portugal) in this context.
+
+1.3 Translating Teams
+=====================
+
+For the Free Translation Project to be a success, we need interested
+people who like their own language and write it well, and who are also
+able to synergize with other translators speaking the same language.
+Each translation team has its own mailing list.  The up-to-date list of
+teams can be found at the Free Translation Project's homepage,
+'http://translationproject.org/', in the "Teams" area.
+
+   If you'd like to volunteer to _work_ at translating messages, you
+should become a member of the translating team for your own language.
+The subscribing address is _not_ the same as the list itself, it has
+'-request' appended.  For example, speakers of Swedish can send a
+message to 'sv-request@li.org', having this message body:
+
+     subscribe
+
+   Keep in mind that team members are expected to participate _actively_
+in translations, or at solving translational difficulties, rather than
+merely lurking around.  If your team does not exist yet and you want to
+start one, or if you are unsure about what to do or how to get started,
+please write to 'coordinator@translationproject.org' to reach the
+coordinator for all translator teams.
+
+   The English team is special.  It works at improving and uniformizing
+the terminology in use.  Proven linguistic skills are praised more than
+programming skills, here.
+
+1.4 Available Packages
+======================
+
+Languages are not equally supported in all packages.  The following
+matrix shows the current state of internationalization, as of Jun 2014.
+The matrix shows, in regard of each package, for which languages PO
+files have been submitted to translation coordination, with a
+translation percentage of at least 50%.
+
+     Ready PO files       af am an ar as ast az be bg bn bn_IN bs ca crh cs
+                        +---------------------------------------------------+
+     a2ps               |                       []                []     [] |
+     aegis              |                                                   |
+     anubis             |                                                   |
+     aspell             |                []                       []     [] |
+     bash               |                          []             []     [] |
+     bfd                |                                                   |
+     binutils           |                                         []        |
+     bison              |                                                   |
+     bison-runtime      |                []                                 |
+     buzztrax           |                                                [] |
+     ccd2cue            |                                                   |
+     ccide              |                                                   |
+     cflow              |                                                   |
+     clisp              |                                                   |
+     coreutils          |                                         []     [] |
+     cpio               |                                                   |
+     cppi               |                                                   |
+     cpplib             |                                         []        |
+     cryptsetup         |                                                [] |
+     datamash           |                                                   |
+     denemo             |                                         []     [] |
+     dfarc              |                                         []        |
+     dialog             |       []                                []     [] |
+     dico               |                                                   |
+     diffutils          |                                                [] |
+     dink               |                                         []        |
+     direvent           |                                                   |
+     doodle             |                                                [] |
+     dos2unix           |                                                   |
+     dos2unix-man       |                                                   |
+     e2fsprogs          |                                         []     [] |
+     enscript           |                                         []        |
+     exif               |                                                [] |
+     fetchmail          |                                         []     [] |
+     findutils          |                                                [] |
+     flex               |                                         []        |
+     freedink           |                                         []     [] |
+     fusionforge        |                                                   |
+     gas                |                                                   |
+     gawk               |                                         []        |
+     gcal               |                                         []        |
+     gcc                |                                                   |
+     gdbm               |                                                   |
+     gettext-examples   | []             []        []             []     [] |
+     gettext-runtime    |                          []             []     [] |
+     gettext-tools      |                          []             []        |
+     gjay               |                                                   |
+     glunarclock        |                []        []                    [] |
+     gnubiff            |                                                [] |
+     gnubik             |          []                                       |
+     gnucash            |          ()              ()             []        |
+     gnuchess           |                                                   |
+     gnulib             |                                                [] |
+     gnunet             |                                                   |
+     gnunet-gtk         |                                                   |
+     gold               |                                                   |
+     gphoto2            |                                                [] |
+     gprof              |                          []                       |
+     gramadoir          |                                                   |
+     grep               |                          []             []     [] |
+     grub               |                                         []        |
+     gsasl              |                                                   |
+     gss                |                                                   |
+     gst-plugins-bad    |                          []                    [] |
+     gst-plugins-base   |                          []             []     [] |
+     gst-plugins-good   |                          []             []     [] |
+     gst-plugins-ugly   |                          []             []     [] |
+     gstreamer          |                []        []             []     [] |
+     gtick              |                                                [] |
+     gtkam              |                       []                       [] |
+     gtkspell           | []             []     []                []     [] |
+     guix               |                                                   |
+     guix-packages      |                                                   |
+     gutenprint         |                                         []        |
+     hello              |                                         []        |
+     help2man           |                                                   |
+     help2man-texi      |                                                   |
+     hylafax            |                                                   |
+     idutils            |                                                   |
+     iso_15924          |                                                [] |
+     iso_3166           | []          []        [] [] []  []   [] [] []  [] |
+     iso_3166_2         |                                                   |
+     iso_4217           |                                                [] |
+     iso_639            |             [] []     [] [] []  []      [] []  [] |
+     iso_639_3          |                []                          []     |
+     iso_639_5          |                                                   |
+     jwhois             |                                                   |
+     kbd                |                                                [] |
+     klavaro            |          []              [] []          []     [] |
+     ld                 |                          []                       |
+     leafpad            |                       [] []             []     [] |
+     libc               |                          []             []     [] |
+     libexif            |                       ()                          |
+     libextractor       |                                                   |
+     libgnutls          |                                                [] |
+     libgphoto2         |                                                [] |
+     libgphoto2_port    |                                                [] |
+     libgsasl           |                                                   |
+     libiconv           |                          []                    [] |
+     libidn             |                                                [] |
+     liferea            |          []    []                       []     [] |
+     lilypond           |                                         []     [] |
+     lordsawar          |                                         []        |
+     lprng              |                                                   |
+     lynx               |                                         []     [] |
+     m4                 |                                                [] |
+     mailfromd          |                                                   |
+     mailutils          |                                                   |
+     make               |                                                [] |
+     man-db             |                                         []     [] |
+     man-db-manpages    |                                                   |
+     midi-instruments   |          []                             []     [] |
+     minicom            |                                                [] |
+     mkisofs            |                                                [] |
+     myserver           |                                                [] |
+     nano               |                          []             []     [] |
+     opcodes            |                                                   |
+     parted             |                                                [] |
+     pies               |                                                   |
+     pnmixer            |                                                   |
+     popt               |                                                [] |
+     procps-ng          |                                                   |
+     procps-ng-man      |                                                   |
+     psmisc             |                                                [] |
+     pspp               |                                         []        |
+     pushover           |                                                [] |
+     pwdutils           |                                                   |
+     pyspread           |                                                   |
+     radius             |                                         []        |
+     recode             |                       []                []     [] |
+     recutils           |                                                   |
+     rpm                |                                                   |
+     rush               |                                                   |
+     sarg               |                                                   |
+     sed                |                []        []             []     [] |
+     sharutils          |                                                [] |
+     shishi             |                                                   |
+     skribilo           |                                                   |
+     solfege            |                                         []     [] |
+     solfege-manual     |                                                   |
+     spotmachine        |                                                   |
+     sudo               |                                         []     [] |
+     sudoers            |                                         []     [] |
+     sysstat            |                                                [] |
+     tar                |                          []             []     [] |
+     texinfo            |                                         []     [] |
+     texinfo_document   |                                         []     [] |
+     tigervnc           |                          []                       |
+     tin                |                                                   |
+     tin-man            |                                                   |
+     tracgoogleappsa... |                                                   |
+     trader             |                                                   |
+     util-linux         |                                                [] |
+     ve                 |                                                   |
+     vice               |                                                   |
+     vmm                |                                                   |
+     vorbis-tools       |                                                [] |
+     wastesedge         |                                                   |
+     wcd                |                                                   |
+     wcd-man            |                                                   |
+     wdiff              |                                         []     [] |
+     wget               |                                                [] |
+     wyslij-po          |                                                   |
+     xboard             |                                                   |
+     xdg-user-dirs      | []    []    [] []     [] []     []      [] []  [] |
+     xkeyboard-config   |                          []             []     [] |
+                        +---------------------------------------------------+
+                          af am an ar as ast az be bg bn bn_IN bs ca crh cs
+                           4  0  2  5  3 11   0  8 25  3   3    1 55  4  74
+
+                          da  de  el en en_GB en_ZA eo es et eu fa fi  fr 
+                        +--------------------------------------------------+
+     a2ps               | []  []  []     []         [] [] []       []  []  |
+     aegis              | []  []                       []              []  |
+     anubis             | []  []                       []          []  []  |
+     aspell             | []  []         []         [] []          []  []  |
+     bash               |                           [] []              []  |
+     bfd                | []                           []          []  []  |
+     binutils           |                              []          []  []  |
+     bison              | []  []  []                [] [] []       []  []  |
+     bison-runtime      | []  []  []                [] [] []       []  []  |
+     buzztrax           | []  []                                   []  []  |
+     ccd2cue            | []  []                    []                 []  |
+     ccide              | []  []                    [] []          []  []  |
+     cflow              | []  []                    []             []  []  |
+     clisp              | []  []     []                []              []  |
+     coreutils          | []  []                       [] []           []  |
+     cpio               | []  []                       []          []  []  |
+     cppi               | []  []                    []             []  []  |
+     cpplib             | []  []                    [] []          []  []  |
+     cryptsetup         | []  []                       []          []  []  |
+     datamash           | []  []                    []                 []  |
+     denemo             | []                                               |
+     dfarc              | []  []                    [] []          []  []  |
+     dialog             | []  []  []                [] []    [] [] []  []  |
+     dico               | []  []                                   []  []  |
+     diffutils          | []  []  []                [] []              []  |
+     dink               | []  []                    [] []          []  []  |
+     direvent           | []  []                    []                 []  |
+     doodle             | []  []                    []             []      |
+     dos2unix           | []  []                    [] []              []  |
+     dos2unix-man       |     []                       []              []  |
+     e2fsprogs          | []  []                    [] []              []  |
+     enscript           | []  []         []         []             []  []  |
+     exif               | []  []                    [] []          []  []  |
+     fetchmail          | []  ()  []     []         [] []              []  |
+     findutils          | []  []  []                [] [] []       []  []  |
+     flex               | []  []                    [] []          []  []  |
+     freedink           | []  []  []                [] []    []    []  []  |
+     fusionforge        |     []                       []              []  |
+     gas                |                              []          []  []  |
+     gawk               | []  []                       []          []  []  |
+     gcal               | []  []                       []              []  |
+     gcc                |     []                                           |
+     gdbm               | []  []                    []             []  []  |
+     gettext-examples   | []  []  []                [] []          []  []  |
+     gettext-runtime    | []  []                    [] []          []  []  |
+     gettext-tools      | []  []                       []          []  []  |
+     gjay               |     []                    []             []  []  |
+     glunarclock        | []  []                    []             []  []  |
+     gnubiff            |     ()                    []             []  ()  |
+     gnubik             | []  []                    []             []  []  |
+     gnucash            | []  ()  ()     ()            ()          ()  ()  |
+     gnuchess           |     []                    [] []              []  |
+     gnulib             | []  []                    [] [] []       []  []  |
+     gnunet             |                              []                  |
+     gnunet-gtk         |     []                                           |
+     gold               |                              []          []  []  |
+     gphoto2            | []  ()                    []                 []  |
+     gprof              | []  []                    [] []          []  []  |
+     gramadoir          | []  []                    []             []  []  |
+     grep               | []  []                    [] [] []       []  []  |
+     grub               | []  []                       []          []  []  |
+     gsasl              | []  []                    []             []  []  |
+     gss                | []  []                    []             []  []  |
+     gst-plugins-bad    | []  []                                       []  |
+     gst-plugins-base   | []  []  []                   []          []  []  |
+     gst-plugins-good   | []  []  []                   []    []    []  []  |
+     gst-plugins-ugly   | []  []  []                [] []    []    []  []  |
+     gstreamer          | []  []  []                   []    []    []  []  |
+     gtick              | []  ()                    []             []  []  |
+     gtkam              | []  ()                    [] []          []  []  |
+     gtkspell           | []  []  []                [] []    []    []  []  |
+     guix               | []                        []                     |
+     guix-packages      |                                                  |
+     gutenprint         | []  []                                   []  []  |
+     hello              | []  []  []                [] [] []       []  []  |
+     help2man           | []  []  []                [] []          []  []  |
+     help2man-texi      |     []                       []              []  |
+     hylafax            |     []                       []                  |
+     idutils            | []  []                    []             []  []  |
+     iso_15924          | []  ()                    [] []    ()    []  ()  |
+     iso_3166           | []  ()  []                [] [] [] ()    []  ()  |
+     iso_3166_2         | []  ()                             ()        ()  |
+     iso_4217           | []  ()  []                   [] [] ()    []  ()  |
+     iso_639            | []  ()                    [] []    ()    []  ()  |
+     iso_639_3          |     ()                             ()        ()  |
+     iso_639_5          |     ()                             ()        ()  |
+     jwhois             |     []                    [] []          []  []  |
+     kbd                | []  []  []                [] []              []  |
+     klavaro            | []  []  []                [] []    []        []  |
+     ld                 | []                           []          []  []  |
+     leafpad            | []  []  []                [] []    []    []  []  |
+     libc               | []  []                       []          []  []  |
+     libexif            | []  []         ()            []              []  |
+     libextractor       |     []                                           |
+     libgnutls          |     []                    []             []  []  |
+     libgphoto2         | []  ()                                       []  |
+     libgphoto2_port    | []  ()                       []    []    []  []  |
+     libgsasl           | []  []                    []             []  []  |
+     libiconv           | []  []                    [] [] []       []  []  |
+     libidn             | []  []                    []             []  []  |
+     liferea            | []  ()  []                   []    []    []  []  |
+     lilypond           | []  []  []                [] []              []  |
+     lordsawar          | []  []                                           |
+     lprng              |                                                  |
+     lynx               | []  []                    []    []       []  []  |
+     m4                 | []  []  []                []             []  []  |
+     mailfromd          |                                              []  |
+     mailutils          |     []                       []          []  []  |
+     make               | []  []                       []          []  []  |
+     man-db             | []  []                    []                 []  |
+     man-db-manpages    |     []                                       []  |
+     midi-instruments   | []  []  []                [] [] []    [] []  []  |
+     minicom            | []  []                       []          []  []  |
+     mkisofs            |                           []             []  []  |
+     myserver           |     []                    []             []  []  |
+     nano               | []  []                    [] []    []    []  []  |
+     opcodes            | []  []                       []          []  []  |
+     parted             | []  []                                       []  |
+     pies               |     []                                           |
+     pnmixer            |     []                                       []  |
+     popt               | []  []                    [] []          []  []  |
+     procps-ng          |     []                                       []  |
+     procps-ng-man      |     []                                       []  |
+     psmisc             | []  []  []                []       []    []  []  |
+     pspp               |     []                       []              []  |
+     pushover           |     ()                    [] []              []  |
+     pwdutils           | []  []                                       []  |
+     pyspread           | []  []                                       []  |
+     radius             |                              []              []  |
+     recode             | []  []  []                [] []          []  []  |
+     recutils           |     []                       []          []  []  |
+     rpm                | []  []                    []             []  []  |
+     rush               |     []                                   []  []  |
+     sarg               | []                                           []  |
+     sed                | []  []  []                [] [] []       []  []  |
+     sharutils          |     []                    []    []           []  |
+     shishi             |     []                                   []  []  |
+     skribilo           | []                           []              []  |
+     solfege            | []  []                    [] [] []    [] []  []  |
+     solfege-manual     |     []                    [] [] []           []  |
+     spotmachine        | []  []                    []             []  []  |
+     sudo               | []  []                    [] []          []  []  |
+     sudoers            | []  []  []                []             []  []  |
+     sysstat            | []  []                    [] []          []  []  |
+     tar                | []  []                    [] [] []       []  []  |
+     texinfo            | []  []                    [] []              []  |
+     texinfo_document   |     []                    [] []              []  |
+     tigervnc           | []  []  []                []             []  []  |
+     tin                | []  []                          []           []  |
+     tin-man            |                []                                |
+     tracgoogleappsa... | []  []                    []             []  []  |
+     trader             | []  []         []         []             []  []  |
+     util-linux         | []  []                       []              []  |
+     ve                 |     []                    [] []          []  []  |
+     vice               | ()  ()                                       ()  |
+     vmm                |     []                                   []      |
+     vorbis-tools       | []  []                    []                 []  |
+     wastesedge         | []                                               |
+     wcd                |     []                    [] []          []      |
+     wcd-man            |     []                                           |
+     wdiff              | []  []                    [] [] []       []  []  |
+     wget               |     []                    [] [] []       []  []  |
+     wyslij-po          |     []                    []             []  []  |
+     xboard             | []  []                       []              []  |
+     xdg-user-dirs      | []  []  []                [] [] [] [] [] []  []  |
+     xkeyboard-config   | []  []  []                [] []          []  []  |
+                        +--------------------------------------------------+
+                          da  de  el en en_GB en_ZA eo es et eu fa fi  fr 
+                          119 131 32  1   6     0   94 95 22 13  4 102 139
+
+                          ga gd gl gu he hi hr hu hy ia id is it ja ka kk
+                        +-------------------------------------------------+
+     a2ps               |                   []          []    [] []       |
+     aegis              |                                     []          |
+     anubis             |                   [] []       []    []          |
+     aspell             | []                []          []    [] []       |
+     bash               |                      []       []    [] []       |
+     bfd                |                               []       []       |
+     binutils           |                               []    [] []       |
+     bison              |                   []                            |
+     bison-runtime      | []    []          [] []    [] []    [] []       |
+     buzztrax           |                                                 |
+     ccd2cue            |                      []                         |
+     ccide              |                   [] []                         |
+     cflow              | []                []          []                |
+     clisp              |                                                 |
+     coreutils          |                      []                []       |
+     cpio               | []                [] []       []    [] []       |
+     cppi               |       []          [] []             [] []       |
+     cpplib             |                               []       []       |
+     cryptsetup         |                                     []          |
+     datamash           |                                                 |
+     denemo             |                                     []          |
+     dfarc              |                   [] []             []          |
+     dialog             | [] [] []          [] []    [] [] [] [] []       |
+     dico               |                                                 |
+     diffutils          |                      []       []    [] []       |
+     dink               |                      []                         |
+     direvent           |                      []                         |
+     doodle             | []                                  []          |
+     dos2unix           |                      []                []       |
+     dos2unix-man       |                                                 |
+     e2fsprogs          |                      []       []                |
+     enscript           | []                []          []                |
+     exif               |       []          []          [] [] [] []       |
+     fetchmail          |                               []    [] []       |
+     findutils          | []    []          [] []       []    [] []       |
+     flex               | []                                              |
+     freedink           |                   [] []       []    []          |
+     fusionforge        |                                                 |
+     gas                |                               []                |
+     gawk               |                               []    () []       |
+     gcal               |                                                 |
+     gcc                |                                                 |
+     gdbm               |                                                 |
+     gettext-examples   | []    []          [] []       []    [] []       |
+     gettext-runtime    | []    []          [] []       []    [] []       |
+     gettext-tools      |                               []    [] []       |
+     gjay               |       []                                        |
+     glunarclock        | []    []          [] []       []    []          |
+     gnubiff            |                      []       []    ()          |
+     gnubik             |       []          []                []          |
+     gnucash            |          () () ()    ()             ()          |
+     gnuchess           |                                                 |
+     gnulib             | []    []             []             [] []       |
+     gnunet             |                                                 |
+     gnunet-gtk         |                                                 |
+     gold               |                               []    []          |
+     gphoto2            |                      []       []    [] []       |
+     gprof              | []                   []       []    []          |
+     gramadoir          | []                   []       []                |
+     grep               | []    []          [] []       []    [] []       |
+     grub               |       []             []             []          |
+     gsasl              | []                [] []       []    []          |
+     gss                | []                [] []       []    []          |
+     gst-plugins-bad    |                   [] []       []                |
+     gst-plugins-base   |       []          [] []       []                |
+     gst-plugins-good   |       []          [] []       []    [] []       |
+     gst-plugins-ugly   |       []          [] []       []    [] []       |
+     gstreamer          |       []          [] []       []    []          |
+     gtick              | []    []             []       []    []          |
+     gtkam              |                      []       [] [] [] []       |
+     gtkspell           | []    []    []    [] [] []    [] [] [] []       |
+     guix               |                                                 |
+     guix-packages      |                                                 |
+     gutenprint         |       []             []             []          |
+     hello              | []    []          [] []       []                |
+     help2man           |                   []                [] []       |
+     help2man-texi      |                                                 |
+     hylafax            |                               []                |
+     idutils            |                      []       []                |
+     iso_15924          |       []             []    [] [] [] []          |
+     iso_3166           | []    [] [] [] [] [] []    [] [] [] [] []    [] |
+     iso_3166_2         |                               []    []          |
+     iso_4217           |                   [] []       [] [] [] []       |
+     iso_639            | []    [] []       [] []       [] [] [] []       |
+     iso_639_3          |       []                            []          |
+     iso_639_5          |                                                 |
+     jwhois             |       []             []       []    []          |
+     kbd                |                      []       []    []          |
+     klavaro            |       []          [] []             []       [] |
+     ld                 | []                            []    [] []       |
+     leafpad            | []    []    []    [] []       []    [] ()       |
+     libc               |       []          []          []    [] []       |
+     libexif            |                                     []          |
+     libextractor       |                                                 |
+     libgnutls          |                                     []          |
+     libgphoto2         |                                     [] []       |
+     libgphoto2_port    |                                     [] []       |
+     libgsasl           | []                   []       []    []          |
+     libiconv           | []    []          [] []       []    [] []       |
+     libidn             |                   [] []       []    []          |
+     liferea            |       []    []       []             [] []       |
+     lilypond           |                                     []          |
+     lordsawar          |                                                 |
+     lprng              |                               []                |
+     lynx               |                      []       []    [] []       |
+     m4                 | []    []          []          []       []       |
+     mailfromd          |                                                 |
+     mailutils          |                                                 |
+     make               |                   []          []    [] []       |
+     man-db             |                               []       []       |
+     man-db-manpages    |                               []       []       |
+     midi-instruments   |       []    []    [] [] []    [] [] [] []       |
+     minicom            |                      []       []       []       |
+     mkisofs            |                               []    []          |
+     myserver           |                                     []          |
+     nano               | []    []          [] []             [] []       |
+     opcodes            | []                            []    []          |
+     parted             |       []             []       []    [] []       |
+     pies               |                                                 |
+     pnmixer            |                   []                []          |
+     popt               | []    [] []       [] []    [] [] [] [] []       |
+     procps-ng          |                                                 |
+     procps-ng-man      |                                                 |
+     psmisc             |                   [] []       []    []          |
+     pspp               |       []                               []       |
+     pushover           |                                     []          |
+     pwdutils           |                               []                |
+     pyspread           |                                                 |
+     radius             |                               []                |
+     recode             | []    []    []    [] []       []    []          |
+     recutils           |                                                 |
+     rpm                |                               []                |
+     rush               |       []                                        |
+     sarg               |                                                 |
+     sed                | []    []          [] []       []    [] []       |
+     sharutils          |                                                 |
+     shishi             |                                                 |
+     skribilo           |                      []                         |
+     solfege            |       []                            []          |
+     solfege-manual     |                                                 |
+     spotmachine        |                                                 |
+     sudo               |       []          []                [] []       |
+     sudoers            |                   []                [] []       |
+     sysstat            |                   [] []       []       []       |
+     tar                | []                [] []       []    [] []       |
+     texinfo            |                   []          []    []          |
+     texinfo_document   |                   [] []             []          |
+     tigervnc           |                                                 |
+     tin                |                                                 |
+     tin-man            |                                                 |
+     tracgoogleappsa... |       []    []    [] []                         |
+     trader             |                   [] []                         |
+     util-linux         |                                        []       |
+     ve                 |                                     []          |
+     vice               |                      ()             ()          |
+     vmm                |                                                 |
+     vorbis-tools       |                   []          []                |
+     wastesedge         |                                     []          |
+     wcd                |                                                 |
+     wcd-man            |                                                 |
+     wdiff              |       []             []             []          |
+     wget               |                   [] []             [] []       |
+     wyslij-po          |       []          []          []                |
+     xboard             |                                                 |
+     xdg-user-dirs      | [] [] [] [] [] [] [] []    [] [] [] [] []    [] |
+     xkeyboard-config   |       []          [] []       []    [] []       |
+                        +-------------------------------------------------+
+                          ga gd gl gu he hi hr hu hy ia id is it ja ka kk
+                          35  2 47  4  8  2 60 71  2  6 81 11 87 57  0  3
+
+                          kn ko ku ky lg lt lv mk ml mn mr ms mt nb ne nl 
+                        +--------------------------------------------------+
+     a2ps               |                                  []          []  |
+     aegis              |                                              []  |
+     anubis             |                                  []    []    []  |
+     aspell             |                            []                []  |
+     bash               |                                        []    []  |
+     bfd                |                                                  |
+     binutils           |                                                  |
+     bison              |                                              []  |
+     bison-runtime      |          []    [] []             []    []    []  |
+     buzztrax           |                                                  |
+     ccd2cue            |                                                  |
+     ccide              |                   []                         []  |
+     cflow              |                                              []  |
+     clisp              |                                              []  |
+     coreutils          |                                        []    []  |
+     cpio               |                                              []  |
+     cppi               |                                                  |
+     cpplib             |                                              []  |
+     cryptsetup         |                                              []  |
+     datamash           |                                        []    []  |
+     denemo             |                                                  |
+     dfarc              |                      []                      []  |
+     dialog             |       []       [] []             []    []    []  |
+     dico               |                                                  |
+     diffutils          |                   []                   []    []  |
+     dink               |                                              []  |
+     direvent           |                                              []  |
+     doodle             |                                              []  |
+     dos2unix           |                                        []    []  |
+     dos2unix-man       |                                              []  |
+     e2fsprogs          |                                              []  |
+     enscript           |                                              []  |
+     exif               |    []             []                         []  |
+     fetchmail          |                                              []  |
+     findutils          |                                        []    []  |
+     flex               |                                              []  |
+     freedink           |                                        []    []  |
+     fusionforge        |                                                  |
+     gas                |                                                  |
+     gawk               |                                              []  |
+     gcal               |                                                  |
+     gcc                |                                                  |
+     gdbm               |                                                  |
+     gettext-examples   |          []       []             [] [] []    []  |
+     gettext-runtime    |    []                                  []    []  |
+     gettext-tools      |    []                                            |
+     gjay               |                                                  |
+     glunarclock        |                   []                         []  |
+     gnubiff            |                                              []  |
+     gnubik             |                                        []    []  |
+     gnucash            | () ()          () ()          ()       () () []  |
+     gnuchess           |                                        []    []  |
+     gnulib             |                                              []  |
+     gnunet             |                                                  |
+     gnunet-gtk         |                                                  |
+     gold               |                                                  |
+     gphoto2            |                                              []  |
+     gprof              |                                  []          []  |
+     gramadoir          |                                              []  |
+     grep               |                                        []    []  |
+     grub               |                []                      []    []  |
+     gsasl              |                                              []  |
+     gss                |                                                  |
+     gst-plugins-bad    |                   []                   []    []  |
+     gst-plugins-base   |                   []                   []    []  |
+     gst-plugins-good   |                [] []                   []    []  |
+     gst-plugins-ugly   |                   []             [] [] []    []  |
+     gstreamer          |                []                      []    []  |
+     gtick              |                                              []  |
+     gtkam              |                                        []    []  |
+     gtkspell           |          []    [] []       []    []    []    []  |
+     guix               |                                                  |
+     guix-packages      |                                                  |
+     gutenprint         |                                              []  |
+     hello              |                   []                   []    []  |
+     help2man           |                                        []        |
+     help2man-texi      |                                                  |
+     hylafax            |                                              []  |
+     idutils            |                                              []  |
+     iso_15924          |                () []                         []  |
+     iso_3166           | [] [] []       () [] [] []    []       []    []  |
+     iso_3166_2         |                ()                            []  |
+     iso_4217           |                () []                   []    []  |
+     iso_639            | [] []          () []    []    []             []  |
+     iso_639_3          | []             ()             []                 |
+     iso_639_5          |                ()                                |
+     jwhois             |                   []                         []  |
+     kbd                |                                              []  |
+     klavaro            |                                        []    []  |
+     ld                 |                                                  |
+     leafpad            |    []    []    [] []                         []  |
+     libc               |    []                                        []  |
+     libexif            |                                              []  |
+     libextractor       |                                              []  |
+     libgnutls          |                                  []          []  |
+     libgphoto2         |                                              []  |
+     libgphoto2_port    |                                              []  |
+     libgsasl           |                                              []  |
+     libiconv           |                []                            []  |
+     libidn             |                                              []  |
+     liferea            |                [] []                         []  |
+     lilypond           |                                              []  |
+     lordsawar          |                                                  |
+     lprng              |                                                  |
+     lynx               |                                              []  |
+     m4                 |                                              []  |
+     mailfromd          |                                                  |
+     mailutils          |                                                  |
+     make               |    []                                        []  |
+     man-db             |                                              []  |
+     man-db-manpages    |                                              []  |
+     midi-instruments   |    [] []       []          []    []       [] []  |
+     minicom            |                                        []        |
+     mkisofs            |                                              []  |
+     myserver           |                                                  |
+     nano               |                                  []    []    []  |
+     opcodes            |                                              []  |
+     parted             |    []                                        []  |
+     pies               |                                                  |
+     pnmixer            |                                              []  |
+     popt               | [] []             []                   []    []  |
+     procps-ng          |                                                  |
+     procps-ng-man      |                                                  |
+     psmisc             |                                              []  |
+     pspp               |                []                            []  |
+     pushover           |                                                  |
+     pwdutils           |                                              []  |
+     pyspread           |                                                  |
+     radius             |                                              []  |
+     recode             |                                        []    []  |
+     recutils           |                                              []  |
+     rpm                |                                              []  |
+     rush               |                                              []  |
+     sarg               |                                                  |
+     sed                |                                        []    []  |
+     sharutils          |                                              []  |
+     shishi             |                                                  |
+     skribilo           |                                                  |
+     solfege            |                                        []    []  |
+     solfege-manual     |                                              []  |
+     spotmachine        |                                              []  |
+     sudo               |    []                                  []    []  |
+     sudoers            |    []                                  []    []  |
+     sysstat            |                                        []    []  |
+     tar                |          []                            []    []  |
+     texinfo            |                                              []  |
+     texinfo_document   |                                              []  |
+     tigervnc           |                                              []  |
+     tin                |                                                  |
+     tin-man            |                                                  |
+     tracgoogleappsa... |                   []                   []    []  |
+     trader             |                                        []        |
+     util-linux         |                                              []  |
+     ve                 |                                              []  |
+     vice               |                                              []  |
+     vmm                |                                              []  |
+     vorbis-tools       |                                              []  |
+     wastesedge         |                                              []  |
+     wcd                |                                              []  |
+     wcd-man            |                                              []  |
+     wdiff              |                                              []  |
+     wget               |                                        []    []  |
+     wyslij-po          |                                              []  |
+     xboard             |                                              []  |
+     xdg-user-dirs      | [] [] [] []    [] [] [] []    []       []    []  |
+     xkeyboard-config   |    []          []                            []  |
+                        +--------------------------------------------------+
+                          kn ko ku ky lg lt lv mk ml mn mr ms mt nb ne nl 
+                           5 15  4  6  0 13 23  3  3  3  4 11  2 42  1 125
+
+                          nn or pa pl  ps pt pt_BR ro ru rw sk sl sq sr 
+                        +------------------------------------------------+
+     a2ps               |          []     []  []   [] []       []    []  |
+     aegis              |                     []      []                 |
+     anubis             |          []                 []             []  |
+     aspell             |          []         []   [] []    [] []    []  |
+     bash               |          []         []      []    [] []    []  |
+     bfd                |                             []             []  |
+     binutils           |                             []             []  |
+     bison              |          []         []                     []  |
+     bison-runtime      |          []     []  []   [] []       [] [] []  |
+     buzztrax           |                     []                         |
+     ccd2cue            |                     []                     []  |
+     ccide              |          []                 []             []  |
+     cflow              |          []         []                     []  |
+     clisp              |                             []                 |
+     coreutils          |          []                 []       []    []  |
+     cpio               |          []                 []             []  |
+     cppi               |          []         []                     []  |
+     cpplib             |                     []      []             []  |
+     cryptsetup         |          []         []                     []  |
+     datamash           |                     []                     []  |
+     denemo             |                                                |
+     dfarc              |          []         []                     []  |
+     dialog             |          []         []   [] []    [] []    []  |
+     dico               |          []                                    |
+     diffutils          |          []         []                     []  |
+     dink               |                                                |
+     direvent           |          []         []                     []  |
+     doodle             |                                         [] []  |
+     dos2unix           |          []         []      []             []  |
+     dos2unix-man       |          []         []                         |
+     e2fsprogs          |          []                                    |
+     enscript           |          []         []   [] []       []    []  |
+     exif               |          []         []   [] []    []       []  |
+     fetchmail          |          []                 []          []     |
+     findutils          |          []         []      []    [] []    []  |
+     flex               |          []         []   [] []             []  |
+     freedink           |          []         []      []       []    []  |
+     fusionforge        |                                                |
+     gas                |                                                |
+     gawk               |          []                                    |
+     gcal               |                                                |
+     gcc                |                                                |
+     gdbm               |          []         []                     []  |
+     gettext-examples   |          []     []  []   [] []    [] []    []  |
+     gettext-runtime    | []       []     []  []   [] []    [] []    []  |
+     gettext-tools      |          []         []   [] []    [] []    []  |
+     gjay               |                                            []  |
+     glunarclock        |          []         []   []       [] []    []  |
+     gnubiff            |                                            []  |
+     gnubik             |          []         []               []    []  |
+     gnucash            |          ()     ()  ()   () ()             []  |
+     gnuchess           |                     []                     []  |
+     gnulib             |          []         []      []       []    []  |
+     gnunet             |                                                |
+     gnunet-gtk         |                                                |
+     gold               |                                                |
+     gphoto2            |          []         []   [] []             []  |
+     gprof              |                     []   [] []             []  |
+     gramadoir          |                                   []       []  |
+     grep               |          []         []      []    [] []    []  |
+     grub               |          []         []      []       []    []  |
+     gsasl              |          []                       []       []  |
+     gss                |          []              []       []       []  |
+     gst-plugins-bad    |          []         []      []    []       []  |
+     gst-plugins-base   |          []         []      []    [] []    []  |
+     gst-plugins-good   |          []         []   [] []    [] []    []  |
+     gst-plugins-ugly   |          []         []   [] []    [] []    []  |
+     gstreamer          |          []         []   [] []    [] []    []  |
+     gtick              |          []         []      []    []       []  |
+     gtkam              |       [] []         []      []    []       []  |
+     gtkspell           |          []     []  []   [] []    [] [] [] []  |
+     guix               |                                                |
+     guix-packages      |                                                |
+     gutenprint         |                                   [] []        |
+     hello              |          []         []      []    [] []    []  |
+     help2man           |          []         []      []             []  |
+     help2man-texi      |          []                                    |
+     hylafax            |                                                |
+     idutils            |          []                 []             []  |
+     iso_15924          |          []     ()       [] []       []    []  |
+     iso_3166           | [] [] [] []     ()  []   [] [] [] [] [] [] []  |
+     iso_3166_2         |          []     ()                         []  |
+     iso_4217           | []       []     ()       [] [] []    []    []  |
+     iso_639            |    [] [] []     ()       [] [] [] [] []    []  |
+     iso_639_3          |       []        ()                             |
+     iso_639_5          |                 ()                         []  |
+     jwhois             |          []         []   []                []  |
+     kbd                |          []                 []                 |
+     klavaro            |       [] []         []      []       []        |
+     ld                 |                                                |
+     leafpad            | []       []     []  []      []    [] []    []  |
+     libc               |          []                 []    []           |
+     libexif            |          []         ()            []           |
+     libextractor       |          []                                    |
+     libgnutls          |          []                                    |
+     libgphoto2         |          []                                    |
+     libgphoto2_port    |          []         []      []    []       []  |
+     libgsasl           |          []              []       []       []  |
+     libiconv           |          []         []            [] []    []  |
+     libidn             |          []         []                     []  |
+     liferea            |          []     []  []   [] ()    []    []     |
+     lilypond           |                                                |
+     lordsawar          |                                                |
+     lprng              |          []                                    |
+     lynx               |                     []      []                 |
+     m4                 |          []         []   [] []             []  |
+     mailfromd          |          []                                    |
+     mailutils          |          []                                    |
+     make               |          []         []      []                 |
+     man-db             |          []                 []             []  |
+     man-db-manpages    |          []                 []             []  |
+     midi-instruments   |          []     []  []   [] []    [] []    []  |
+     minicom            |          []         []   [] []                 |
+     mkisofs            |          []                 []             []  |
+     myserver           |                                      []    []  |
+     nano               |          []         []   [] []       []    []  |
+     opcodes            |                                                |
+     parted             |          []         []      []    [] []    []  |
+     pies               |          []                                    |
+     pnmixer            |                             []                 |
+     popt               |          []     []  []      []       []    []  |
+     procps-ng          |          []                                    |
+     procps-ng-man      |          []                                    |
+     psmisc             |          []         []      []             []  |
+     pspp               |          []                 []                 |
+     pushover           |                                                |
+     pwdutils           |          []                                    |
+     pyspread           | []                  []                         |
+     radius             |          []                 []                 |
+     recode             |          []     []  []   [] []    [] []    []  |
+     recutils           |                     []                     []  |
+     rpm                |          []                                    |
+     rush               |          []         []                     []  |
+     sarg               |                     []      []                 |
+     sed                |          []     []  []   [] []    [] []    []  |
+     sharutils          |          []         []                     []  |
+     shishi             |          []                                []  |
+     skribilo           |                                            []  |
+     solfege            |          []         []      []                 |
+     solfege-manual     |          []         []                         |
+     spotmachine        |                     []                     []  |
+     sudo               |          []         []      []    [] []    []  |
+     sudoers            |          []         []               []    []  |
+     sysstat            |          []         []      []    []       []  |
+     tar                |          []         []      []       []    []  |
+     texinfo            |          []         []      []                 |
+     texinfo_document   |          []         []                         |
+     tigervnc           |                     []      []             []  |
+     tin                |                             []                 |
+     tin-man            |                                                |
+     tracgoogleappsa... |          []         []      []             []  |
+     trader             |                             []             []  |
+     util-linux         |          []         []                         |
+     ve                 |          []         []                     []  |
+     vice               |                                                |
+     vmm                |                                                |
+     vorbis-tools       |          []                          []    []  |
+     wastesedge         |                                                |
+     wcd                |                                                |
+     wcd-man            |                                                |
+     wdiff              |          []         []      []       []    []  |
+     wget               |          []         []      []    []       []  |
+     wyslij-po          | []       []         []                     []  |
+     xboard             |          []                 []             []  |
+     xdg-user-dirs      | [] [] [] []  [] []  []   [] []    [] [] [] []  |
+     xkeyboard-config   |          []         []      []       []        |
+                        +------------------------------------------------+
+                          nn or pa pl  ps pt pt_BR ro ru rw sk sl sq sr 
+                           7  3  6 114  1 12  88   32 82  3 40 45  7 101
+
+                          sv  sw ta te tg th tr uk  ur vi  wa wo zh_CN
+                        +----------------------------------------------+
+     a2ps               | []              [] [] []     []              |
+     aegis              |                              []              |
+     anubis             | []                 [] []     []              |
+     aspell             | []                    []     []  []     []   |
+     bash               | []                    []     []         []   |
+     bfd                | []                    []     []              |
+     binutils           | []                    []     []              |
+     bison              | []                    []     []         []   |
+     bison-runtime      | []              [] [] []     []         []   |
+     buzztrax           | []                           []         []   |
+     ccd2cue            |                       []     []         []   |
+     ccide              | []                    []     []         []   |
+     cflow              | []                    []     []         []   |
+     clisp              |                                              |
+     coreutils          | []                    []     []              |
+     cpio               | []                 [] []     []         []   |
+     cppi               | []                    []     []         []   |
+     cpplib             | []                 [] []     []         []   |
+     cryptsetup         |                       []     []         []   |
+     datamash           | []                    []     []              |
+     denemo             |                                         []   |
+     dfarc              | []                           []              |
+     dialog             | []  []          []           []  []     []   |
+     dico               |                       []                     |
+     diffutils          | []                 [] []     []         []   |
+     dink               | []                                           |
+     direvent           |                       []     []              |
+     doodle             | []                           []              |
+     dos2unix           | []                    []     []         []   |
+     dos2unix-man       | []                    []                []   |
+     e2fsprogs          | []                    []     []         []   |
+     enscript           | []                 [] []     []              |
+     exif               | []                 [] []     []         []   |
+     fetchmail          | []                 []        []         []   |
+     findutils          | []                 [] []     []         []   |
+     flex               | []                 []        []         []   |
+     freedink           | []              []           []              |
+     fusionforge        |                                              |
+     gas                |                       []                     |
+     gawk               | []                           []         []   |
+     gcal               | []                 []                   []   |
+     gcc                | []                                           |
+     gdbm               |                       []     []              |
+     gettext-examples   | []                 [] []     []         []   |
+     gettext-runtime    | []                 [] []     []         []   |
+     gettext-tools      | []                 [] []     []         []   |
+     gjay               |                 []           []         []   |
+     glunarclock        | []                           []  []     []   |
+     gnubiff            | []                           []              |
+     gnubik             | []                    []     []         []   |
+     gnucash            |        () ()              () ()         []   |
+     gnuchess           |                       []     []         []   |
+     gnulib             | []                    []     []         []   |
+     gnunet             |                                              |
+     gnunet-gtk         |                                              |
+     gold               |                       []     []              |
+     gphoto2            | []                    []     []         []   |
+     gprof              | []                 [] []     []              |
+     gramadoir          | []                           []         []   |
+     grep               | []              []    []     []         []   |
+     grub               | []                 [] []     []              |
+     gsasl              | []                    []     []         []   |
+     gss                | []                           []         []   |
+     gst-plugins-bad    | []                 [] []     []         []   |
+     gst-plugins-base   | []                 [] []     []         []   |
+     gst-plugins-good   | []                 [] []     []         []   |
+     gst-plugins-ugly   | []                 [] []     []         []   |
+     gstreamer          | []                 [] []     []         []   |
+     gtick              |                       []     []         []   |
+     gtkam              | []                    []     []         []   |
+     gtkspell           | []              [] [] []     []  []     []   |
+     guix               |                                              |
+     guix-packages      |                                              |
+     gutenprint         |                    [] []     []         []   |
+     hello              | []              [] [] []     []         []   |
+     help2man           |                       []     []         []   |
+     help2man-texi      |                       []                     |
+     hylafax            |                              []              |
+     idutils            |                       []     []         []   |
+     iso_15924          | []              () [] []     ()         []   |
+     iso_3166           | []        []    () [] []     ()  []     []   |
+     iso_3166_2         |                 () [] []     ()         []   |
+     iso_4217           | []              () [] []     ()         []   |
+     iso_639            | []     [] []    () [] []     ()  []     []   |
+     iso_639_3          |        []       () [] []     ()              |
+     iso_639_5          |                 ()    []     ()              |
+     jwhois             | []                 []        []         []   |
+     kbd                | []                    []     []         []   |
+     klavaro            | []                    []  [] []     []  []   |
+     ld                 | []                 [] []     []         []   |
+     leafpad            | []              [] [] []     []         []   |
+     libc               | []                 [] []     []         []   |
+     libexif            | []                           []         ()   |
+     libextractor       |                       []     []              |
+     libgnutls          | []                    []     []         []   |
+     libgphoto2         | []                    []     []              |
+     libgphoto2_port    | []                    []     []         []   |
+     libgsasl           | []                    []     []         []   |
+     libiconv           | []                    []     []  []     []   |
+     libidn             | ()                    []     []         []   |
+     liferea            | []                 [] []     []         []   |
+     lilypond           |                              []              |
+     lordsawar          |                                              |
+     lprng              |                              []              |
+     lynx               | []                 [] []     []              |
+     m4                 | []                           []         []   |
+     mailfromd          |                       []     []              |
+     mailutils          |                              []              |
+     make               | []                    []     []         []   |
+     man-db             | []                           []         []   |
+     man-db-manpages    | []                                      []   |
+     midi-instruments   | []              [] [] []     []         []   |
+     minicom            | []                           []              |
+     mkisofs            |                       []     []         []   |
+     myserver           |                              []              |
+     nano               | []                    []     []         []   |
+     opcodes            |                       []     []         []   |
+     parted             | []                 [] []     []         []   |
+     pies               |                       []     []              |
+     pnmixer            |                       []     []         []   |
+     popt               | []     []       [] [] []     []         []   |
+     procps-ng          |                       []     []              |
+     procps-ng-man      |                       []                     |
+     psmisc             | []                    []     []         []   |
+     pspp               |                    [] []                []   |
+     pushover           | []                                           |
+     pwdutils           | []                           []              |
+     pyspread           |                       []                     |
+     radius             |                       []     []              |
+     recode             | []                 []        []         []   |
+     recutils           | []                    []     []              |
+     rpm                | []                    []     []         []   |
+     rush               |                       []     []              |
+     sarg               |                                              |
+     sed                | []                 [] []     []         []   |
+     sharutils          | []                    []     []         []   |
+     shishi             |                              []         []   |
+     skribilo           | []                    []                     |
+     solfege            | []                 []        []         []   |
+     solfege-manual     |                    []                        |
+     spotmachine        | []                    []     []              |
+     sudo               | []                 [] []     []         []   |
+     sudoers            | []                    []     []         []   |
+     sysstat            | []                 [] []     []         []   |
+     tar                | []                 [] []     []         []   |
+     texinfo            |                    [] []     []              |
+     texinfo_document   |                       []                     |
+     tigervnc           | []                    []                []   |
+     tin                |                                         []   |
+     tin-man            |                                              |
+     tracgoogleappsa... | []              []    []     []         []   |
+     trader             | []                                           |
+     util-linux         | []                    []     []         []   |
+     ve                 | []                    []     []         []   |
+     vice               | ()                 ()                        |
+     vmm                |                                              |
+     vorbis-tools       | []                           []              |
+     wastesedge         |                                              |
+     wcd                |                       []     []         []   |
+     wcd-man            |                       []                     |
+     wdiff              | []                    []     []         []   |
+     wget               |                       []     []         []   |
+     wyslij-po          |                       []     []              |
+     xboard             |                       []                []   |
+     xdg-user-dirs      | []     [] []    [] [] []     []         []   |
+     xkeyboard-config   | []                 [] []     []              |
+                        +----------------------------------------------+
+                          sv  sw ta te tg th tr uk  ur vi  wa wo zh_CN
+                          106  1  4  3  0 13 51 115  1 125  7  1  100 
+
+                          zh_HK zh_TW
+                        +-------------+
+     a2ps               |             | 30
+     aegis              |             |  9
+     anubis             |             | 19
+     aspell             |             | 29
+     bash               |        []   | 23
+     bfd                |             | 11
+     binutils           |             | 12
+     bison              |        []   | 18
+     bison-runtime      |        []   | 38
+     buzztrax           |             |  9
+     ccd2cue            |             | 10
+     ccide              |             | 17
+     cflow              |             | 16
+     clisp              |             | 10
+     coreutils          |             | 18
+     cpio               |             | 20
+     cppi               |             | 17
+     cpplib             |        []   | 19
+     cryptsetup         |             | 14
+     datamash           |             | 11
+     denemo             |             |  5
+     dfarc              |             | 17
+     dialog             |        []   | 42
+     dico               |             |  6
+     diffutils          |             | 22
+     dink               |             | 10
+     direvent           |             | 11
+     doodle             |             | 12
+     dos2unix           |        []   | 18
+     dos2unix-man       |             |  9
+     e2fsprogs          |             | 15
+     enscript           |             | 21
+     exif               |             | 27
+     fetchmail          |             | 19
+     findutils          |             | 29
+     flex               |        []   | 19
+     freedink           |             | 24
+     fusionforge        |             |  3
+     gas                |             |  5
+     gawk               |             | 13
+     gcal               |             |  8
+     gcc                |             |  2
+     gdbm               |             | 10
+     gettext-examples   |  []    []   | 40
+     gettext-runtime    |  []    []   | 35
+     gettext-tools      |        []   | 24
+     gjay               |             |  9
+     glunarclock        |        []   | 27
+     gnubiff            |             |  9
+     gnubik             |             | 19
+     gnucash            |        ()   |  6
+     gnuchess           |             | 11
+     gnulib             |             | 23
+     gnunet             |             |  1
+     gnunet-gtk         |             |  1
+     gold               |             |  7
+     gphoto2            |        []   | 19
+     gprof              |             | 21
+     gramadoir          |             | 14
+     grep               |        []   | 31
+     grub               |             | 21
+     gsasl              |        []   | 19
+     gss                |             | 17
+     gst-plugins-bad    |             | 21
+     gst-plugins-base   |             | 27
+     gst-plugins-good   |             | 32
+     gst-plugins-ugly   |             | 34
+     gstreamer          |        []   | 32
+     gtick              |             | 19
+     gtkam              |             | 24
+     gtkspell           |  []    []   | 48
+     guix               |             |  2
+     guix-packages      |             |  0
+     gutenprint         |             | 15
+     hello              |        []   | 30
+     help2man           |             | 18
+     help2man-texi      |             |  5
+     hylafax            |             |  5
+     idutils            |             | 14
+     iso_15924          |        []   | 23
+     iso_3166           |  []    []   | 58
+     iso_3166_2         |             |  9
+     iso_4217           |  []    []   | 28
+     iso_639            |  []    []   | 46
+     iso_639_3          |             | 10
+     iso_639_5          |             |  2
+     jwhois             |        []   | 20
+     kbd                |             | 17
+     klavaro            |             | 30
+     ld                 |        []   | 15
+     leafpad            |        []   | 39
+     libc               |        []   | 24
+     libexif            |             | 10
+     libextractor       |             |  5
+     libgnutls          |             | 13
+     libgphoto2         |             | 10
+     libgphoto2_port    |        []   | 19
+     libgsasl           |             | 18
+     libiconv           |        []   | 29
+     libidn             |             | 17
+     liferea            |             | 29
+     lilypond           |             | 11
+     lordsawar          |             |  3
+     lprng              |             |  3
+     lynx               |             | 19
+     m4                 |        []   | 22
+     mailfromd          |             |  4
+     mailutils          |             |  6
+     make               |             | 19
+     man-db             |             | 15
+     man-db-manpages    |             | 10
+     midi-instruments   |        []   | 43
+     minicom            |        []   | 17
+     mkisofs            |             | 13
+     myserver           |             |  9
+     nano               |        []   | 30
+     opcodes            |             | 12
+     parted             |        []   | 23
+     pies               |             |  4
+     pnmixer            |             |  9
+     popt               |        []   | 36
+     procps-ng          |             |  5
+     procps-ng-man      |             |  4
+     psmisc             |        []   | 22
+     pspp               |             | 13
+     pushover           |             |  6
+     pwdutils           |             |  8
+     pyspread           |             |  6
+     radius             |             |  9
+     recode             |             | 31
+     recutils           |             | 10
+     rpm                |        []   | 13
+     rush               |             | 10
+     sarg               |             |  4
+     sed                |        []   | 35
+     sharutils          |             | 13
+     shishi             |             |  7
+     skribilo           |             |  7
+     solfege            |             | 21
+     solfege-manual     |             |  9
+     spotmachine        |             | 11
+     sudo               |             | 26
+     sudoers            |             | 22
+     sysstat            |             | 23
+     tar                |        []   | 30
+     texinfo            |             | 17
+     texinfo_document   |             | 13
+     tigervnc           |             | 14
+     tin                |        []   |  7
+     tin-man            |             |  1
+     tracgoogleappsa... |        []   | 22
+     trader             |             | 12
+     util-linux         |             | 13
+     ve                 |             | 14
+     vice               |             |  1
+     vmm                |             |  3
+     vorbis-tools       |             | 13
+     wastesedge         |             |  3
+     wcd                |             |  8
+     wcd-man            |             |  3
+     wdiff              |        []   | 23
+     wget               |             | 21
+     wyslij-po          |             | 14
+     xboard             |             | 10
+     xdg-user-dirs      |  []    []   | 68
+     xkeyboard-config   |        []   | 28
+                        +-------------+
+       89 teams           zh_HK zh_TW
+      166 domains           7    42    2809
+
+   Some counters in the preceding matrix are higher than the number of
+visible blocks let us expect.  This is because a few extra PO files are
+used for implementing regional variants of languages, or language
+dialects.
+
+   For a PO file in the matrix above to be effective, the package to
+which it applies should also have been internationalized and distributed
+as such by its maintainer.  There might be an observable lag between the
+mere existence a PO file and its wide availability in a distribution.
+
+   If Jun 2014 seems to be old, you may fetch a more recent copy of this
+'ABOUT-NLS' file on most GNU archive sites.  The most up-to-date matrix
+with full percentage details can be found at
+'http://translationproject.org/extra/matrix.html'.
+
+1.5 Using 'gettext' in new packages
+===================================
+
+If you are writing a freely available program and want to
+internationalize it you are welcome to use GNU 'gettext' in your
+package.  Of course you have to respect the GNU Lesser General Public
+License which covers the use of the GNU 'gettext' library.  This means
+in particular that even non-free programs can use 'libintl' as a shared
+library, whereas only free software can use 'libintl' as a static
+library or use modified versions of 'libintl'.
+
+   Once the sources are changed appropriately and the setup can handle
+the use of 'gettext' the only thing missing are the translations.  The
+Free Translation Project is also available for packages which are not
+developed inside the GNU project.  Therefore the information given above
+applies also for every other Free Software Project.  Contact
+'coordinator@translationproject.org' to make the '.pot' files available
+to the translation teams.
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..4253798
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,6 @@
+Sree Harsha Totakura <sreeharsha@totakura.in>
+Florian Dold <dold@taler.net>
+Marcello Stanisci <stanisci@taler.net>
+Christian Grothoff <grothoff@taler.net>
+Özgür Kesim <oec-taler@kesim.org>
+Benedikt Mueller
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..dba13ed
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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 of the License, or
+    (at your option) any later version.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
index 0000000..e69de29
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..e82fd21
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,368 @@
+Installation Instructions
+*************************
+
+   Copyright (C) 1994-1996, 1999-2002, 2004-2017, 2020-2021 Free
+Software Foundation, Inc.
+
+   Copying and distribution of this file, with or without modification,
+are permitted in any medium without royalty provided the copyright
+notice and this notice are preserved.  This file is offered as-is,
+without warranty of any kind.
+
+Basic Installation
+==================
+
+   Briefly, the shell command './configure && make && make install'
+should configure, build, and install this package.  The following
+more-detailed instructions are generic; see the 'README' file for
+instructions specific to this package.  Some packages provide this
+'INSTALL' file but do not implement all of the features documented
+below.  The lack of an optional feature in a given package is not
+necessarily a bug.  More recommendations for GNU packages can be found
+in *note Makefile Conventions: (standards)Makefile Conventions.
+
+   The 'configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a 'Makefile' in each directory of the package.
+It may also create one or more '.h' files containing system-dependent
+definitions.  Finally, it creates a shell script 'config.status' that
+you can run in the future to recreate the current configuration, and a
+file 'config.log' containing compiler output (useful mainly for
+debugging 'configure').
+
+   It can also use an optional file (typically called 'config.cache' and
+enabled with '--cache-file=config.cache' or simply '-C') that saves the
+results of its tests to speed up reconfiguring.  Caching is disabled by
+default to prevent problems with accidental use of stale cache files.
+
+   If you need to do unusual things to compile the package, please try
+to figure out how 'configure' could check whether to do them, and mail
+diffs or instructions to the address given in the 'README' so they can
+be considered for the next release.  If you are using the cache, and at
+some point 'config.cache' contains results you don't want to keep, you
+may remove or edit it.
+
+   The file 'configure.ac' (or 'configure.in') is used to create
+'configure' by a program called 'autoconf'.  You need 'configure.ac' if
+you want to change it or regenerate 'configure' using a newer version of
+'autoconf'.
+
+   The simplest way to compile this package is:
+
+  1. 'cd' to the directory containing the package's source code and type
+     './configure' to configure the package for your system.
+
+     Running 'configure' might take a while.  While running, it prints
+     some messages telling which features it is checking for.
+
+  2. Type 'make' to compile the package.
+
+  3. Optionally, type 'make check' to run any self-tests that come with
+     the package, generally using the just-built uninstalled binaries.
+
+  4. Type 'make install' to install the programs and any data files and
+     documentation.  When installing into a prefix owned by root, it is
+     recommended that the package be configured and built as a regular
+     user, and only the 'make install' phase executed with root
+     privileges.
+
+  5. Optionally, type 'make installcheck' to repeat any self-tests, but
+     this time using the binaries in their final installed location.
+     This target does not install anything.  Running this target as a
+     regular user, particularly if the prior 'make install' required
+     root privileges, verifies that the installation completed
+     correctly.
+
+  6. You can remove the program binaries and object files from the
+     source code directory by typing 'make clean'.  To also remove the
+     files that 'configure' created (so you can compile the package for
+     a different kind of computer), type 'make distclean'.  There is
+     also a 'make maintainer-clean' target, but that is intended mainly
+     for the package's developers.  If you use it, you may have to get
+     all sorts of other programs in order to regenerate files that came
+     with the distribution.
+
+  7. Often, you can also type 'make uninstall' to remove the installed
+     files again.  In practice, not all packages have tested that
+     uninstallation works correctly, even though it is required by the
+     GNU Coding Standards.
+
+  8. Some packages, particularly those that use Automake, provide 'make
+     distcheck', which can by used by developers to test that all other
+     targets like 'make install' and 'make uninstall' work correctly.
+     This target is generally not run by end users.
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the 'configure' script does not know about.  Run './configure --help'
+for details on some of the pertinent environment variables.
+
+   You can give 'configure' initial values for configuration parameters
+by setting variables in the command line or in the environment.  Here is
+an example:
+
+     ./configure CC=c99 CFLAGS=-g LIBS=-lposix
+
+   *Note Defining Variables::, for more details.
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you can use GNU 'make'.  'cd' to the
+directory where you want the object files and executables to go and run
+the 'configure' script.  'configure' automatically checks for the source
+code in the directory that 'configure' is in and in '..'.  This is known
+as a "VPATH" build.
+
+   With a non-GNU 'make', it is safer to compile the package for one
+architecture at a time in the source code directory.  After you have
+installed the package for one architecture, use 'make distclean' before
+reconfiguring for another architecture.
+
+   On MacOS X 10.5 and later systems, you can create libraries and
+executables that work on multiple system types--known as "fat" or
+"universal" binaries--by specifying multiple '-arch' options to the
+compiler but only a single '-arch' option to the preprocessor.  Like
+this:
+
+     ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
+                 CPP="gcc -E" CXXCPP="g++ -E"
+
+   This is not guaranteed to produce working output in all cases, you
+may have to build one architecture at a time and combine the results
+using the 'lipo' tool if you have problems.
+
+Installation Names
+==================
+
+   By default, 'make install' installs the package's commands under
+'/usr/local/bin', include files under '/usr/local/include', etc.  You
+can specify an installation prefix other than '/usr/local' by giving
+'configure' the option '--prefix=PREFIX', where PREFIX must be an
+absolute file name.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+pass the option '--exec-prefix=PREFIX' to 'configure', the package uses
+PREFIX as the prefix for installing programs and libraries.
+Documentation and other data files still use the regular prefix.
+
+   In addition, if you use an unusual directory layout you can give
+options like '--bindir=DIR' to specify different values for particular
+kinds of files.  Run 'configure --help' for a list of the directories
+you can set and what kinds of files go in them.  In general, the default
+for these options is expressed in terms of '${prefix}', so that
+specifying just '--prefix' will affect all of the other directory
+specifications that were not explicitly provided.
+
+   The most portable way to affect installation locations is to pass the
+correct locations to 'configure'; however, many packages provide one or
+both of the following shortcuts of passing variable assignments to the
+'make install' command line to change installation locations without
+having to reconfigure or recompile.
+
+   The first method involves providing an override variable for each
+affected directory.  For example, 'make install
+prefix=/alternate/directory' will choose an alternate location for all
+directory configuration variables that were expressed in terms of
+'${prefix}'.  Any directories that were specified during 'configure',
+but not in terms of '${prefix}', must each be overridden at install time
+for the entire installation to be relocated.  The approach of makefile
+variable overrides for each directory variable is required by the GNU
+Coding Standards, and ideally causes no recompilation.  However, some
+platforms have known limitations with the semantics of shared libraries
+that end up requiring recompilation when using this method, particularly
+noticeable in packages that use GNU Libtool.
+
+   The second method involves providing the 'DESTDIR' variable.  For
+example, 'make install DESTDIR=/alternate/directory' will prepend
+'/alternate/directory' before all installation names.  The approach of
+'DESTDIR' overrides is not required by the GNU Coding Standards, and
+does not work on platforms that have drive letters.  On the other hand,
+it does better at avoiding recompilation issues, and works well even
+when some directory options were not specified in terms of '${prefix}'
+at 'configure' time.
+
+Optional Features
+=================
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving 'configure' the
+option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'.
+
+   Some packages pay attention to '--enable-FEATURE' options to
+'configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to '--with-PACKAGE' options, where PACKAGE
+is something like 'gnu-as' or 'x' (for the X Window System).  The
+'README' should mention any '--enable-' and '--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, 'configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the 'configure' options '--x-includes=DIR' and
+'--x-libraries=DIR' to specify their locations.
+
+   Some packages offer the ability to configure how verbose the
+execution of 'make' will be.  For these packages, running './configure
+--enable-silent-rules' sets the default to minimal output, which can be
+overridden with 'make V=1'; while running './configure
+--disable-silent-rules' sets the default to verbose, which can be
+overridden with 'make V=0'.
+
+Particular systems
+==================
+
+   On HP-UX, the default C compiler is not ANSI C compatible.  If GNU CC
+is not installed, it is recommended to use the following options in
+order to use an ANSI C compiler:
+
+     ./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
+
+and if that doesn't work, install pre-built binaries of GCC for HP-UX.
+
+   HP-UX 'make' updates targets which have the same timestamps as their
+prerequisites, which makes it generally unusable when shipped generated
+files such as 'configure' are involved.  Use GNU 'make' instead.
+
+   On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
+parse its '<wchar.h>' header file.  The option '-nodtk' can be used as a
+workaround.  If GNU CC is not installed, it is therefore recommended to
+try
+
+     ./configure CC="cc"
+
+and if that doesn't work, try
+
+     ./configure CC="cc -nodtk"
+
+   On Solaris, don't put '/usr/ucb' early in your 'PATH'.  This
+directory contains several dysfunctional programs; working variants of
+these programs are available in '/usr/bin'.  So, if you need '/usr/ucb'
+in your 'PATH', put it _after_ '/usr/bin'.
+
+   On Haiku, software installed for all users goes in '/boot/common',
+not '/usr/local'.  It is recommended to use the following options:
+
+     ./configure --prefix=/boot/common
+
+Specifying the System Type
+==========================
+
+   There may be some features 'configure' cannot figure out
+automatically, but needs to determine by the type of machine the package
+will run on.  Usually, assuming the package is built to be run on the
+_same_ architectures, 'configure' can figure that out, but if it prints
+a message saying it cannot guess the machine type, give it the
+'--build=TYPE' option.  TYPE can either be a short name for the system
+type, such as 'sun4', or a canonical name which has the form:
+
+     CPU-COMPANY-SYSTEM
+
+where SYSTEM can have one of these forms:
+
+     OS
+     KERNEL-OS
+
+   See the file 'config.sub' for the possible values of each field.  If
+'config.sub' isn't included in this package, then this package doesn't
+need to know the machine type.
+
+   If you are _building_ compiler tools for cross-compiling, you should
+use the option '--target=TYPE' to select the type of system they will
+produce code for.
+
+   If you want to _use_ a cross compiler, that generates code for a
+platform different from the build platform, you should specify the
+"host" platform (i.e., that on which the generated programs will
+eventually be run) with '--host=TYPE'.
+
+Sharing Defaults
+================
+
+   If you want to set default values for 'configure' scripts to share,
+you can create a site shell script called 'config.site' that gives
+default values for variables like 'CC', 'cache_file', and 'prefix'.
+'configure' looks for 'PREFIX/share/config.site' if it exists, then
+'PREFIX/etc/config.site' if it exists.  Or, you can set the
+'CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all 'configure' scripts look for a site script.
+
+Defining Variables
+==================
+
+   Variables not defined in a site shell script can be set in the
+environment passed to 'configure'.  However, some packages may run
+configure again during the build, and the customized values of these
+variables may be lost.  In order to avoid this problem, you should set
+them in the 'configure' command line, using 'VAR=value'.  For example:
+
+     ./configure CC=/usr/local2/bin/gcc
+
+causes the specified 'gcc' to be used as the C compiler (unless it is
+overridden in the site shell script).
+
+Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an
+Autoconf limitation.  Until the limitation is lifted, you can use this
+workaround:
+
+     CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash
+
+'configure' Invocation
+======================
+
+   'configure' recognizes the following options to control how it
+operates.
+
+'--help'
+'-h'
+     Print a summary of all of the options to 'configure', and exit.
+
+'--help=short'
+'--help=recursive'
+     Print a summary of the options unique to this package's
+     'configure', and exit.  The 'short' variant lists options used only
+     in the top level, while the 'recursive' variant lists options also
+     present in any nested packages.
+
+'--version'
+'-V'
+     Print the version of Autoconf used to generate the 'configure'
+     script, and exit.
+
+'--cache-file=FILE'
+     Enable the cache: use and save the results of the tests in FILE,
+     traditionally 'config.cache'.  FILE defaults to '/dev/null' to
+     disable caching.
+
+'--config-cache'
+'-C'
+     Alias for '--cache-file=config.cache'.
+
+'--quiet'
+'--silent'
+'-q'
+     Do not print messages saying which checks are being made.  To
+     suppress all normal output, redirect it to '/dev/null' (any error
+     messages will still be shown).
+
+'--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     'configure' can determine that directory automatically.
+
+'--prefix=DIR'
+     Use DIR as the installation prefix.  *note Installation Names:: for
+     more details, including other options available for fine-tuning the
+     installation locations.
+
+'--no-create'
+'-n'
+     Run the configure checks, but stop before creating any output
+     files.
+
+'configure' also accepts some other, not widely useful, options.  Run
+'configure --help' for more details.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..83b761a
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,23 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+if DOC_ONLY
+if ENABLE_DOC
+  SUBDIRS = . contrib doc po
+else
+  SUBDIRS = . contrib po
+endif
+else
+if ENABLE_DOC
+  SUBDIRS = . contrib src doc po
+else
+  SUBDIRS = . contrib src po
+endif
+endif
+
+@DX_RULES@
+
+ACLOCAL_AMFLAGS = -I m4
+EXTRA_DIST = build-aux/config.rpath   \
+  AUTHORS \
+  README.1st
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..e69de29
diff --git a/README b/README
new file mode 100644
index 0000000..2e6dbba
--- /dev/null
+++ b/README
@@ -0,0 +1,119 @@
+                       Welcome to GNU Taler
+
+
+What is Taler?
+==============
+
+Taler is an electronic payment system providing the ability to pay
+anonymously using digital cash.  Taler consists of a network protocol
+definition (using a RESTful API over HTTP), a Exchange (which creates
+digital coins), a Wallet (which allows customers to manage, store and
+spend digital coins), and a Merchant website which allows customers to
+spend their digital coins.  Naturally, each Merchant is different, but
+Taler includes code examples to help Merchants integrate Taler as a
+payment system.
+
+Taler is currently developed by a worldwide group of independent free software
+developers and Taler Systems SA.  Taler is free software and an official GNU
+package (https://www.gnu.org/).
+
+This is an alpha release with a few known bugs, lacking a few important
+features, documentation, testing, performance tuning and an external security
+audit.  However, you can run the code and it largely works fine.  This package
+also only includes the Taler exchange, not the other components of the system.
+
+Documentation about Taler can be found at https://taler.net/.
+Our bug tracker is at https://bugs.taler.net/.
+
+
+Joining GNU
+===========
+
+This is a GNU program, developed by the GNU Project and part of the
+GNU Operating System. If you are the author of an awesome program and
+want to join us in writing Free Software, please consider making it an
+official GNU program and become a GNU maintainer.  You can find
+instructions on how to do so at http://www.gnu.org/help/evaluation.
+We are looking forward to hacking with you!
+
+
+Dependencies:
+=============
+
+These are the direct dependencies for running a Taler exchange:
+
+- GNUnet            >= 0.16.0
+- GNU libmicrohttpd >= 0.9.71
+- PostgreSQL        >= 13.0
+
+
+
+Project structure is currently as follows:
+
+src/include/
+  -- installed headers for public APIs
+
+src/util/
+  -- common utility functions (currency representation,
+     Taler-specific cryptography, Taler-specific json
+     support)
+
+src/pq/
+  -- Postgres-specific utility functions
+
+src/exchangedb/
+  -- Exchange database backend (with database-specific plugins)
+
+src/exchange/
+  -- taler exchange server
+
+src/exchange-tools/
+  -- taler exchange helper programs
+
+src/lib/
+  -- libtalerexchange: C API to issue HTTP requests to exchange
+
+src/auditor/
+  -- tools to generate reports about financial performance and
+     to validate that the exchange has been operating correctly
+
+src/auditordb/
+  -- database logic for the auditor component (with database-specific
+     plugins)
+
+src/benchmark/
+  -- tool to run performance measurements
+
+src/templating/
+  -- logic to generate HTML pages from templates at runtime
+
+src/kyclogic/
+  -- core logic and plugins to trigger and manage KYC processes
+     as required by banking regulation
+
+src/bank-lib/
+  -- bank REST client logic and implementation of an in-memory
+     RTGS emulator ("fakebank") for testing.
+
+src/extensions/
+  -- extensions to the core logic of an exchange
+
+src/json/
+  -- helper functions for generating and parsing JSON
+
+src/mhd/
+  -- helper functions for interacting with GNU libmicrohttpd
+
+src/curl/
+  -- helper functions for interacting with libcurl
+
+
+Getting Started
+===============
+
+Please follow the exchange manual you can view after
+installing using
+
+$ info taler-exchange
+
+or by visiting https://docs.taler.net/.
diff --git a/README.1st b/README.1st
new file mode 100644
index 0000000..e1925d7
--- /dev/null
+++ b/README.1st
@@ -0,0 +1,19 @@
+Building Taler
+==============
+
+Contributions are welcome. Please submit bugs you find to
+https://bugs.taler.net/ or our bugs mailinglist.  Submit patches via E-Mail to
+taler@gnu.org, formatted with `git format-patch`.
+
+In order to run the unit tests by hand (instead of using "make check"),
+you need to set the environment variable "TALER_PREFIX" to the
+directory where Taler's libraries are installed.
+Before running any testcases, you must complete the installation.
+
+Quick summary:
+
+$ ./configure --prefix=$SOMEWHERE
+$ make
+$ make install
+$ export $GNUNET_PREFIX=$SOMEWHERE
+$ make check
diff --git a/aclocal.m4 b/aclocal.m4
new file mode 100644
index 0000000..44ab1c8
--- /dev/null
+++ b/aclocal.m4
@@ -0,0 +1,1518 @@
+# generated automatically by aclocal 1.16.5 -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+m4_ifndef([AC_CONFIG_MACRO_DIRS], [m4_defun([_AM_CONFIG_MACRO_DIRS], 
[])m4_defun([AC_CONFIG_MACRO_DIRS], [_AM_CONFIG_MACRO_DIRS($@)])])
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.71],,
+[m4_warning([this file was generated for autoconf 2.71.
+You have another version of autoconf.  It may work, but is not guaranteed to.
+If you have problems, you may need to regenerate the build system entirely.
+To do so, use the procedure documented by the package, typically 
'autoreconf'.])])
+
+# pkg.m4 - Macros to locate and use pkg-config.   -*- Autoconf -*-
+# serial 12 (pkg-config-0.29.2)
+
+dnl Copyright © 2004 Scott James Remnant <scott@netsplit.com>.
+dnl Copyright © 2012-2015 Dan Nicholson <dbn.lists@gmail.com>
+dnl
+dnl This program is free software; you can redistribute it and/or modify
+dnl it under the terms of the GNU General Public License as published by
+dnl the Free Software Foundation; either version 2 of the License, or
+dnl (at your option) any later version.
+dnl
+dnl This program is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+dnl General Public License for more details.
+dnl
+dnl You should have received a copy of the GNU General Public License
+dnl along with this program; if not, write to the Free Software
+dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+dnl 02111-1307, USA.
+dnl
+dnl As a special exception to the GNU General Public License, if you
+dnl distribute this file as part of a program that contains a
+dnl configuration script generated by Autoconf, you may include it under
+dnl the same distribution terms that you use for the rest of that
+dnl program.
+
+dnl PKG_PREREQ(MIN-VERSION)
+dnl -----------------------
+dnl Since: 0.29
+dnl
+dnl Verify that the version of the pkg-config macros are at least
+dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's
+dnl installed version of pkg-config, this checks the developer's version
+dnl of pkg.m4 when generating configure.
+dnl
+dnl To ensure that this macro is defined, also add:
+dnl m4_ifndef([PKG_PREREQ],
+dnl     [m4_fatal([must install pkg-config 0.29 or later before running 
autoconf/autogen])])
+dnl
+dnl See the "Since" comment for each macro you use to see what version
+dnl of the macros you require.
+m4_defun([PKG_PREREQ],
+[m4_define([PKG_MACROS_VERSION], [0.29.2])
+m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1,
+    [m4_fatal([pkg.m4 version $1 or higher is required but 
]PKG_MACROS_VERSION[ found])])
+])dnl PKG_PREREQ
+
+dnl PKG_PROG_PKG_CONFIG([MIN-VERSION])
+dnl ----------------------------------
+dnl Since: 0.16
+dnl
+dnl Search for the pkg-config tool and set the PKG_CONFIG variable to
+dnl first found in the path. Checks that the version of pkg-config found
+dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is
+dnl used since that's the first version where most current features of
+dnl pkg-config existed.
+AC_DEFUN([PKG_PROG_PKG_CONFIG],
+[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
+m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
+m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
+AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
+AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
+AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search 
path])
+
+if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
+       AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
+fi
+if test -n "$PKG_CONFIG"; then
+       _pkg_min_version=m4_default([$1], [0.9.0])
+       AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
+       if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
+               AC_MSG_RESULT([yes])
+       else
+               AC_MSG_RESULT([no])
+               PKG_CONFIG=""
+       fi
+fi[]dnl
+])dnl PKG_PROG_PKG_CONFIG
+
+dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------------------------------
+dnl Since: 0.18
+dnl
+dnl Check to see whether a particular set of modules exists. Similar to
+dnl PKG_CHECK_MODULES(), but does not set variables or print errors.
+dnl
+dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+dnl only at the first occurrence in configure.ac, so if the first place
+dnl it's called might be skipped (such as if it is within an "if", you
+dnl have to call PKG_CHECK_EXISTS manually
+AC_DEFUN([PKG_CHECK_EXISTS],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+if test -n "$PKG_CONFIG" && \
+    AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
+  m4_default([$2], [:])
+m4_ifvaln([$3], [else
+  $3])dnl
+fi])
+
+dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
+dnl ---------------------------------------------
+dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting
+dnl pkg_failed based on the result.
+m4_define([_PKG_CONFIG],
+[if test -n "$$1"; then
+    pkg_cv_[]$1="$$1"
+ elif test -n "$PKG_CONFIG"; then
+    PKG_CHECK_EXISTS([$3],
+                     [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
+                     test "x$?" != "x0" && pkg_failed=yes ],
+                    [pkg_failed=yes])
+ else
+    pkg_failed=untried
+fi[]dnl
+])dnl _PKG_CONFIG
+
+dnl _PKG_SHORT_ERRORS_SUPPORTED
+dnl ---------------------------
+dnl Internal check to see if pkg-config supports short errors.
+AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
+        _pkg_short_errors_supported=yes
+else
+        _pkg_short_errors_supported=no
+fi[]dnl
+])dnl _PKG_SHORT_ERRORS_SUPPORTED
+
+
+dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl   [ACTION-IF-NOT-FOUND])
+dnl --------------------------------------------------------------
+dnl Since: 0.4.0
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES might not happen, you should be sure to include an
+dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
+AC_DEFUN([PKG_CHECK_MODULES],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
+AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
+
+pkg_failed=no
+AC_MSG_CHECKING([for $2])
+
+_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
+_PKG_CONFIG([$1][_LIBS], [libs], [$2])
+
+m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables 
$1[]_CFLAGS
+and $1[]_LIBS to avoid the need to call pkg-config.
+See the pkg-config man page for more details.])
+
+if test $pkg_failed = yes; then
+        AC_MSG_RESULT([no])
+        _PKG_SHORT_ERRORS_SUPPORTED
+        if test $_pkg_short_errors_supported = yes; then
+                $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors 
--cflags --libs "$2" 2>&1`
+        else
+                $1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs 
"$2" 2>&1`
+        fi
+        # Put the nasty error message in config.log where it belongs
+        echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
+
+        m4_default([$4], [AC_MSG_ERROR(
+[Package requirements ($2) were not met:
+
+$$1_PKG_ERRORS
+
+Consider adjusting the PKG_CONFIG_PATH environment variable if you
+installed software in a non-standard prefix.
+
+_PKG_TEXT])[]dnl
+        ])
+elif test $pkg_failed = untried; then
+        AC_MSG_RESULT([no])
+        m4_default([$4], [AC_MSG_FAILURE(
+[The pkg-config script could not be found or is too old.  Make sure it
+is in your PATH or set the PKG_CONFIG environment variable to the full
+path to pkg-config.
+
+_PKG_TEXT
+
+To get pkg-config, see <http://pkg-config.freedesktop.org/>.])[]dnl
+        ])
+else
+        $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
+        $1[]_LIBS=$pkg_cv_[]$1[]_LIBS
+        AC_MSG_RESULT([yes])
+        $3
+fi[]dnl
+])dnl PKG_CHECK_MODULES
+
+
+dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
+dnl   [ACTION-IF-NOT-FOUND])
+dnl ---------------------------------------------------------------------
+dnl Since: 0.29
+dnl
+dnl Checks for existence of MODULES and gathers its build flags with
+dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags
+dnl and VARIABLE-PREFIX_LIBS from --libs.
+dnl
+dnl Note that if there is a possibility the first call to
+dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to
+dnl include an explicit call to PKG_PROG_PKG_CONFIG in your
+dnl configure.ac.
+AC_DEFUN([PKG_CHECK_MODULES_STATIC],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+_save_PKG_CONFIG=$PKG_CONFIG
+PKG_CONFIG="$PKG_CONFIG --static"
+PKG_CHECK_MODULES($@)
+PKG_CONFIG=$_save_PKG_CONFIG[]dnl
+])dnl PKG_CHECK_MODULES_STATIC
+
+
+dnl PKG_INSTALLDIR([DIRECTORY])
+dnl -------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable pkgconfigdir as the location where a module
+dnl should install pkg-config .pc files. By default the directory is
+dnl $libdir/pkgconfig, but the default can be changed by passing
+dnl DIRECTORY. The user can override through the --with-pkgconfigdir
+dnl parameter.
+AC_DEFUN([PKG_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+    [pkg-config installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([pkgconfigdir],
+    [AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
+    [with_pkgconfigdir=]pkg_default)
+AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_INSTALLDIR
+
+
+dnl PKG_NOARCH_INSTALLDIR([DIRECTORY])
+dnl --------------------------------
+dnl Since: 0.27
+dnl
+dnl Substitutes the variable noarch_pkgconfigdir as the location where a
+dnl module should install arch-independent pkg-config .pc files. By
+dnl default the directory is $datadir/pkgconfig, but the default can be
+dnl changed by passing DIRECTORY. The user can override through the
+dnl --with-noarch-pkgconfigdir parameter.
+AC_DEFUN([PKG_NOARCH_INSTALLDIR],
+[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
+m4_pushdef([pkg_description],
+    [pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
+AC_ARG_WITH([noarch-pkgconfigdir],
+    [AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
+    [with_noarch_pkgconfigdir=]pkg_default)
+AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
+m4_popdef([pkg_default])
+m4_popdef([pkg_description])
+])dnl PKG_NOARCH_INSTALLDIR
+
+
+dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
+dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl -------------------------------------------
+dnl Since: 0.28
+dnl
+dnl Retrieves the value of the pkg-config variable for the given module.
+AC_DEFUN([PKG_CHECK_VAR],
+[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
+AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
+
+_PKG_CONFIG([$1], [variable="][$3]["], [$2])
+AS_VAR_COPY([$1], [pkg_cv_][$1])
+
+AS_VAR_IF([$1], [""], [$5], [$4])dnl
+])dnl PKG_CHECK_VAR
+
+dnl PKG_WITH_MODULES(VARIABLE-PREFIX, MODULES,
+dnl   [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND],
+dnl   [DESCRIPTION], [DEFAULT])
+dnl ------------------------------------------
+dnl
+dnl Prepare a "--with-" configure option using the lowercase
+dnl [VARIABLE-PREFIX] name, merging the behaviour of AC_ARG_WITH and
+dnl PKG_CHECK_MODULES in a single macro.
+AC_DEFUN([PKG_WITH_MODULES],
+[
+m4_pushdef([with_arg], m4_tolower([$1]))
+
+m4_pushdef([description],
+           [m4_default([$5], [build with ]with_arg[ support])])
+
+m4_pushdef([def_arg], [m4_default([$6], [auto])])
+m4_pushdef([def_action_if_found], [AS_TR_SH([with_]with_arg)=yes])
+m4_pushdef([def_action_if_not_found], [AS_TR_SH([with_]with_arg)=no])
+
+m4_case(def_arg,
+            [yes],[m4_pushdef([with_without], [--without-]with_arg)],
+            [m4_pushdef([with_without],[--with-]with_arg)])
+
+AC_ARG_WITH(with_arg,
+     AS_HELP_STRING(with_without, description[ @<:@default=]def_arg[@:>@]),,
+    [AS_TR_SH([with_]with_arg)=def_arg])
+
+AS_CASE([$AS_TR_SH([with_]with_arg)],
+            [yes],[PKG_CHECK_MODULES([$1],[$2],$3,$4)],
+            [auto],[PKG_CHECK_MODULES([$1],[$2],
+                                        [m4_n([def_action_if_found]) $3],
+                                        [m4_n([def_action_if_not_found]) $4])])
+
+m4_popdef([with_arg])
+m4_popdef([description])
+m4_popdef([def_arg])
+
+])dnl PKG_WITH_MODULES
+
+dnl PKG_HAVE_WITH_MODULES(VARIABLE-PREFIX, MODULES,
+dnl   [DESCRIPTION], [DEFAULT])
+dnl -----------------------------------------------
+dnl
+dnl Convenience macro to trigger AM_CONDITIONAL after PKG_WITH_MODULES
+dnl check._[VARIABLE-PREFIX] is exported as make variable.
+AC_DEFUN([PKG_HAVE_WITH_MODULES],
+[
+PKG_WITH_MODULES([$1],[$2],,,[$3],[$4])
+
+AM_CONDITIONAL([HAVE_][$1],
+               [test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"])
+])dnl PKG_HAVE_WITH_MODULES
+
+dnl PKG_HAVE_DEFINE_WITH_MODULES(VARIABLE-PREFIX, MODULES,
+dnl   [DESCRIPTION], [DEFAULT])
+dnl ------------------------------------------------------
+dnl
+dnl Convenience macro to run AM_CONDITIONAL and AC_DEFINE after
+dnl PKG_WITH_MODULES check. HAVE_[VARIABLE-PREFIX] is exported as make
+dnl and preprocessor variable.
+AC_DEFUN([PKG_HAVE_DEFINE_WITH_MODULES],
+[
+PKG_HAVE_WITH_MODULES([$1],[$2],[$3],[$4])
+
+AS_IF([test "$AS_TR_SH([with_]m4_tolower([$1]))" = "yes"],
+        [AC_DEFINE([HAVE_][$1], 1, [Enable ]m4_tolower([$1])[ support])])
+])dnl PKG_HAVE_DEFINE_WITH_MODULES
+
+# Copyright (C) 2002-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_AUTOMAKE_VERSION(VERSION)
+# ----------------------------
+# Automake X.Y traces this macro to ensure aclocal.m4 has been
+# generated from the m4 files accompanying Automake X.Y.
+# (This private macro should not be called outside this file.)
+AC_DEFUN([AM_AUTOMAKE_VERSION],
+[am__api_version='1.16'
+dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
+dnl require some minimum version.  Point them to the right macro.
+m4_if([$1], [1.16.5], [],
+      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
+])
+
+# _AM_AUTOCONF_VERSION(VERSION)
+# -----------------------------
+# aclocal traces this macro to find the Autoconf version.
+# This is a private macro too.  Using m4_define simplifies
+# the logic in aclocal, which can simply ignore this definition.
+m4_define([_AM_AUTOCONF_VERSION], [])
+
+# AM_SET_CURRENT_AUTOMAKE_VERSION
+# -------------------------------
+# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# This function is AC_REQUIREd by AM_INIT_AUTOMAKE.
+AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
+[AM_AUTOMAKE_VERSION([1.16.5])dnl
+m4_ifndef([AC_AUTOCONF_VERSION],
+  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
+_AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))])
+
+# AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets
+# $ac_aux_dir to '$srcdir/foo'.  In other projects, it is set to
+# '$srcdir', '$srcdir/..', or '$srcdir/../..'.
+#
+# Of course, Automake must honor this variable whenever it calls a
+# tool from the auxiliary directory.  The problem is that $srcdir (and
+# therefore $ac_aux_dir as well) can be either absolute or relative,
+# depending on how configure is run.  This is pretty annoying, since
+# it makes $ac_aux_dir quite unusable in subdirectories: in the top
+# source directory, any form will work fine, but in subdirectories a
+# relative path needs to be adjusted first.
+#
+# $ac_aux_dir/missing
+#    fails when called from a subdirectory if $ac_aux_dir is relative
+# $top_srcdir/$ac_aux_dir/missing
+#    fails if $ac_aux_dir is absolute,
+#    fails when called from a subdirectory in a VPATH build with
+#          a relative $ac_aux_dir
+#
+# The reason of the latter failure is that $top_srcdir and $ac_aux_dir
+# are both prefixed by $srcdir.  In an in-source build this is usually
+# harmless because $srcdir is '.', but things will broke when you
+# start a VPATH build or use an absolute $srcdir.
+#
+# So we could use something similar to $top_srcdir/$ac_aux_dir/missing,
+# iff we strip the leading $srcdir from $ac_aux_dir.  That would be:
+#   am_aux_dir='\$(top_srcdir)/'`expr "$ac_aux_dir" : "$srcdir//*\(.*\)"`
+# and then we would define $MISSING as
+#   MISSING="\${SHELL} $am_aux_dir/missing"
+# This will work as long as MISSING is not called from configure, because
+# unfortunately $(top_srcdir) has no meaning in configure.
+# However there are other variables, like CC, which are often used in
+# configure, and could therefore not use this "fixed" $ac_aux_dir.
+#
+# Another solution, used here, is to always expand $ac_aux_dir to an
+# absolute PATH.  The drawback is that using absolute paths prevent a
+# configured tree to be moved without reconfiguration.
+
+AC_DEFUN([AM_AUX_DIR_EXPAND],
+[AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
+])
+
+# AM_CONDITIONAL                                            -*- Autoconf -*-
+
+# Copyright (C) 1997-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_CONDITIONAL(NAME, SHELL-CONDITION)
+# -------------------------------------
+# Define a conditional.
+AC_DEFUN([AM_CONDITIONAL],
+[AC_PREREQ([2.52])dnl
+ m4_if([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
+       [$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
+AC_SUBST([$1_TRUE])dnl
+AC_SUBST([$1_FALSE])dnl
+_AM_SUBST_NOTMAKE([$1_TRUE])dnl
+_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+m4_define([_AM_COND_VALUE_$1], [$2])dnl
+if $2; then
+  $1_TRUE=
+  $1_FALSE='#'
+else
+  $1_TRUE='#'
+  $1_FALSE=
+fi
+AC_CONFIG_COMMANDS_PRE(
+[if test -z "${$1_TRUE}" && test -z "${$1_FALSE}"; then
+  AC_MSG_ERROR([[conditional "$1" was never defined.
+Usually this means the macro was only invoked conditionally.]])
+fi])])
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+
+# There are a few dirty hacks below to avoid letting 'AC_PROG_CC' be
+# written in clear, in which case automake, when reading aclocal.m4,
+# will think it sees a *use*, and therefore will trigger all it's
+# C support machinery.  Also note that it means that autoscan, seeing
+# CC etc. in the Makefile, will ask for an AC_PROG_CC use...
+
+
+# _AM_DEPENDENCIES(NAME)
+# ----------------------
+# See how the compiler implements dependency checking.
+# NAME is "CC", "CXX", "OBJC", "OBJCXX", "UPC", or "GJC".
+# We try a few techniques and use that to set a single cache variable.
+#
+# We don't AC_REQUIRE the corresponding AC_PROG_CC since the latter was
+# modified to invoke _AM_DEPENDENCIES(CC); we would have a circular
+# dependency, and given that the user is not expected to run this macro,
+# just rely on AC_PROG_CC.
+AC_DEFUN([_AM_DEPENDENCIES],
+[AC_REQUIRE([AM_SET_DEPDIR])dnl
+AC_REQUIRE([AM_OUTPUT_DEPENDENCY_COMMANDS])dnl
+AC_REQUIRE([AM_MAKE_INCLUDE])dnl
+AC_REQUIRE([AM_DEP_TRACK])dnl
+
+m4_if([$1], [CC],   [depcc="$CC"   am_compiler_list=],
+      [$1], [CXX],  [depcc="$CXX"  am_compiler_list=],
+      [$1], [OBJC], [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
+      [$1], [OBJCXX], [depcc="$OBJCXX" am_compiler_list='gcc3 gcc'],
+      [$1], [UPC],  [depcc="$UPC"  am_compiler_list=],
+      [$1], [GCJ],  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
+                    [depcc="$$1"   am_compiler_list=])
+
+AC_CACHE_CHECK([dependency style of $depcc],
+               [am_cv_$1_dependencies_compiler_type],
+[if test -z "$AMDEP_TRUE" && test -f "$am_depcomp"; then
+  # We make a subdir and do the tests there.  Otherwise we can end up
+  # making bogus files that we don't know about and never remove.  For
+  # instance it was reported that on HP-UX the gcc test will end up
+  # making a dummy file named 'D' -- because '-MD' means "put the output
+  # in D".
+  rm -rf conftest.dir
+  mkdir conftest.dir
+  # Copy depcomp to subdir because otherwise we won't find it if we're
+  # using a relative directory.
+  cp "$am_depcomp" conftest.dir
+  cd conftest.dir
+  # We will build objects and dependencies in a subdirectory because
+  # it helps to detect inapplicable dependency modes.  For instance
+  # both Tru64's cc and ICC support -MD to output dependencies as a
+  # side effect of compilation, but ICC will put the dependencies in
+  # the current directory while Tru64 will put them in the object
+  # directory.
+  mkdir sub
+
+  am_cv_$1_dependencies_compiler_type=none
+  if test "$am_compiler_list" = ""; then
+     am_compiler_list=`sed -n ['s/^#*\([a-zA-Z0-9]*\))$/\1/p'] < ./depcomp`
+  fi
+  am__universal=false
+  m4_case([$1], [CC],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac],
+    [CXX],
+    [case " $depcc " in #(
+     *\ -arch\ *\ -arch\ *) am__universal=true ;;
+     esac])
+
+  for depmode in $am_compiler_list; do
+    # Setup a source with many dependencies, because some compilers
+    # like to wrap large dependency lists on column 80 (with \), and
+    # we should not choose a depcomp mode which is confused by this.
+    #
+    # We need to recreate these files for each test, as the compiler may
+    # overwrite some of them when testing with obscure command lines.
+    # This happens at least with the AIX C compiler.
+    : > sub/conftest.c
+    for i in 1 2 3 4 5 6; do
+      echo '#include "conftst'$i'.h"' >> sub/conftest.c
+      # Using ": > sub/conftst$i.h" creates only sub/conftst1.h with
+      # Solaris 10 /bin/sh.
+      echo '/* dummy */' > sub/conftst$i.h
+    done
+    echo "${am__include} ${am__quote}sub/conftest.Po${am__quote}" > confmf
+
+    # We check with '-c' and '-o' for the sake of the "dashmstdout"
+    # mode.  It turns out that the SunPro C++ compiler does not properly
+    # handle '-M -o', and we need to detect this.  Also, some Intel
+    # versions had trouble with output in subdirs.
+    am__obj=sub/conftest.${OBJEXT-o}
+    am__minus_obj="-o $am__obj"
+    case $depmode in
+    gcc)
+      # This depmode causes a compiler race in universal mode.
+      test "$am__universal" = false || continue
+      ;;
+    nosideeffect)
+      # After this tag, mechanisms are not by side-effect, so they'll
+      # only be used when explicitly requested.
+      if test "x$enable_dependency_tracking" = xyes; then
+       continue
+      else
+       break
+      fi
+      ;;
+    msvc7 | msvc7msys | msvisualcpp | msvcmsys)
+      # This compiler won't grok '-c -o', but also, the minuso test has
+      # not run yet.  These depmodes are late enough in the game, and
+      # so weak that their functioning should not be impacted.
+      am__obj=conftest.${OBJEXT-o}
+      am__minus_obj=
+      ;;
+    none) break ;;
+    esac
+    if depmode=$depmode \
+       source=sub/conftest.c object=$am__obj \
+       depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
+       $SHELL ./depcomp $depcc -c $am__minus_obj sub/conftest.c \
+         >/dev/null 2>conftest.err &&
+       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
+       grep $am__obj sub/conftest.Po > /dev/null 2>&1 &&
+       ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
+      # icc doesn't choke on unknown options, it will just issue warnings
+      # or remarks (even with -Werror).  So we grep stderr for any message
+      # that says an option was ignored or not supported.
+      # When given -MP, icc 7.0 and 7.1 complain thusly:
+      #   icc: Command line warning: ignoring option '-M'; no argument required
+      # The diagnosis changed in icc 8.0:
+      #   icc: Command line remark: option '-MP' not supported
+      if (grep 'ignoring option' conftest.err ||
+          grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else
+        am_cv_$1_dependencies_compiler_type=$depmode
+        break
+      fi
+    fi
+  done
+
+  cd ..
+  rm -rf conftest.dir
+else
+  am_cv_$1_dependencies_compiler_type=none
+fi
+])
+AC_SUBST([$1DEPMODE], [depmode=$am_cv_$1_dependencies_compiler_type])
+AM_CONDITIONAL([am__fastdep$1], [
+  test "x$enable_dependency_tracking" != xno \
+  && test "$am_cv_$1_dependencies_compiler_type" = gcc3])
+])
+
+
+# AM_SET_DEPDIR
+# -------------
+# Choose a directory name for dependency files.
+# This macro is AC_REQUIREd in _AM_DEPENDENCIES.
+AC_DEFUN([AM_SET_DEPDIR],
+[AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+AC_SUBST([DEPDIR], ["${am__leading_dot}deps"])dnl
+])
+
+
+# AM_DEP_TRACK
+# ------------
+AC_DEFUN([AM_DEP_TRACK],
+[AC_ARG_ENABLE([dependency-tracking], [dnl
+AS_HELP_STRING(
+  [--enable-dependency-tracking],
+  [do not reject slow dependency extractors])
+AS_HELP_STRING(
+  [--disable-dependency-tracking],
+  [speeds up one-time build])])
+if test "x$enable_dependency_tracking" != xno; then
+  am_depcomp="$ac_aux_dir/depcomp"
+  AMDEPBACKSLASH='\'
+  am__nodep='_no'
+fi
+AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
+AC_SUBST([AMDEPBACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([am__nodep])dnl
+_AM_SUBST_NOTMAKE([am__nodep])dnl
+])
+
+# Generate code to set up dependency tracking.              -*- Autoconf -*-
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_OUTPUT_DEPENDENCY_COMMANDS
+# ------------------------------
+AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
+[{
+  # Older Autoconf quotes --file arguments for eval, but not when files
+  # are listed without --file.  Let's play safe and only enable the eval
+  # if we detect the quoting.
+  # TODO: see whether this extra hack can be removed once we start
+  # requiring Autoconf 2.70 or later.
+  AS_CASE([$CONFIG_FILES],
+          [*\'*], [eval set x "$CONFIG_FILES"],
+          [*], [set x $CONFIG_FILES])
+  shift
+  # Used to flag and report bootstrapping failures.
+  am_rc=0
+  for am_mf
+  do
+    # Strip MF so we end up with the name of the file.
+    am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'`
+    # Check whether this is an Automake generated Makefile which includes
+    # dependency-tracking related rules and includes.
+    # Grep'ing the whole file directly is not great: AIX grep has a line
+    # limit of 2048, but all sed's we know have understand at least 4000.
+    sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \
+      || continue
+    am_dirpart=`AS_DIRNAME(["$am_mf"])`
+    am_filepart=`AS_BASENAME(["$am_mf"])`
+    AM_RUN_LOG([cd "$am_dirpart" \
+      && sed -e '/# am--include-marker/d' "$am_filepart" \
+        | $MAKE -f - am--depfiles]) || am_rc=$?
+  done
+  if test $am_rc -ne 0; then
+    AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments
+    for automatic dependency tracking.  If GNU make was not used, consider
+    re-running the configure script with MAKE="gmake" (or whatever is
+    necessary).  You can also try re-running configure with the
+    '--disable-dependency-tracking' option to at least be able to build
+    the package (albeit without support for automatic dependency tracking).])
+  fi
+  AS_UNSET([am_dirpart])
+  AS_UNSET([am_filepart])
+  AS_UNSET([am_mf])
+  AS_UNSET([am_rc])
+  rm -f conftest-deps.mk
+}
+])# _AM_OUTPUT_DEPENDENCY_COMMANDS
+
+
+# AM_OUTPUT_DEPENDENCY_COMMANDS
+# -----------------------------
+# This macro should only be invoked once -- use via AC_REQUIRE.
+#
+# This code is only required when automatic dependency tracking is enabled.
+# This creates each '.Po' and '.Plo' makefile fragment that we'll need in
+# order to bootstrap the dependency handling code.
+AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
+[AC_CONFIG_COMMANDS([depfiles],
+     [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS],
+     [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])])
+
+# Do all the work for Automake.                             -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This macro actually does too much.  Some checks are only needed if
+# your package does certain things.  But this isn't really a big deal.
+
+dnl Redefine AC_PROG_CC to automatically invoke _AM_PROG_CC_C_O.
+m4_define([AC_PROG_CC],
+m4_defn([AC_PROG_CC])
+[_AM_PROG_CC_C_O
+])
+
+# AM_INIT_AUTOMAKE(PACKAGE, VERSION, [NO-DEFINE])
+# AM_INIT_AUTOMAKE([OPTIONS])
+# -----------------------------------------------
+# The call with PACKAGE and VERSION arguments is the old style
+# call (pre autoconf-2.50), which is being phased out.  PACKAGE
+# and VERSION should now be passed to AC_INIT and removed from
+# the call to AM_INIT_AUTOMAKE.
+# We support both call styles for the transition.  After
+# the next Automake release, Autoconf can make the AC_INIT
+# arguments mandatory, and then we can depend on a new Autoconf
+# release and drop the old call support.
+AC_DEFUN([AM_INIT_AUTOMAKE],
+[AC_PREREQ([2.65])dnl
+m4_ifdef([_$0_ALREADY_INIT],
+  [m4_fatal([$0 expanded multiple times
+]m4_defn([_$0_ALREADY_INIT]))],
+  [m4_define([_$0_ALREADY_INIT], m4_expansion_stack)])dnl
+dnl Autoconf wants to disallow AM_ names.  We explicitly allow
+dnl the ones we care about.
+m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
+AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
+AC_REQUIRE([AC_PROG_INSTALL])dnl
+if test "`cd $srcdir && pwd`" != "`pwd`"; then
+  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
+  # is not polluted with repeated "-I."
+  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
+  # test to see if srcdir already configured
+  if test -f $srcdir/config.status; then
+    AC_MSG_ERROR([source directory already configured; run "make distclean" 
there first])
+  fi
+fi
+
+# test whether we have cygpath
+if test -z "$CYGPATH_W"; then
+  if (cygpath --version) >/dev/null 2>/dev/null; then
+    CYGPATH_W='cygpath -w'
+  else
+    CYGPATH_W=echo
+  fi
+fi
+AC_SUBST([CYGPATH_W])
+
+# Define the identity of the package.
+dnl Distinguish between old-style and new-style calls.
+m4_ifval([$2],
+[AC_DIAGNOSE([obsolete],
+             [$0: two- and three-arguments forms are deprecated.])
+m4_ifval([$3], [_AM_SET_OPTION([no-define])])dnl
+ AC_SUBST([PACKAGE], [$1])dnl
+ AC_SUBST([VERSION], [$2])],
+[_AM_SET_OPTIONS([$1])dnl
+dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
+m4_if(
+  m4_ifset([AC_PACKAGE_NAME], [ok]):m4_ifset([AC_PACKAGE_VERSION], [ok]),
+  [ok:ok],,
+  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
+ AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
+ AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
+
+_AM_IF_OPTION([no-define],,
+[AC_DEFINE_UNQUOTED([PACKAGE], ["$PACKAGE"], [Name of package])
+ AC_DEFINE_UNQUOTED([VERSION], ["$VERSION"], [Version number of package])])dnl
+
+# Some tools Automake needs.
+AC_REQUIRE([AM_SANITY_CHECK])dnl
+AC_REQUIRE([AC_ARG_PROGRAM])dnl
+AM_MISSING_PROG([ACLOCAL], [aclocal-${am__api_version}])
+AM_MISSING_PROG([AUTOCONF], [autoconf])
+AM_MISSING_PROG([AUTOMAKE], [automake-${am__api_version}])
+AM_MISSING_PROG([AUTOHEADER], [autoheader])
+AM_MISSING_PROG([MAKEINFO], [makeinfo])
+AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl
+AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+# For better backward compatibility.  To be removed once Automake 1.9.x
+# dies out for good.  For more background, see:
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00001.html>
+# <https://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
+AC_SUBST([mkdir_p], ['$(MKDIR_P)'])
+# We need awk for the "check" target (and possibly the TAP driver).  The
+# system "awk" is bad on some platforms.
+AC_REQUIRE([AC_PROG_AWK])dnl
+AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+AC_REQUIRE([AM_SET_LEADING_DOT])dnl
+_AM_IF_OPTION([tar-ustar], [_AM_PROG_TAR([ustar])],
+             [_AM_IF_OPTION([tar-pax], [_AM_PROG_TAR([pax])],
+                            [_AM_PROG_TAR([v7])])])
+_AM_IF_OPTION([no-dependencies],,
+[AC_PROVIDE_IFELSE([AC_PROG_CC],
+                 [_AM_DEPENDENCIES([CC])],
+                 [m4_define([AC_PROG_CC],
+                            m4_defn([AC_PROG_CC])[_AM_DEPENDENCIES([CC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_CXX],
+                 [_AM_DEPENDENCIES([CXX])],
+                 [m4_define([AC_PROG_CXX],
+                            
m4_defn([AC_PROG_CXX])[_AM_DEPENDENCIES([CXX])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJC],
+                 [_AM_DEPENDENCIES([OBJC])],
+                 [m4_define([AC_PROG_OBJC],
+                            
m4_defn([AC_PROG_OBJC])[_AM_DEPENDENCIES([OBJC])])])dnl
+AC_PROVIDE_IFELSE([AC_PROG_OBJCXX],
+                 [_AM_DEPENDENCIES([OBJCXX])],
+                 [m4_define([AC_PROG_OBJCXX],
+                            
m4_defn([AC_PROG_OBJCXX])[_AM_DEPENDENCIES([OBJCXX])])])dnl
+])
+# Variables for tags utilities; see am/tags.am
+if test -z "$CTAGS"; then
+  CTAGS=ctags
+fi
+AC_SUBST([CTAGS])
+if test -z "$ETAGS"; then
+  ETAGS=etags
+fi
+AC_SUBST([ETAGS])
+if test -z "$CSCOPE"; then
+  CSCOPE=cscope
+fi
+AC_SUBST([CSCOPE])
+
+AC_REQUIRE([AM_SILENT_RULES])dnl
+dnl The testsuite driver may need to know about EXEEXT, so add the
+dnl 'am__EXEEXT' conditional if _AM_COMPILER_EXEEXT was seen.  This
+dnl macro is hooked onto _AC_COMPILER_EXEEXT early, see below.
+AC_CONFIG_COMMANDS_PRE(dnl
+[m4_provide_if([_AM_COMPILER_EXEEXT],
+  [AM_CONDITIONAL([am__EXEEXT], [test -n "$EXEEXT"])])])dnl
+
+# POSIX will say in a future version that running "rm -f" with no argument
+# is OK; and we want to be able to make that assumption in our Makefile
+# recipes.  So use an aggressive probe to check that the usage we want is
+# actually supported "in the wild" to an acceptable degree.
+# See automake bug#10828.
+# To make any issue more visible, cause the running configure to be aborted
+# by default if the 'rm' program in use doesn't match our expectations; the
+# user can still override this though.
+if rm -f && rm -fr && rm -rf; then : OK; else
+  cat >&2 <<'END'
+Oops!
+
+Your 'rm' program seems unable to run without file operands specified
+on the command line, even when the '-f' option is present.  This is contrary
+to the behaviour of most rm programs out there, and not conforming with
+the upcoming POSIX standard: <http://austingroupbugs.net/view.php?id=542>
+
+Please tell bug-automake@gnu.org about your system, including the value
+of your $PATH and any error possibly output before this message.  This
+can help us improve future automake versions.
+
+END
+  if test x"$ACCEPT_INFERIOR_RM_PROGRAM" = x"yes"; then
+    echo 'Configuration will proceed anyway, since you have set the' >&2
+    echo 'ACCEPT_INFERIOR_RM_PROGRAM variable to "yes"' >&2
+    echo >&2
+  else
+    cat >&2 <<'END'
+Aborting the configuration process, to ensure you take notice of the issue.
+
+You can download and install GNU coreutils to get an 'rm' implementation
+that behaves properly: <https://www.gnu.org/software/coreutils/>.
+
+If you want to complete the configuration process using your problematic
+'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM
+to "yes", and re-run configure.
+
+END
+    AC_MSG_ERROR([Your 'rm' program is bad, sorry.])
+  fi
+fi
+dnl The trailing newline in this macro's definition is deliberate, for
+dnl backward compatibility and to allow trailing 'dnl'-style comments
+dnl after the AM_INIT_AUTOMAKE invocation. See automake bug#16841.
+])
+
+dnl Hook into '_AC_COMPILER_EXEEXT' early to learn its expansion.  Do not
+dnl add the conditional right here, as _AC_COMPILER_EXEEXT may be further
+dnl mangled by Autoconf and run in a shell conditional statement.
+m4_define([_AC_COMPILER_EXEEXT],
+m4_defn([_AC_COMPILER_EXEEXT])[m4_provide([_AM_COMPILER_EXEEXT])])
+
+# When config.status generates a header, we must update the stamp-h file.
+# This file resides in the same directory as the config header
+# that is generated.  The stamp files are numbered to have different names.
+
+# Autoconf calls _AC_AM_CONFIG_HEADER_HOOK (when defined) in the
+# loop where config.status creates the headers, so we can generate
+# our stamp files there.
+AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
+[# Compute $1's index in $config_headers.
+_am_arg=$1
+_am_stamp_count=1
+for _am_header in $config_headers :; do
+  case $_am_header in
+    $_am_arg | $_am_arg:* )
+      break ;;
+    * )
+      _am_stamp_count=`expr $_am_stamp_count + 1` ;;
+  esac
+done
+echo "timestamp for $_am_arg" 
>`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_SH
+# ------------------
+# Define $install_sh.
+AC_DEFUN([AM_PROG_INSTALL_SH],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+if test x"${install_sh+set}" != xset; then
+  case $am_aux_dir in
+  *\ * | *\    *)
+    install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
+  *)
+    install_sh="\${SHELL} $am_aux_dir/install-sh"
+  esac
+fi
+AC_SUBST([install_sh])])
+
+# Copyright (C) 2003-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# Check whether the underlying file-system supports filenames
+# with a leading dot.  For instance MS-DOS doesn't.
+AC_DEFUN([AM_SET_LEADING_DOT],
+[rm -rf .tst 2>/dev/null
+mkdir .tst 2>/dev/null
+if test -d .tst; then
+  am__leading_dot=.
+else
+  am__leading_dot=_
+fi
+rmdir .tst 2>/dev/null
+AC_SUBST([am__leading_dot])])
+
+# Check to see how 'make' treats includes.                 -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MAKE_INCLUDE()
+# -----------------
+# Check whether make has an 'include' directive that can support all
+# the idioms we need for our automatic dependency tracking code.
+AC_DEFUN([AM_MAKE_INCLUDE],
+[AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive])
+cat > confinc.mk << 'END'
+am__doit:
+       @echo this is the am__doit target >confinc.out
+.PHONY: am__doit
+END
+am__include="#"
+am__quote=
+# BSD make does it like this.
+echo '.include "confinc.mk" # ignored' > confmf.BSD
+# Other make implementations (GNU, Solaris 10, AIX) do it like this.
+echo 'include confinc.mk # ignored' > confmf.GNU
+_am_result=no
+for s in GNU BSD; do
+  AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out])
+  AS_CASE([$?:`cat confinc.out 2>/dev/null`],
+      ['0:this is the am__doit target'],
+      [AS_CASE([$s],
+          [BSD], [am__include='.include' am__quote='"'],
+          [am__include='include' am__quote=''])])
+  if test "$am__include" != "#"; then
+    _am_result="yes ($s style)"
+    break
+  fi
+done
+rm -f confinc.* confmf.*
+AC_MSG_RESULT([${_am_result}])
+AC_SUBST([am__include])])
+AC_SUBST([am__quote])])
+
+# Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
+
+# Copyright (C) 1997-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_MISSING_PROG(NAME, PROGRAM)
+# ------------------------------
+AC_DEFUN([AM_MISSING_PROG],
+[AC_REQUIRE([AM_MISSING_HAS_RUN])
+$1=${$1-"${am_missing_run}$2"}
+AC_SUBST($1)])
+
+# AM_MISSING_HAS_RUN
+# ------------------
+# Define MISSING if not defined so far and test if it is modern enough.
+# If it is, set am_missing_run to use it, otherwise, to nothing.
+AC_DEFUN([AM_MISSING_HAS_RUN],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([missing])dnl
+if test x"${MISSING+set}" != xset; then
+  MISSING="\${SHELL} '$am_aux_dir/missing'"
+fi
+# Use eval to expand $SHELL
+if eval "$MISSING --is-lightweight"; then
+  am_missing_run="$MISSING "
+else
+  am_missing_run=
+  AC_MSG_WARN(['missing' script is too old or missing])
+fi
+])
+
+# Helper functions for option handling.                     -*- Autoconf -*-
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_MANGLE_OPTION(NAME)
+# -----------------------
+AC_DEFUN([_AM_MANGLE_OPTION],
+[[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])])
+
+# _AM_SET_OPTION(NAME)
+# --------------------
+# Set option NAME.  Presently that only means defining a flag for this option.
+AC_DEFUN([_AM_SET_OPTION],
+[m4_define(_AM_MANGLE_OPTION([$1]), [1])])
+
+# _AM_SET_OPTIONS(OPTIONS)
+# ------------------------
+# OPTIONS is a space-separated list of Automake options.
+AC_DEFUN([_AM_SET_OPTIONS],
+[m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])])
+
+# _AM_IF_OPTION(OPTION, IF-SET, [IF-NOT-SET])
+# -------------------------------------------
+# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise.
+AC_DEFUN([_AM_IF_OPTION],
+[m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])])
+
+# Copyright (C) 1999-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_CC_C_O
+# ---------------
+# Like AC_PROG_CC_C_O, but changed for automake.  We rewrite AC_PROG_CC
+# to automatically call this.
+AC_DEFUN([_AM_PROG_CC_C_O],
+[AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
+AC_REQUIRE_AUX_FILE([compile])dnl
+AC_LANG_PUSH([C])dnl
+AC_CACHE_CHECK(
+  [whether $CC understands -c and -o together],
+  [am_cv_prog_cc_c_o],
+  [AC_LANG_CONFTEST([AC_LANG_PROGRAM([])])
+  # Make sure it works both with $CC and with simple cc.
+  # Following AC_PROG_CC_C_O, we do the test twice because some
+  # compilers refuse to overwrite an existing .o file with -o,
+  # though they will create one.
+  am_cv_prog_cc_c_o=yes
+  for am_i in 1 2; do
+    if AM_RUN_LOG([$CC -c conftest.$ac_ext -o conftest2.$ac_objext]) \
+         && test -f conftest2.$ac_objext; then
+      : OK
+    else
+      am_cv_prog_cc_c_o=no
+      break
+    fi
+  done
+  rm -f core conftest*
+  unset am_i])
+if test "$am_cv_prog_cc_c_o" != yes; then
+   # Losing compiler, so override with the script.
+   # FIXME: It is wrong to rewrite CC.
+   # But if we don't then we get into trouble of one sort or another.
+   # A longer-term fix would be to have automake use am__CC in this case,
+   # and then we could set am__CC="\$(top_srcdir)/compile \$(CC)"
+   CC="$am_aux_dir/compile $CC"
+fi
+AC_LANG_POP([C])])
+
+# For backward compatibility.
+AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_RUN_LOG(COMMAND)
+# -------------------
+# Run COMMAND, save the exit status in ac_status, and log it.
+# (This has been adapted from Autoconf's _AC_RUN_LOG macro.)
+AC_DEFUN([AM_RUN_LOG],
+[{ echo "$as_me:$LINENO: $1" >&AS_MESSAGE_LOG_FD
+   ($1) >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD
+   ac_status=$?
+   echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD
+   (exit $ac_status); }])
+
+# Check to make sure that the build environment is sane.    -*- Autoconf -*-
+
+# Copyright (C) 1996-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SANITY_CHECK
+# ---------------
+AC_DEFUN([AM_SANITY_CHECK],
+[AC_MSG_CHECKING([whether build environment is sane])
+# Reject unsafe characters in $srcdir or the absolute working directory
+# name.  Accept space and tab only in the latter.
+am_lf='
+'
+case `pwd` in
+  *[[\\\"\#\$\&\'\`$am_lf]]*)
+    AC_MSG_ERROR([unsafe absolute working directory name]);;
+esac
+case $srcdir in
+  *[[\\\"\#\$\&\'\`$am_lf\ \   ]]*)
+    AC_MSG_ERROR([unsafe srcdir value: '$srcdir']);;
+esac
+
+# Do 'set' in a subshell so we don't clobber the current shell's
+# arguments.  Must try -L first in case configure is actually a
+# symlink; some systems play weird games with the mod time of symlinks
+# (eg FreeBSD returns the mod time of the symlink's containing
+# directory).
+if (
+   am_has_slept=no
+   for am_try in 1 2; do
+     echo "timestamp, slept: $am_has_slept" > conftest.file
+     set X `ls -Lt "$srcdir/configure" conftest.file 2> /dev/null`
+     if test "$[*]" = "X"; then
+       # -L didn't work.
+       set X `ls -t "$srcdir/configure" conftest.file`
+     fi
+     if test "$[*]" != "X $srcdir/configure conftest.file" \
+       && test "$[*]" != "X conftest.file $srcdir/configure"; then
+
+       # If neither matched, then we have a broken ls.  This can happen
+       # if, for instance, CONFIG_SHELL is bash and it inherits a
+       # broken ls alias from the environment.  This has actually
+       # happened.  Such a system could not be considered "sane".
+       AC_MSG_ERROR([ls -t appears to fail.  Make sure there is not a broken
+  alias in your environment])
+     fi
+     if test "$[2]" = conftest.file || test $am_try -eq 2; then
+       break
+     fi
+     # Just in case.
+     sleep 1
+     am_has_slept=yes
+   done
+   test "$[2]" = conftest.file
+   )
+then
+   # Ok.
+   :
+else
+   AC_MSG_ERROR([newly created file is older than distributed files!
+Check your system clock])
+fi
+AC_MSG_RESULT([yes])
+# If we didn't sleep, we still need to ensure time stamps of config.status and
+# generated files are strictly newer.
+am_sleep_pid=
+if grep 'slept: no' conftest.file >/dev/null 2>&1; then
+  ( sleep 1 ) &
+  am_sleep_pid=$!
+fi
+AC_CONFIG_COMMANDS_PRE(
+  [AC_MSG_CHECKING([that generated files are newer than configure])
+   if test -n "$am_sleep_pid"; then
+     # Hide warnings about reused PIDs.
+     wait $am_sleep_pid 2>/dev/null
+   fi
+   AC_MSG_RESULT([done])])
+rm -f conftest.file
+])
+
+# Copyright (C) 2009-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_SILENT_RULES([DEFAULT])
+# --------------------------
+# Enable less verbose build rules; with the default set to DEFAULT
+# ("yes" being less verbose, "no" or empty being verbose).
+AC_DEFUN([AM_SILENT_RULES],
+[AC_ARG_ENABLE([silent-rules], [dnl
+AS_HELP_STRING(
+  [--enable-silent-rules],
+  [less verbose build output (undo: "make V=1")])
+AS_HELP_STRING(
+  [--disable-silent-rules],
+  [verbose build output (undo: "make V=0")])dnl
+])
+case $enable_silent_rules in @%:@ (((
+  yes) AM_DEFAULT_VERBOSITY=0;;
+   no) AM_DEFAULT_VERBOSITY=1;;
+    *) AM_DEFAULT_VERBOSITY=m4_if([$1], [yes], [0], [1]);;
+esac
+dnl
+dnl A few 'make' implementations (e.g., NonStop OS and NextStep)
+dnl do not support nested variable expansions.
+dnl See automake bug#9928 and bug#10237.
+am_make=${MAKE-make}
+AC_CACHE_CHECK([whether $am_make supports nested variables],
+   [am_cv_make_support_nested_variables],
+   [if AS_ECHO([['TRUE=$(BAR$(V))
+BAR0=false
+BAR1=true
+V=1
+am__doit:
+       @$(TRUE)
+.PHONY: am__doit']]) | $am_make -f - >/dev/null 2>&1; then
+  am_cv_make_support_nested_variables=yes
+else
+  am_cv_make_support_nested_variables=no
+fi])
+if test $am_cv_make_support_nested_variables = yes; then
+  dnl Using '$V' instead of '$(V)' breaks IRIX make.
+  AM_V='$(V)'
+  AM_DEFAULT_V='$(AM_DEFAULT_VERBOSITY)'
+else
+  AM_V=$AM_DEFAULT_VERBOSITY
+  AM_DEFAULT_V=$AM_DEFAULT_VERBOSITY
+fi
+AC_SUBST([AM_V])dnl
+AM_SUBST_NOTMAKE([AM_V])dnl
+AC_SUBST([AM_DEFAULT_V])dnl
+AM_SUBST_NOTMAKE([AM_DEFAULT_V])dnl
+AC_SUBST([AM_DEFAULT_VERBOSITY])dnl
+AM_BACKSLASH='\'
+AC_SUBST([AM_BACKSLASH])dnl
+_AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl
+])
+
+# Copyright (C) 2001-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# AM_PROG_INSTALL_STRIP
+# ---------------------
+# One issue with vendor 'install' (even GNU) is that you can't
+# specify the program used to strip binaries.  This is especially
+# annoying in cross-compiling environments, where the build's strip
+# is unlikely to handle the host's binaries.
+# Fortunately install-sh will honor a STRIPPROG variable, so we
+# always use install-sh in "make install-strip", and initialize
+# STRIPPROG with the value of the STRIP variable (set by the user).
+AC_DEFUN([AM_PROG_INSTALL_STRIP],
+[AC_REQUIRE([AM_PROG_INSTALL_SH])dnl
+# Installed binaries are usually stripped using 'strip' when the user
+# run "make install-strip".  However 'strip' might not be the right
+# tool to use in cross-compilation environments, therefore Automake
+# will honor the 'STRIP' environment variable to overrule this program.
+dnl Don't test for $cross_compiling = yes, because it might be 'maybe'.
+if test "$cross_compiling" != no; then
+  AC_CHECK_TOOL([STRIP], [strip], :)
+fi
+INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+AC_SUBST([INSTALL_STRIP_PROGRAM])])
+
+# Copyright (C) 2006-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_SUBST_NOTMAKE(VARIABLE)
+# ---------------------------
+# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
+# This macro is traced by Automake.
+AC_DEFUN([_AM_SUBST_NOTMAKE])
+
+# AM_SUBST_NOTMAKE(VARIABLE)
+# --------------------------
+# Public sister of _AM_SUBST_NOTMAKE.
+AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)])
+
+# Check how to create a tarball.                            -*- Autoconf -*-
+
+# Copyright (C) 2004-2021 Free Software Foundation, Inc.
+#
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# _AM_PROG_TAR(FORMAT)
+# --------------------
+# Check how to create a tarball in format FORMAT.
+# FORMAT should be one of 'v7', 'ustar', or 'pax'.
+#
+# Substitute a variable $(am__tar) that is a command
+# writing to stdout a FORMAT-tarball containing the directory
+# $tardir.
+#     tardir=directory && $(am__tar) > result.tar
+#
+# Substitute a variable $(am__untar) that extract such
+# a tarball read from stdin.
+#     $(am__untar) < result.tar
+#
+AC_DEFUN([_AM_PROG_TAR],
+[# Always define AMTAR for backward compatibility.  Yes, it's still used
+# in the wild :-(  We should find a proper way to deprecate it ...
+AC_SUBST([AMTAR], ['$${TAR-tar}'])
+
+# We'll loop over all known methods to create a tar archive until one works.
+_am_tools='gnutar m4_if([$1], [ustar], [plaintar]) pax cpio none'
+
+m4_if([$1], [v7],
+  [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'],
+
+  [m4_case([$1],
+    [ustar],
+     [# The POSIX 1988 'ustar' format is defined with fixed-size fields.
+      # There is notably a 21 bits limit for the UID and the GID.  In fact,
+      # the 'pax' utility can hang on bigger UID/GID (see automake bug#8343
+      # and bug#13588).
+      am_max_uid=2097151 # 2^21 - 1
+      am_max_gid=$am_max_uid
+      # The $UID and $GID variables are not portable, so we need to resort
+      # to the POSIX-mandated id(1) utility.  Errors in the 'id' calls
+      # below are definitely unexpected, so allow the users to see them
+      # (that is, avoid stderr redirection).
+      am_uid=`id -u || echo unknown`
+      am_gid=`id -g || echo unknown`
+      AC_MSG_CHECKING([whether UID '$am_uid' is supported by ustar format])
+      if test $am_uid -le $am_max_uid; then
+         AC_MSG_RESULT([yes])
+      else
+         AC_MSG_RESULT([no])
+         _am_tools=none
+      fi
+      AC_MSG_CHECKING([whether GID '$am_gid' is supported by ustar format])
+      if test $am_gid -le $am_max_gid; then
+         AC_MSG_RESULT([yes])
+      else
+        AC_MSG_RESULT([no])
+        _am_tools=none
+      fi],
+
+  [pax],
+    [],
+
+  [m4_fatal([Unknown tar format])])
+
+  AC_MSG_CHECKING([how to create a $1 tar archive])
+
+  # Go ahead even if we have the value already cached.  We do so because we
+  # need to set the values for the 'am__tar' and 'am__untar' variables.
+  _am_tools=${am_cv_prog_tar_$1-$_am_tools}
+
+  for _am_tool in $_am_tools; do
+    case $_am_tool in
+    gnutar)
+      for _am_tar in tar gnutar gtar; do
+        AM_RUN_LOG([$_am_tar --version]) && break
+      done
+      am__tar="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - 
"'"$$tardir"'
+      am__tar_="$_am_tar --format=m4_if([$1], [pax], [posix], [$1]) -chf - 
"'"$tardir"'
+      am__untar="$_am_tar -xf -"
+      ;;
+    plaintar)
+      # Must skip GNU tar: if it does not support --format= it doesn't create
+      # ustar tarball either.
+      (tar --version) >/dev/null 2>&1 && continue
+      am__tar='tar chf - "$$tardir"'
+      am__tar_='tar chf - "$tardir"'
+      am__untar='tar xf -'
+      ;;
+    pax)
+      am__tar='pax -L -x $1 -w "$$tardir"'
+      am__tar_='pax -L -x $1 -w "$tardir"'
+      am__untar='pax -r'
+      ;;
+    cpio)
+      am__tar='find "$$tardir" -print | cpio -o -H $1 -L'
+      am__tar_='find "$tardir" -print | cpio -o -H $1 -L'
+      am__untar='cpio -i -H $1 -d'
+      ;;
+    none)
+      am__tar=false
+      am__tar_=false
+      am__untar=false
+      ;;
+    esac
+
+    # If the value was cached, stop now.  We just wanted to have am__tar
+    # and am__untar set.
+    test -n "${am_cv_prog_tar_$1}" && break
+
+    # tar/untar a dummy directory, and stop if the command works.
+    rm -rf conftest.dir
+    mkdir conftest.dir
+    echo GrepMe > conftest.dir/file
+    AM_RUN_LOG([tardir=conftest.dir && eval $am__tar_ >conftest.tar])
+    rm -rf conftest.dir
+    if test -s conftest.tar; then
+      AM_RUN_LOG([$am__untar <conftest.tar])
+      AM_RUN_LOG([cat conftest.dir/file])
+      grep GrepMe conftest.dir/file >/dev/null 2>&1 && break
+    fi
+  done
+  rm -rf conftest.dir
+
+  AC_CACHE_VAL([am_cv_prog_tar_$1], [am_cv_prog_tar_$1=$_am_tool])
+  AC_MSG_RESULT([$am_cv_prog_tar_$1])])
+
+AC_SUBST([am__tar])
+AC_SUBST([am__untar])
+]) # _AM_PROG_TAR
+
+m4_include([m4/ax_compare_version.m4])
+m4_include([m4/ax_have_epoll.m4])
+m4_include([m4/ax_lib_postgresql.m4])
+m4_include([m4/ax_prog_doxygen.m4])
+m4_include([m4/gettext.m4])
+m4_include([m4/iconv.m4])
+m4_include([m4/intlmacosx.m4])
+m4_include([m4/lib-ld.m4])
+m4_include([m4/lib-link.m4])
+m4_include([m4/lib-prefix.m4])
+m4_include([m4/libcurl.m4])
+m4_include([m4/libgcrypt.m4])
+m4_include([m4/libgnurl.m4])
+m4_include([m4/libtool.m4])
+m4_include([m4/longlong.m4])
+m4_include([m4/ltoptions.m4])
+m4_include([m4/ltsugar.m4])
+m4_include([m4/ltversion.m4])
+m4_include([m4/lt~obsolete.m4])
+m4_include([m4/m4_ax_python_module.m4])
+m4_include([m4/mhd.m4])
+m4_include([m4/nls.m4])
+m4_include([m4/po.m4])
+m4_include([m4/progtest.m4])
diff --git a/bootstrap b/bootstrap
new file mode 100755
index 0000000..da4f632
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,42 @@
+#!/bin/sh
+# This file is in the public domain.
+
+set -eu
+
+if ! git --version >/dev/null; then
+  echo "git not installed"
+  exit 1
+fi
+
+if ! htmlark --version >/dev/null; then
+  echo "htmlark not installed"
+  echo "Run 'pip install htmlark'"
+  exit 1
+fi
+
+
+echo "$0: Updating submodules"
+echo | git submodule update --init --force --remote
+
+# Generate based on pinned submodule
+./contrib/gana-generate.sh
+
+# This is more portable than `which' but comes with
+# the caveat of not(?) properly working on busybox's ash:
+existence()
+{
+    command -v "$1" >/dev/null 2>&1
+}
+
+
+if existence uncrustify; then
+    echo "Installing uncrustify hook and configuration"
+    # Install uncrustify format symlink (if possible)
+    ln -s contrib/uncrustify.cfg uncrustify.cfg 2> /dev/null || true
+    # Install pre-commit hook (if possible)
+    ln -s ../../contrib/uncrustify_precommit .git/hooks/pre-commit 2> 
/dev/null || true
+else
+    echo "Uncrustify not detected, hook not installed. Please install 
uncrustify if you plan on doing development"
+fi
+
+autoreconf -fi
diff --git a/ci/Containerfile b/ci/Containerfile
new file mode 100644
index 0000000..8b07921
--- /dev/null
+++ b/ci/Containerfile
@@ -0,0 +1,71 @@
+FROM docker.io/library/debian:bookworm
+
+ENV DEBIAN_FRONTEND=noninteractive
+
+RUN apt-get update -yqq && \
+    apt-get install -yqq \
+                  autoconf \
+                  autopoint \
+                  curl \
+                   git \
+                  libcurl4-gnutls-dev \
+                  libgcrypt-dev \
+                  libidn11-dev \
+                  libjansson-dev \
+                   libmicrohttpd-dev \
+                  libpq-dev \
+                  libqrencode-dev \
+                   libsodium-dev \
+                   libtool \
+                  libunistring-dev \
+                  make \
+                  pkg-config \
+                  python3-pip \
+                  python3-sphinx \
+                  python3-sphinx-rtd-theme \
+                   recutils \
+                   texinfo \
+                  zlib1g-dev
+
+# Debian packaging tools
+RUN apt-get install -yqq \
+                   po-debconf \
+                   build-essential \
+                   debhelper-compat \
+                   devscripts
+
+# Documentation dependencies
+RUN apt-get install -yqq \
+                   doxygen \
+                   graphviz
+
+# Test suite dependencies
+RUN apt-get install -yqq \
+                  jq \
+                   postgresql \
+                   sudo \
+                   wget
+
+# Install Taler (and friends) packages
+RUN curl -sS https://deb.taler.net/apt-nightly/taler-bookworm-ci.sources \
+    | tee /etc/apt/sources.list.d/taler-bookworm-ci.sources
+
+RUN echo '\
+Package: * \n\
+Pin: origin "deb.taler.net" \n\
+Pin-Priority: 999' > /etc/apt/preferences.d/taler
+
+RUN cat /etc/apt/preferences.d/taler && \
+    apt-get update -y && \
+    apt-get install -y \
+                   libgnunet-dev \
+                   libgnunet \
+                   gnunet \
+&& rm -rf /var/lib/apt/lists/*
+
+
+RUN pip3 install --break-system-packages htmlark
+
+WORKDIR /workdir
+
+CMD ["bash", "/workdir/ci/ci.sh"]
diff --git a/ci/jobs/0-codespell/config.ini b/ci/jobs/0-codespell/config.ini
new file mode 100644
index 0000000..1c52b6a
--- /dev/null
+++ b/ci/jobs/0-codespell/config.ini
@@ -0,0 +1,5 @@
+[build]
+HALT_ON_FAILURE = False
+WARN_ON_FAILURE = True
+CONTAINER_BUILD = False
+CONTAINER_NAME = nixery.dev/shell/codespell
diff --git a/ci/jobs/0-codespell/dictionary.txt 
b/ci/jobs/0-codespell/dictionary.txt
new file mode 100644
index 0000000..b4d6433
--- /dev/null
+++ b/ci/jobs/0-codespell/dictionary.txt
@@ -0,0 +1,44 @@
+# List of "words" that codespell should ignore in our sources.
+#
+# Note: The word sensitivity depends on how the to-be-ignored word is
+#  spelled in codespell_lib/data/dictionary.txt.  F.e. if there is a word
+# 'foo' and you add 'Foo' _here_, codespell will continue to complain
+#  about 'Foo'.
+#
+BRE
+ND
+Nd
+TE
+TEH
+UPDATEing
+WAN
+aci
+acn
+ba
+bre
+cant
+complet
+doas
+ect
+ehr
+fo
+hel
+ifset
+ist
+keypair
+nd
+onl
+openin
+ot
+ser
+sie
+som
+sover
+te
+te
+teh
+tha
+ths
+updateing
+wan
+wih
diff --git a/ci/jobs/0-codespell/job.sh b/ci/jobs/0-codespell/job.sh
new file mode 100755
index 0000000..58bd07b
--- /dev/null
+++ b/ci/jobs/0-codespell/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+codespell -I "${job_dir}"/dictionary.txt -S 
"*.bib,*.bst,*.cls,*.json,*.png,*.svg,*.wav,*.gz,*/templating/test?/**,**/auditor/*.sql,**/templating/mustach**,*.fees,*key,*.tag,*.info,*.latexmkrc,*.ecc,*.jpg,*.zkey,*.sqlite,*/contrib/hellos/**,*/vpn/tests/**,*.priv,*.file,*.tgz,*.woff,*.gif,*.odt,*.fee,*.deflate,*.dat,*.jpeg,*.eps,*.odg,*/m4/ax_lib_postgresql.m4,*/m4/libgcrypt.m4,*.rpath,config.status,ABOUT-NLS,*/doc/texinfo.tex,*.PNG,*.??.json,*.docx,*.ods,*.doc,*.docx,*.xcf,*.xlsx,*.ecc,*
 [...]
diff --git a/ci/jobs/1-build/build.sh b/ci/jobs/1-build/build.sh
new file mode 100755
index 0000000..b113742
--- /dev/null
+++ b/ci/jobs/1-build/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -exuo pipefail
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+           --enable-logging=verbose \
+           --disable-doc
+
+make
diff --git a/ci/jobs/1-build/job.sh b/ci/jobs/1-build/job.sh
new file mode 100755
index 0000000..8d79902
--- /dev/null
+++ b/ci/jobs/1-build/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/build.sh
diff --git a/ci/jobs/2-test/job.sh b/ci/jobs/2-test/job.sh
new file mode 100755
index 0000000..bfb24e3
--- /dev/null
+++ b/ci/jobs/2-test/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/test.sh
diff --git a/ci/jobs/2-test/test.sh b/ci/jobs/2-test/test.sh
new file mode 100755
index 0000000..39fca5c
--- /dev/null
+++ b/ci/jobs/2-test/test.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -evu
+
+./bootstrap
+./configure CFLAGS="-ggdb -O0" \
+           --enable-logging=verbose \
+           --disable-doc
+make
+make install
+
+sudo -u postgres /usr/lib/postgresql/15/bin/postgres -D 
/etc/postgresql/15/main -h localhost -p 5432 &
+sleep 10
+sudo -u postgres createuser -p 5432 root
+sudo -u postgres createdb -p 5432 -O root talercheck
+
+check_command()
+{
+       # Set LD_LIBRARY_PATH so tests can find the installed libs
+       LD_LIBRARY_PATH=/usr/local/lib PGPORT=5432 make check
+}
+
+print_logs()
+{
+       for i in src/*/test-suite.log
+       do
+               for FAILURE in $(grep '^FAIL:' ${i} | cut -d' ' -f2)
+               do
+                       echo "Printing ${FAILURE}.log"
+                       tail "$(dirname $i)/${FAILURE}.log"
+               done
+       done
+}
+
+if ! check_command ; then
+       print_logs
+       exit 1
+fi
diff --git a/ci/jobs/3-docs/docs.sh b/ci/jobs/3-docs/docs.sh
new file mode 100755
index 0000000..fe2b968
--- /dev/null
+++ b/ci/jobs/3-docs/docs.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+set -exuo pipefail
+
+./bootstrap
+./configure --enable-only-doc
+
+pushd ./doc/doxygen/
+
+make full
+
+popd
diff --git a/ci/jobs/3-docs/job.sh b/ci/jobs/3-docs/job.sh
new file mode 100755
index 0000000..a72bca4
--- /dev/null
+++ b/ci/jobs/3-docs/job.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+set -exuo pipefail
+
+job_dir=$(dirname "${BASH_SOURCE[0]}")
+
+"${job_dir}"/docs.sh
diff --git a/ci/jobs/4-deb-package/install-fix.patch 
b/ci/jobs/4-deb-package/install-fix.patch
new file mode 100644
index 0000000..8334c5a
--- /dev/null
+++ b/ci/jobs/4-deb-package/install-fix.patch
@@ -0,0 +1,13 @@
+diff --git a/debian/taler-exchange.install b/debian/taler-exchange.install
+index 631c270b..072c6231 100644
+--- a/debian/taler-exchange.install
++++ b/debian/taler-exchange.install
+@@ -36,6 +36,6 @@ usr/share/taler/exchange/templates/*.must
+ debian/etc-taler-exchange/* etc/
+ 
+ # Terms of service / privacy policy templates
+-usr/share/taler/exchange/*.rst
++#usr/share/taler/exchange/terms/*.rst
+ # Translations of ToS/PP
+-usr/share/taler/exchange/locale/*/LC_MESSAGES/*.po
++#usr/share/taler/exchange/terms/locale/*/LC_MESSAGES/*.po
diff --git a/ci/jobs/4-deb-package/job.sh b/ci/jobs/4-deb-package/job.sh
new file mode 100755
index 0000000..dc78cdf
--- /dev/null
+++ b/ci/jobs/4-deb-package/job.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+set -exuo pipefail
+# This file is in the public domain.
+# Helper script to build the latest DEB packages in the container.
+
+unset LD_LIBRARY_PATH
+
+
+git apply ./ci/jobs/2-deb-package/install-fix.patch
+
+# Get current version from debian/control file.
+DEB_VERSION=$(dpkg-parsechangelog -S Version)
+
+# Install build-time dependencies.
+mk-build-deps --install --tool='apt-get -o Debug::pkgProblemResolver=yes 
--no-install-recommends --yes' debian/control
+
+# We do a sparse checkout, so we need to hint
+# the version to the build system.
+echo $DEB_VERSION > .version
+./bootstrap
+dpkg-buildpackage -rfakeroot -b -uc -us
+
+ls ../*.deb
+mv ../*.deb /artifacts/
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..fc08c5f
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,541 @@
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+#
+#  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, If not, see 
<http://www.gnu.org/license>
+#
+#
+AC_PREREQ([2.69])
+AC_INIT([taler-exchange],[0.9.2],[taler-bug@gnunet.org])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_SRCDIR([src/util/util.c])
+AC_CONFIG_HEADERS([taler_config.h])
+AC_CANONICAL_TARGET
+AC_CANONICAL_HOST
+AC_CANONICAL_BUILD
+# support for non-recursive builds
+AM_INIT_AUTOMAKE([subdir-objects 1.9 tar-pax])
+
+# pretty build rules
+AM_SILENT_RULES([yes])
+
+AC_CONFIG_MACRO_DIR([m4])
+AC_PROG_AWK
+AC_PROG_CC
+AC_PROG_OBJC
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+AM_PROG_CC_C_O
+
+LT_INIT([disable-static dlopen])
+
+DX_INIT_DOXYGEN([taler-exchange],,,
+ DX_PS_FEATURE(OFF),
+ DX_PDF_FEATURE(OFF),
+ DX_RTF_FEATURE(OFF),
+ DX_CHI_FEATURE(OFF),
+ DX_XML_FEATURE(OFF))
+
+AC_MSG_CHECKING([whether to compile documentation ONLY])
+AC_ARG_ENABLE([only-doc],
+  [AS_HELP_STRING([--enable-only-doc], [only compile Taler documentation])],
+  [doc_only=${enableval}],
+  [doc_only=no])
+AC_MSG_RESULT($doc_only)
+AM_CONDITIONAL([DOC_ONLY], [test "x$doc_only" = "xyes"])
+
+
+# Not indented, as most of the file falls under this one...
+AS_IF([test "x$doc_only" != xyes],[
+
+# Force some CFLAGS
+CFLAGS="-Wall -Wno-address-of-packed-member $CFLAGS"
+
+TALER_LIB_LDFLAGS="-export-dynamic -no-undefined"
+TALER_PLUGIN_LDFLAGS="-export-dynamic -avoid-version -module -no-undefined"
+
+AC_SUBST(TALER_LIB_LDFLAGS)
+AC_SUBST(TALER_PLUGIN_LDFLAGS)
+
+
+# Checks for header files.
+AC_CHECK_HEADERS([stdint.h stdlib.h string.h unistd.h sys/socket.h sys/un.h 
netinet/in.h netinet/ip.h])
+
+AX_PYTHON_MODULE([jinja2],true)
+
+
+# Require minimum libgcrypt version
+need_libgcrypt_version=1.6.1
+AC_DEFINE_UNQUOTED([NEED_LIBGCRYPT_VERSION], ["$need_libgcrypt_version"],
+                                             [minimum version of libgcrypt 
required])
+AM_PATH_LIBGCRYPT([$need_libgcrypt_version])
+
+
+
+# should expensive tests be run?
+AC_MSG_CHECKING(whether to run expensive tests)
+AC_ARG_ENABLE([expensivetests],
+   [AS_HELP_STRING([--enable-expensivetests], [enable running expensive 
testcases])],
+   [enable_expensive=${enableval}],
+   [enable_expensive=no])
+AC_MSG_RESULT($enable_expensive)
+AM_CONDITIONAL([HAVE_EXPENSIVE_TESTS], [test "x$enable_expensive" = "xyes"])
+
+
+AC_MSG_CHECKING(whether to enable epoll)
+AC_ARG_ENABLE([[epoll]],
+  [AS_HELP_STRING([[--enable-epoll[=ARG]]], [enable epoll support (yes, no, 
auto) [auto]])],
+    [enable_epoll=${enableval}],
+    [enable_epoll='auto']
+  )
+AC_MSG_RESULT($enable_epoll)
+AM_CONDITIONAL([MHD_HAVE_EPOLL], [test "x$enable_epoll" = "xyes"])
+
+AS_IF([test "$enable_epoll" != "no"],
+      [AX_HAVE_EPOLL
+       AS_IF([test "${ax_cv_have_epoll}" = "yes"],
+          [AC_DEFINE([[EPOLL_SUPPORT]],[[1]],[Define to 1 to enable epoll 
support])
+           enable_epoll='yes'],
+       [AS_IF([test "$enable_epoll" = "yes"],
+              AC_MSG_ERROR([[Support for epoll was explicitly requested but 
cannot be enabled on this platform.]]))
+        enable_epoll='no'])])
+
+
+AS_IF([test "x$enable_epoll" = "xyes"],
+  AC_CACHE_CHECK([for epoll_create1()],
+                 [mhd_cv_have_epoll_create1], [
+    AC_LINK_IFELSE([
+      AC_LANG_PROGRAM([[
+#include <sys/epoll.h>
+        ]], [[
+int fd;
+fd = epoll_create1(EPOLL_CLOEXEC);]])],
+      [mhd_cv_have_epoll_create1=yes],
+      [mhd_cv_have_epoll_create1=no])])
+  AS_IF([test "x$mhd_cv_have_epoll_create1" = "xyes"],[
+    AC_DEFINE([[HAVE_EPOLL_CREATE1]], [[1]], [Define if you have epoll_create1 
function.])]))
+
+
+# Check for GNUnet's libgnunetutil.
+libgnunetutil=0
+AC_MSG_CHECKING([for libgnunetutil])
+AC_ARG_WITH(gnunet,
+            [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet 
installation])],
+            [AC_MSG_RESULT([given as $with_gnunet])],
+            [AC_MSG_RESULT(not given)
+             with_gnunet=yes])
+AS_CASE([$with_gnunet],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-gnunet is required])],
+        [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
+         CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
+AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h],
+ [AC_CHECK_LIB([gnunetutil], [GNUNET_SCHEDULER_run], libgnunetutil=1)])
+AS_IF([test $libgnunetutil != 1],
+  [AC_MSG_ERROR([[
+***
+*** You need libgnunetutil >= 0.19.0 to build this program.
+*** This library is part of GNUnet, available at
+***   https://gnunet.org
+*** ]])])
+
+
+# Check for GNUnet's libgnunetjson.
+libgnunetjson=0
+AC_MSG_CHECKING([for libgnunetjson])
+AC_ARG_WITH(gnunet,
+            [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet 
installation])],
+            [AC_MSG_RESULT([given as $with_gnunet])],
+            [AC_MSG_RESULT(not given)
+             with_gnunet=yes])
+AS_CASE([$with_gnunet],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-gnunet is required])],
+        [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
+         CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
+AC_CHECK_HEADERS([gnunet/gnunet_json_lib.h],
+ [AC_CHECK_LIB([gnunetjson], [GNUNET_JSON_parse], libgnunetjson=1)])
+AS_IF([test $libgnunetjson != 1],
+  [AC_MSG_ERROR([[
+***
+*** You need libgnunetjson to build this program.
+*** Make sure you have libjansson installed while
+*** building GNUnet.
+*** ]])])
+
+# check for gettext
+AM_GNU_GETTEXT([external])
+AM_GNU_GETTEXT_VERSION([0.19.8])
+
+
+# Save before checking libgnurl/libcurl
+CFLAGS_SAVE=$CFLAGS
+LDFLAGS_SAVE=$LDFLAGS
+LIBS_SAVE=$LIBS
+
+# check for libgnurl
+# libgnurl
+LIBGNURL_CHECK_CONFIG(,7.34.0,gnurl=1,gnurl=0)
+LIBCURL_CHECK_CONFIG(,7.34.0,[curl=1],[curl=0])
+
+# cURL must support CURLINFO_TLS_SESSION, version >= 7.34
+AS_IF([test "x$curl" = x1],[
+ AC_CHECK_HEADER([curl/curl.h],
+  [AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=1],[curl=0],[[#include 
<curl/curl.h>]])],
+  [curl=0])
+])
+
+
+# libcurl and libgnurl should be mutually exclusive
+AS_IF([test "$gnurl" = 1],
+      [AM_CONDITIONAL(HAVE_LIBGNURL, true)
+       AC_DEFINE([HAVE_LIBGNURL],[1],[Have libgnurl])
+       AM_CONDITIONAL(HAVE_LIBCURL, false)
+       AC_DEFINE([HAVE_LIBCURL],[0],[Lacking libcurl])
+       [LIBGNURLCURL_LIBS="-lgnurl"]],
+      [AS_IF([test "$curl" = 1],
+             [AM_CONDITIONAL(HAVE_LIBGNURL, false)
+              AC_DEFINE([HAVE_LIBGNURL],[0],[Lacking libgnurl])
+              AM_CONDITIONAL(HAVE_LIBCURL, true)
+              AC_DEFINE([HAVE_LIBCURL],[1],[Have libcurl])
+              [LIBGNURLCURL_LIBS="-lcurl"]],
+             [AC_MSG_ERROR([FATAL: No libgnurl/libcurl])])])
+
+AC_SUBST([LIBGNURLCURL_LIBS])
+
+# Check for GNUnet's libgnunetcurl.
+libgnunetcurl=0
+AC_MSG_CHECKING([for libgnunetcurl])
+AC_ARG_WITH(gnunet,
+            [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet 
installation])],
+            [AC_MSG_RESULT([given as $with_gnunet])],
+            [AC_MSG_RESULT(not given)
+             with_gnunet=yes])
+AS_CASE([$with_gnunet],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-gnunet is required])],
+        [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
+         CPPFLAGS="-I$with_gnunet/include $CPPFLAGS"])
+AC_CHECK_HEADERS([gnunet/gnunet_curl_lib.h],
+ [AC_CHECK_LIB([gnunetcurl], [GNUNET_CURL_get_select_info], libgnunetcurl=1)])
+AS_IF([test $libgnunetcurl != 1],
+  [AC_MSG_ERROR([[
+***
+*** You need libgnunetcurl to build this program.
+*** Make sure you have libcurl or libgnurl installed while
+*** building GNUnet.
+*** ]])])
+
+
+# Restore after gnurl/curl checks messed up these values
+CFLAGS=$CFLAGS_SAVE
+LDFLAGS=$LDFLAGS_SAVE
+LIBS=$LIBS_SAVE
+
+# test for postgres
+AX_LIB_POSTGRESQL([13.0])
+AS_IF([test "x$found_postgresql" = "xyes"],
+  [SAVE_CPPFLAGS="$CPPFLAGS"
+   CPPFLAGS="$POSTGRES_CPPFLAGS $CPPFLAGS"
+   AC_CHECK_HEADERS([libpq-fe.h], [postgres=1], [postgres=0])])
+AS_IF([test "x$postgres" != "x1"],
+  [AC_MSG_ERROR([[
+***
+*** You need libpq(-dev) >= 13.0 to build this program.
+*** ]])])
+AM_CONDITIONAL([HAVE_POSTGRESQL], [test "x$postgres" = "x1"])
+AC_DEFINE_UNQUOTED([HAVE_POSTGRESQL], [$postgres],
+                   [Define to 1 if Postgres is available])
+
+# Check for GNUnet's libgnunetpq.
+libgnunetpq=0
+AC_MSG_CHECKING([for libgnunetpq])
+AC_ARG_WITH(gnunet,
+            [AS_HELP_STRING([--with-gnunet=PFX], [base of GNUnet 
installation])],
+            [AC_MSG_RESULT([given as $with_gnunet])],
+            [AC_MSG_RESULT(not given)
+             with_gnunet=yes])
+AS_CASE([$with_gnunet],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-gnunet is required])],
+        [LDFLAGS="-L$with_gnunet/lib $LDFLAGS"
+         CPPFLAGS="-I$with_gnunet/include ${CPPFLAGS}"])
+CPPFLAGS="${CPPFLAGS} ${POSTGRESQL_CPPFLAGS}"
+AC_CHECK_HEADERS([gnunet/gnunet_pq_lib.h],
+ [AC_CHECK_LIB([gnunetpq], [GNUNET_PQ_result_spec_array_string], 
libgnunetpq=1)])
+AS_IF([test $libgnunetpq != 1],
+  [AC_MSG_ERROR([[
+***
+*** You need libgnunetpq version >= 4.0.0 to build this program.
+*** Make sure you have Postgres installed while
+*** building GNUnet (and that your GNUnet version
+*** is recent!)
+*** ]])])
+
+CFLAGS_SAVE=$CFLAGS
+LDFLAGS_SAVE=$LDFLAGS
+LIBS_SAVE="$LIBS"
+
+# Check for GNUnet's libgnunetsq
+libgnunetsq=0
+AC_MSG_CHECKING([for libgnunetsq])
+AC_CHECK_HEADERS([gnunet/gnunet_sq_lib.h],
+ [AC_CHECK_LIB([gnunetsq], [GNUNET_SQ_result_spec_string], libgnunetsq=1)])
+
+
+# check for libmicrohttpd
+AC_MSG_CHECKING([for microhttpd])
+AC_ARG_WITH([microhttpd],
+            [AS_HELP_STRING([--with-microhttpd=PFX], [base of microhttpd 
installation])],
+            [AC_MSG_RESULT([given as $with_microhttpd])],
+            [AC_MSG_RESULT([not given])
+             with_microhttpd=yes])
+AS_CASE([$with_microhttpd],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-microhttpd is required])],
+        [LDFLAGS="-L$with_microhttpd/lib $LDFLAGS"
+         CPPFLAGS="-I$with_microhttpd/include $CPPFLAGS"])
+MHD_VERSION_AT_LEAST([0.9.71])
+
+# check for libjansson (Jansson JSON library)
+jansson=0
+AC_MSG_CHECKING([for jansson])
+AC_ARG_WITH([jansson],
+            [AS_HELP_STRING([--with-jansson=PFX], [base of jansson 
installation])],
+            [AC_MSG_RESULT([given as $with_jansson])],
+            [AC_MSG_RESULT([not given])
+             with_jansson=yes])
+AS_CASE([$with_jansson],
+        [yes], [],
+        [no], [AC_MSG_ERROR([--with-jansson is required])],
+        [LDFLAGS="-L$with_jansson/lib $LDFLAGS"
+         CPPFLAGS="-I$with_jansson/include $CPPFLAGS"])
+AC_CHECK_LIB(jansson,json_dumpb,
+  [AC_CHECK_HEADER([jansson.h],[jansson=1])])
+AS_IF([test $jansson = 0],
+  [AC_MSG_ERROR([[
+***
+*** You need libjansson >= 2.10 to build this program.
+*** ]])])
+
+
+
+CFLAGS=$CFLAGS_SAVE
+LDFLAGS=$LDFLAGS_SAVE
+LIBS=$LIBS_SAVE
+
+# test for sqlite
+sqlite=false
+AC_MSG_CHECKING(for SQLite)
+AC_ARG_WITH(sqlite,
+  [  --with-sqlite=PFX       base of SQLite installation],
+  [AC_MSG_RESULT("$with_sqlite")
+   AS_CASE([$with_sqlite],
+     [no],[],
+     [yes],[
+      AC_CHECK_HEADERS(sqlite3.h,
+      sqlite=true)],
+     [
+    LDFLAGS="-L$with_sqlite/lib $LDFLAGS"
+    CPPFLAGS="-I$with_sqlite/include $CPPFLAGS"
+    AC_CHECK_HEADERS(sqlite3.h,
+     EXT_LIB_PATH="-L$with_sqlite/lib $EXT_LIB_PATH"
+     SQLITE_LDFLAGS="-L$with_sqlite/lib"
+     SQLITE_CPPFLAGS="-I$with_sqlite/include"
+     sqlite=true)
+    LDFLAGS=$SAVE_LDFLAGS
+    CPPFLAGS=$SAVE_CPPFLAGS
+    ])
+  ],
+  [AC_MSG_RESULT([--with-sqlite not specified])
+    AC_CHECK_HEADERS(sqlite3.h, sqlite=true)])
+AM_CONDITIONAL(HAVE_SQLITE, [test x$sqlite = xtrue] && [test $libgnunetsq = 1])
+AC_SUBST(SQLITE_CPPFLAGS)
+AC_SUBST(SQLITE_LDFLAGS)
+
+# check for libtalertwistertesting
+talertwister=0
+AC_MSG_CHECKING([for talertwister])
+AC_ARG_WITH([twister],
+            [AS_HELP_STRING([--with-twister=PFX], [base of libtalertwister])],
+            [AC_MSG_RESULT([given as $with_twister])],
+            [AC_MSG_RESULT([not given])
+             with_twister=yes])
+AS_CASE([$with_twister],
+        [yes], [],
+        [no], [AC_MSG_WARN([no twister-testing will be compiled])],
+        [LDFLAGS="-L$with_twister/lib $LDFLAGS"
+         CPPFLAGS="-I$with_twister/include $CPPFLAGS"])
+
+AC_CHECK_HEADERS([taler/taler_twister_service.h],
+ [AC_CHECK_LIB([talertwister], [TALER_TWISTER_connect], talertwister=1)])
+AM_CONDITIONAL(HAVE_TWISTER, test x$talertwister = x1)
+
+# should developer logic be compiled (not-for-production code)?
+AC_MSG_CHECKING(whether to compile developer logic)
+AC_ARG_ENABLE([developer-mode],
+   [AS_HELP_STRING([--enable-developer-mode], [enable compiling developer 
code])],
+   [enable_developer=${enableval}],
+   [enable_developer=yes])
+AC_MSG_RESULT($enable_developer)
+AM_CONDITIONAL([HAVE_DEVELOPER], [test "x$enable_developer" = "xyes"])
+enable_dev=1
+AS_IF([test "x$enableval" = "xno"], [enable_dev=0])
+# developer-logic requires a more recent MHD than usual.
+AC_CHECK_DECL([MHD_OPTION_NOTIFY_CONNECTION],,[enable_dev=0],[[#include 
<microhttpd.h>]])
+AC_DEFINE_UNQUOTED([HAVE_DEVELOPER],[$enable_dev],[1 if developer logic is 
enabled, 0 otherwise])
+
+AC_PATH_PROG([JQ], [jq], [no])
+if test "$JQ" = "no"; then
+  AC_MSG_ERROR([jq is required but not found. Please install jq.])
+fi
+
+
+
+# Adam shostack suggests the following for Windows:
+# -D_FORTIFY_SOURCE=2 -fstack-protector-all
+AC_ARG_ENABLE(gcc-hardening,
+   AS_HELP_STRING(--enable-gcc-hardening, enable compiler security checks),
+[AS_IF([test x$enableval = xyes],[
+    CFLAGS="$CFLAGS -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 
-fstack-protector-all"
+    CFLAGS="$CFLAGS -fwrapv -fPIE -Wstack-protector"
+    CFLAGS="$CFLAGS --param ssp-buffer-size=1"
+    LDFLAGS="$LDFLAGS -pie"])])
+
+
+# Linker hardening options
+# Currently these options are ELF specific - you can't use this with MacOSX
+AC_ARG_ENABLE(linker-hardening,
+  AS_HELP_STRING(--enable-linker-hardening, enable linker security fixups),
+  [AS_IF([test x$enableval = xyes],[LDFLAGS="$LDFLAGS -z relro -z now"])])
+
+
+AC_ARG_ENABLE(sanitizer,
+  AS_HELP_STRING(--enable-sanitizer, enable Address Sanitizer and Undefined 
Behavior Sanitizer),
+[AS_IF([test x$enableval = xyes],[
+   LDFLAGS="$CFLAGS -fsanitize=address,undefined -fno-omit-frame-pointer"
+ ])])
+
+# logging
+extra_logging=0
+AC_ARG_ENABLE([logging],
+   AS_HELP_STRING([--enable-logging@<:@=value@:>@],[Enable logging calls. 
Possible values: yes,no,verbose ('yes' is the default)]),
+   [AS_IF([test "x$enableval" = "xyes"], [],
+          [test "x$enableval" = "xno"], 
[AC_DEFINE([GNUNET_CULL_LOGGING],[],[Define to cull all logging calls])],
+          [test "x$enableval" = "xverbose"], [extra_logging=1]
+          [test "x$enableval" = "xveryverbose"], [extra_logging=2])
+   ], [])
+AC_DEFINE_UNQUOTED([GNUNET_EXTRA_LOGGING],[$extra_logging],[1 if extra logging 
is enabled, 2 for very verbose extra logging, 0 otherwise])
+
+# gcov compilation
+AC_MSG_CHECKING(whether to compile with support for code coverage analysis)
+AC_ARG_ENABLE([coverage],
+              AS_HELP_STRING([--enable-coverage],
+                             [compile the library with code coverage support]),
+              [use_gcov=${enableval}],
+              [use_gcov=no])
+AC_MSG_RESULT($use_gcov)
+AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"])
+
+# version info
+AC_PATH_PROG(gitcommand, git)
+AC_MSG_CHECKING(for source being under a VCS)
+git_version=
+AS_IF([test ! "X$gitcommand" = "X"],
+[
+  git_version=$(cd $srcdir ; git rev-list -n 1 --abbrev-commit HEAD 
2>/dev/null)
+])
+AS_IF([test "X$git_version" = "X"],
+  [
+    vcs_name="no"
+    vcs_version="\"release\""
+  ],
+  [
+    vcs_name="yes, git-svn"
+    vcs_version="\"git-$git_version\""
+  ])
+AC_MSG_RESULT($vcs_name)
+
+AC_MSG_CHECKING(VCS version)
+AC_MSG_RESULT($vcs_version)
+AC_DEFINE_UNQUOTED(VCS_VERSION, [$vcs_version], [VCS revision/hash or tarball 
version])
+
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_TYPE_INTMAX_T
+AC_TYPE_UINTMAX_T
+
+# Checks for library functions.
+AC_CHECK_FUNCS([strdup])
+
+
+AC_ARG_ENABLE([[doc]],
+  [AS_HELP_STRING([[--disable-doc]], [do not build any documentation])], ,
+    [enable_doc=yes])
+test "x$enable_doc" = "xno" || enable_doc=yes
+AM_CONDITIONAL([ENABLE_DOC], [test "x$enable_doc" = "xyes"])
+
+
+],[  # This is the big test "$doc_only" on top of the file!
+
+
+# logic if doc_only is set, make sure conditionals are still defined
+AM_CONDITIONAL([HAVE_EXPENSIVE_TESTS], [false])
+AM_CONDITIONAL([MHD_HAVE_EPOLL], [false])
+AM_CONDITIONAL([HAVE_POSTGRESQL], [false])
+AM_CONDITIONAL([HAVE_SQLITE], [false])
+AM_CONDITIONAL([HAVE_LIBCURL], [false])
+AM_CONDITIONAL([HAVE_LIBGNURL], [false])
+AM_CONDITIONAL([HAVE_DEVELOPER], [false])
+AM_CONDITIONAL([USE_COVERAGE], [false])
+AM_CONDITIONAL([ENABLE_DOC], [true])
+AM_CONDITIONAL([HAVE_TWISTER], [false])
+
+# end of 'doc_only'
+])
+
+AC_CONFIG_FILES([Makefile
+                 contrib/Makefile
+                 doc/Makefile
+                 doc/doxygen/Makefile
+                 po/Makefile.in
+                 src/Makefile
+                 src/auditor/Makefile
+                 src/auditordb/Makefile
+                 src/bank-lib/Makefile
+                 src/curl/Makefile
+                 src/exchange/Makefile
+                 src/exchangedb/Makefile
+                 src/exchange-tools/Makefile
+                 src/extensions/Makefile
+                 src/extensions/age_restriction/Makefile
+                 src/lib/Makefile
+                 src/kyclogic/Makefile
+                 src/testing/Makefile
+                 src/benchmark/Makefile
+                 src/include/Makefile
+                 src/json/Makefile
+                 src/mhd/Makefile
+                 src/pq/Makefile
+                 src/sq/Makefile
+                 src/templating/Makefile
+                 src/util/Makefile
+                 ])
+AC_OUTPUT
diff --git a/contrib/.gitignore b/contrib/.gitignore
new file mode 100644
index 0000000..aa92d47
--- /dev/null
+++ b/contrib/.gitignore
@@ -0,0 +1,2 @@
+taler-terms-generator
+locale/**/*.pot
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 0000000..58c8969
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1,61 @@
+# This file is in the public domain.
+
+SUBDIRS = .
+
+tmplpkgdatadir = $(datadir)/taler/exchange/templates/
+dist_tmplpkgdata_DATA = \
+  persona-exchange-unauthorized.en.must \
+  persona-load-failure.en.must \
+  persona-exchange-unpaid.en.must \
+  persona-logic-failure.en.must \
+  persona-invalid-response.en.must \
+  persona-network-timeout.en.must \
+  persona-kyc-failed.en.must \
+  persona-provider-failure.en.must
+
+termsdir=$(datadir)/taler/terms/
+terms_DATA = \
+  exchange-tos-v0.rst \
+  exchange-tos-bfh-v0.rst \
+  exchange-pp-v0.rst
+
+install-exec-local:
+       find locale/ -name "*.po"
+       mkdir -p $(DESTDIR)$(datadir)
+       cp --parents -r $$(find locale/ -name "*.po") $(DESTDIR)$(datadir)
+
+rdatadir=$(datadir)/taler/exchange
+rdata_DATA = \
+  auditor-report.tex.j2
+
+bin_SCRIPTS = \
+  taler-auditor-dbconfig \
+  taler-exchange-dbconfig \
+  taler-terms-generator \
+  taler-bank-manage-testing \
+  taler-nexus-prepare
+
+edit_script = $(SED) -e 's,%termsdir%,$(termsdir),'g $(NULL)
+taler-terms-generator: taler-terms-generator.in
+       rm -f $@ $@.tmp && \
+       $(edit_script) $< >$@.tmp && \
+       chmod a-w+x $@.tmp && \
+       mv $@.tmp $@
+
+CLEANFILES = \
+  taler-terms-generator
+
+EXTRA_DIST = \
+  locale/de/LC_MESSAGES/exchange-tos-v0.po \
+  taler-bank-manage-testing \
+  taler-nexus-prepare \
+  taler-terms-generator.in \
+  gana-generate.sh \
+  gana/gnu-taler-error-codes/registry.rec \
+  gana/gnu-taler-error-codes/Makefile \
+  $(terms_DATA) \
+  $(rdata_DATA) \
+  coverage.sh \
+  gnunet.tag \
+  microhttpd.tag \
+  packages
diff --git a/contrib/coverage.sh b/contrib/coverage.sh
new file mode 100755
index 0000000..cce6222
--- /dev/null
+++ b/contrib/coverage.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+# Run from 'taler-exchange/' top-level directory to generate
+# code coverage data.
+TOP=`pwd`
+mkdir -p doc/coverage/
+lcov -d $TOP -z
+make check
+lcov -d $TOP -c --no-external -o doc/coverage/coverage.info
+lcov -r doc/coverage/coverage.info **/test_* **/perf_*  -o 
doc/coverage/rcoverage.info
+genhtml -o doc/coverage doc/coverage/rcoverage.info
diff --git a/contrib/taler-exchange-dbconfig b/contrib/taler-exchange-dbconfig
new file mode 100755
index 0000000..dc92abb
--- /dev/null
+++ b/contrib/taler-exchange-dbconfig
@@ -0,0 +1,137 @@
+#!/bin/bash
+# This file is part of GNU TALER.
+# Copyright (C) 2023 Taler Systems SA
+#
+# TALER is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free 
Software
+# Foundation; either version 2.1, 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 Lesser General Public License for more 
details.
+#
+# You should have received a copy of the GNU Lesser General Public License 
along with
+# TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+#
+# @author Christian Grothoff
+#
+#
+# Error checking on
+set -eu
+
+RESET_DB=0
+SKIP_DBINIT=0
+DBUSER="taler-exchange-httpd"
+DBGROUP="taler-exchange-db"
+DBNAME="exchange"
+CFGFILE="/etc/taler/secrets/exchange-db.secret.conf"
+
+# Parse command-line options
+while getopts ':g:hn:rsu:' OPTION; do
+    case "$OPTION" in
+        h)
+            echo 'Supported options:'
+            echo "  -c FILENAME  -- write configuration to FILENAME (default: 
$CFGFILE)"
+            echo "  -g GROUP     -- taler-exchange to be run by GROUP 
(default: $DBGROUP)"
+            echo "  -h           -- print this help text"
+            echo "  -n NAME      -- user NAME for database name (default: 
$DBNAME)"
+            echo "  -r           -- reset database (dangerous)"
+            echo "  -s           -- skip database initialization"
+            echo "  -u USER      -- taler-exchange to be run by USER (default: 
$DBUSER)"
+            exit 0
+            ;;
+        n)
+            DBNAME="$OPTARG"
+            ;;
+        r)
+            RESET_DB="1"
+            ;;
+        s)
+            SKIP_DBINIT="1"
+            ;;
+        u)
+            DBUSER="$OPTARG"
+            ;;
+        ?)
+        exit_fail "Unrecognized command line option"
+        ;;
+    esac
+done
+
+if ! id postgres > /dev/null
+then
+    echo "Could not find 'postgres' user. Please install Postgresql first"
+    exit 1
+fi
+
+if [ "$(id -u)" -ne 0 ]
+then
+    echo "This script must be run as root"
+    exit 1
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+    if ! taler-exchange-dbinit -v 2> /dev/null
+    then
+        echo "Required 'taler-exchange-dbinit' not found. Please fix your 
installation."
+    fi
+fi
+
+if ! id "$DBUSER" > /dev/null
+then
+    echo "Could not find '$DBUSER' user. Please set it up first"
+    exit 1
+fi
+
+if sudo -i -u postgres psql "$DBNAME" < /dev/null 2> /dev/null
+then
+    if [ 1 = "$RESET_DB" ]
+    then
+        echo "Deleting existing database '$DBNAME'." 1>&2
+        sudo -i -u postgres dropdb "$DBNAME"
+    else
+        echo "Database '$DBNAME' already exists, refusing to setup again."
+        echo "Use -r to delete the existing database first (dangerous!)."
+        exit 77
+    fi
+fi
+
+echo "Setting up database user '$DBUSER'." 1>&2
+
+if ! sudo -i -u postgres createuser "$DBUSER" 2> /dev/null
+then
+    echo "Database user '$DBUSER' already existed. Continuing anyway." 1>&2
+fi
+
+echo "Creating database '$DBNAME'." 1>&2
+
+if ! sudo -i -u postgres createdb -O "$DBUSER" "$DBNAME"
+then
+    echo "Failed to create database '$DBNAME'"
+    exit 1
+fi
+
+if [ -f "$CFGFILE" ]
+then
+    echo "Adding database configuration to '$CFGFILE'." 1>&2
+    echo -e "[exchangedb-postgres]\nCONFIG=postgres:///$DBNAME\n" >> "$CFGFILE"
+    chown root:"$DBGROUP" "$CFGFILE"
+    chmod 640 "$CFGFILE"
+else
+    echo "Configuration '$CFGFILE' does not yet exist, creating it." 1>&2
+    mkdir -p "$(dirname "$CFGFILE")"
+    echo -e "[exchangedb-postgres]\nCONFIG=postgres:///$DBNAME\n" >> "$CFGFILE"
+    chown root:"$DBGROUP" "$CFGFILE"
+    chmod 640 "$CFGFILE"
+fi
+
+if [ 0 = "$SKIP_DBINIT" ]
+then
+    echo "Initializing database '$DBNAME'." 1>&2
+    sudo -u "$DBUSER" taler-exchange-dbinit
+fi
+
+echo "Database configuration finished." 1>&2
+
+exit 0
diff --git a/contrib/uncrustify-mode.el b/contrib/uncrustify-mode.el
new file mode 100755
index 0000000..cf615b0
--- /dev/null
+++ b/contrib/uncrustify-mode.el
@@ -0,0 +1,161 @@
+;;; uncrustify-mode.el --- Minor mode to automatically uncrustify.
+
+;; Copyright (C) 2012  tabi
+;; Author: Tabito Ohtani <koko1000ban@gmail.com>
+;; Version: 0.01
+;; Keywords: uncrustify
+
+;; This program 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 of the License, or
+;; (at your option) any later version.
+
+;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Installation:
+
+;; drop requirements and this file into a directory in your `load-path',
+;; and put these lines into your .emacs file.
+
+;; (require 'uncrustify-mode)
+;; (add-hook 'c-mode-common-hook
+;;    '(lambda ()
+;;        (uncrustify-mode 1)))
+
+;;; ChangeLog:
+;; * 0.0.1:
+;;   Initial version.
+
+
+;; case
+(eval-when-compile
+  (require 'cl))
+
+;;; Variables:
+
+(defcustom uncrustify-config-path
+  "~/.uncrustify.cfg"
+  "uncrustify config file path"
+  :group 'uncrustify
+  :type 'file)
+(make-variable-buffer-local 'uncrustify-config-path)
+
+(defcustom uncrustify-bin
+  "uncrustify -q"
+  "The command to run uncrustify."
+  :group 'uncrustify)
+
+;;; Functions:
+
+(defun uncrustify-get-lang-from-mode (&optional mode)
+  "uncrustify lang option"
+  (let ((m (or mode major-mode)))
+    (case m
+      ('c-mode "C")
+      ('c++-mode "CPP")
+      ('d-mode "D")
+      ('java-mode "JAVA")
+      ('objc-mode "OC")
+      (t
+       nil))))
+
+(defun uncrustify-point->line (point)
+  "Get the line number that POINT is on."
+  ;; I'm not bothering to use save-excursion because I think I'm
+  ;; calling this function from inside other things that are likely to
+  ;; use that and all I really need to do is restore my current
+  ;; point. So that's what I'm doing manually.
+  (let ((line 1)
+        (original-point (point)))
+    (goto-char (point-min))
+    (while (< (point) point)
+      (incf line)
+      (forward-line))
+    (goto-char original-point)
+    line))
+
+(defun uncrustify-invoke-command (lang start-in end-in)
+  "Run uncrustify on the current region or buffer."
+  (if lang
+      (let ((start (or start-in (point-min)))
+            (end   (or end-in   (point-max)))
+            (original-line (uncrustify-point->line (point)))
+            (cmd (concat uncrustify-bin " -c " uncrustify-config-path " -l " 
lang))
+            (out-buf (get-buffer-create "*uncrustify-out*"))
+            (error-buf (get-buffer-create "*uncrustify-errors*")))
+
+        (with-current-buffer error-buf (erase-buffer))
+        (with-current-buffer out-buf (erase-buffer))
+
+        ;; Inexplicably, save-excursion doesn't work to restore the
+        ;; point. I'm using it to restore the mark and point and manually
+        ;; navigating to the proper new-line.
+        (let ((result
+               (save-excursion
+                 (let ((ret (shell-command-on-region start end cmd t t 
error-buf nil)))
+                   (if (and
+                        (numberp ret)
+                        (zerop ret))
+                       ;; Success! Clean up.
+                       (progn
+                         (message "Success! uncrustify modify buffer.")
+                         (kill-buffer error-buf)
+                         t)
+                     ;; Oops! Show our error and give back the text that
+                     ;; shell-command-on-region stole.
+                     (progn (undo)
+                            (with-current-buffer error-buf
+                              (message "uncrustify error: <%s> <%s>" ret 
(buffer-string)))
+                            nil))))))
+
+          ;; This goto-line is outside the save-excursion because it'd get
+          ;; removed otherwise.  I hate this bug. It makes things so ugly.
+          (goto-line original-line)
+          (not result)))
+    (message "uncrustify not support this mode : %s" major-mode)))
+
+(defun uncrustify ()
+  (interactive)
+  (save-restriction
+    (widen)
+    (uncrustify-invoke-command (uncrustify-get-lang-from-mode) 
(region-beginning) (region-end))))
+
+(defun uncrustify-buffer ()
+  (interactive)
+  (save-restriction
+    (widen)
+    (uncrustify-invoke-command (uncrustify-get-lang-from-mode) (point-min) 
(point-max))))
+
+;;; mode
+
+(defun uncrustify-write-hook ()
+  "Uncrustifys a buffer during `write-file-hooks' for `uncrustify-mode'.
+   if uncrustify returns not nil then the buffer isn't saved."
+  (if uncrustify-mode
+      (save-restriction
+        (widen)
+        (uncrustify-invoke-command (uncrustify-get-lang-from-mode) (point-min) 
(point-max)))))
+
+;;;###autoload
+(define-minor-mode uncrustify-mode
+  "Automatically `uncrustify' when saving."
+  :lighter " Uncrustify"
+  (if (not (uncrustify-get-lang-from-mode))
+      (message "uncrustify not support this mode : %s" major-mode)
+  (if (version<= "24" emacs-version)
+    (if uncrustify-mode
+        (add-hook 'write-file-hooks 'uncrustify-write-hook nil t)
+      (remove-hook 'uncrustify-write-hook t))
+    (make-local-hook 'write-file-hooks)
+    (funcall (if uncrustify-mode #'add-hook #'remove-hook)
+             'write-file-hooks 'uncrustify-write-hook))))
+
+(provide 'uncrustify-mode)
+
+;;; uncrustify-mode.el ends here
diff --git a/contrib/uncrustify.cfg b/contrib/uncrustify.cfg
new file mode 100644
index 0000000..af2d8e6
--- /dev/null
+++ b/contrib/uncrustify.cfg
@@ -0,0 +1,95 @@
+input_tab_size = 2
+output_tab_size = 2
+
+indent_columns = 2
+indent_with_tabs = 0
+indent_case_brace = 2
+indent_label=-16
+
+code_width=80
+#cmd_width=80
+
+# Leave most comments alone for now
+cmt_indent_multi=false
+sp_cmt_cpp_start=add
+
+sp_not=add
+
+sp_func_call_user_paren_paren=remove
+sp_inside_fparen=remove
+sp_after_cast=add
+
+ls_for_split_full=true
+ls_func_split_full=true
+ls_code_width=true
+
+# Arithmetic operations in wrapped expressions should be at the start
+# of the line.
+pos_arith=lead
+
+# Fully parenthesize boolean exprs
+mod_full_paren_if_bool=false
+
+# Braces should be on their own line
+nl_fdef_brace=add
+nl_enum_brace=add
+nl_struct_brace=add
+nl_union_brace=add
+nl_if_brace=add
+nl_brace_else=add
+nl_elseif_brace=add
+nl_while_brace=add
+nl_switch_brace=add
+
+# no newline between "else" and "if"
+nl_else_if=remove
+
+nl_func_paren=remove
+nl_assign_brace=remove
+
+# No extra newlines that cause noisy diffs
+nl_start_of_file=remove
+nl_after_func_proto = 2
+nl_after_func_body = 3
+# If there's no new line, it's not a text file!
+nl_end_of_file=add
+nl_max_blank_in_func = 3
+nl_max = 3
+
+sp_inside_paren = remove
+
+sp_arith = add
+sp_arith_additive = add
+
+# We want spaces before and after "="
+sp_before_assign = add
+sp_after_assign = add
+
+# we want "char *foo;"
+sp_after_ptr_star = remove
+sp_between_ptr_star = remove
+
+# we want "if (foo) { ... }"
+sp_before_sparen = add
+
+sp_inside_fparen = remove
+sp_inside_sparen = remove
+
+# add space before function call and decl: "foo (x)"
+sp_func_call_paren = add
+sp_func_proto_paren = add
+sp_func_proto_paren_empty = add
+sp_func_def_paren = add
+sp_func_def_paren_empty = add
+
+# We'd want it for "if ( (foo) || (bar) )", but not for "if (m())",
+# so as uncrustify doesn't give exactly what we want => ignore
+sp_paren_paren = ignore
+sp_inside_paren = remove
+sp_bool = force
+
+nl_func_type_name = force
+#nl_branch_else = add
+nl_else_brace = add
+nl_elseif_brace = add
+nl_for_brace = add
diff --git a/contrib/uncrustify.el b/contrib/uncrustify.el
new file mode 100644
index 0000000..2ab6961
--- /dev/null
+++ b/contrib/uncrustify.el
@@ -0,0 +1,13 @@
+;; suggested integration of uncrustify for Emacs
+;; This assumes that the 'uncrustify-mode.el' is
+;; installed to '~/.emacs.d/load-path/'. Feel free
+;; to put it elsewhere and adjust the load path below!
+
+;; adding the following to ~/.emacs will then run
+;; uncrustify whenever saving a C buffer.
+
+(add-to-list 'load-path "~/.emacs.d/load-path/")
+(require 'uncrustify-mode)
+(add-hook 'c-mode-common-hook 
+         '(lambda ()
+             (uncrustify-mode 1)))
diff --git a/contrib/uncrustify.sh b/contrib/uncrustify.sh
new file mode 100755
index 0000000..e8e05d3
--- /dev/null
+++ b/contrib/uncrustify.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+
+set -eu
+
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+
+if ! uncrustify --version >/dev/null; then
+  echo "you need to install uncrustify for indentation"
+  exit 1
+fi
+
+find "$DIR/../src" \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) \
+  -exec uncrustify -c "$DIR/uncrustify.cfg" --replace --no-backup {} + \
+  || true
diff --git a/contrib/uncrustify_precommit b/contrib/uncrustify_precommit
new file mode 100755
index 0000000..c10bc26
--- /dev/null
+++ b/contrib/uncrustify_precommit
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+# use as .git/hooks/pre-commit
+exec 1>&2
+
+RET=0
+changed=$(git diff --cached --name-only | grep -v mustach | grep -v 
templating/test./)
+crustified=""
+
+for f in $changed;
+do
+ if echo $f | grep \\.[c,h]\$ > /dev/null
+ then
+    # compare result of uncrustify with changes
+    #
+    # only change any of the invocations here if
+    # they are portable across all cmp and shell
+    # implementations !
+    uncrustify -q -c uncrustify.cfg -f $f | cmp -s $f -
+    if test $? = 1 ;
+    then
+      crustified=" $crustified $f"
+      RET=1
+    fi
+  fi
+done
+
+if [ $RET = 1 ];
+then
+  echo "Run"
+  echo "uncrustify --replace -c uncrustify.cfg ${crustified}"
+  echo "before committing."
+fi
+exit $RET
diff --git a/contrib/update-pp.sh b/contrib/update-pp.sh
new file mode 100755
index 0000000..728216c
--- /dev/null
+++ b/contrib/update-pp.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# This file is in the public domain
+
+# Should be called with the list of languages to generate, i.e.
+# $ ./update-pp.sh en de fr it
+
+# Error checking on
+set -eu
+echo "Generating PP for ETag $VERSION"
+
+rm -f sphinx.log sphinx.err
+# We process inputs using Makefile in tos/ directory
+cd pp
+for l in $@
+do
+    mkdir -p $l
+    echo "Generating PP for language $l"
+    cat conf.py.in | sed -e "s/%VERSION%/$VERSION/g" > conf.py
+    # 'f' is for the supported formats, note that the 'make' target
+    # MUST match the file extension.
+    for f in html txt pdf epub xml
+    do
+        rm -rf _build
+        echo "  Generating format $f"
+        make -e SPHINXOPTS="-D language='$l'" $f >>sphinx.log 2>>sphinx.err < 
/dev/null
+        if test $f = "html"
+        then
+            htmlark -o $l/${VERSION}.$f _build/$f/${VERSION}.$f
+        else
+            mv _build/$f/${VERSION}.$f $l/${VERSION}.$f
+        fi
+        if test $f = "txt"
+        then
+            cp $l/${VERSION}.$f $l/${VERSION}.md
+        fi
+    done
+done
+cd ..
diff --git a/contrib/update-tos.sh b/contrib/update-tos.sh
new file mode 100755
index 0000000..dcf9e39
--- /dev/null
+++ b/contrib/update-tos.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# This file is in the public domain
+
+# Should be called with the list of languages to generate, i.e.
+# $ ./update-tos.sh en de fr it
+
+# Error checking on
+set -eu
+echo "Generating TOS for ETag $VERSION"
+
+rm -f sphinx.log sphinx.err
+# We process inputs using Makefile in tos/ directory
+cd tos
+for l in $@
+do
+    mkdir -p $l
+    echo "Generating TOS for language $l"
+    cat conf.py.in | sed -e "s/%VERSION%/$VERSION/g" > conf.py
+    # 'f' is for the supported formats, note that the 'make' target
+    # MUST match the file extension.
+    for f in html txt pdf epub xml
+    do
+        rm -rf _build
+        echo "  Generating format $f"
+        make -e SPHINXOPTS="-D language='$l'" $f >>sphinx.log 2>>sphinx.err < 
/dev/null
+        if test $f = "html"
+        then
+            htmlark -o $l/${VERSION}.$f _build/$f/${VERSION}.$f
+        else
+            mv _build/$f/${VERSION}.$f $l/${VERSION}.$f
+        fi
+        if test $f = "txt"
+        then
+            cp $l/${VERSION}.$f $l/${VERSION}.md
+        fi
+    done
+done
+cd ..
+echo "Success"
diff --git a/debian/.gitignore b/debian/.gitignore
new file mode 100644
index 0000000..f3ddfd1
--- /dev/null
+++ b/debian/.gitignore
@@ -0,0 +1,23 @@
+.debhelper/
+autoreconf.after
+autoreconf.before
+debhelper-build-stamp
+files
+*.log
+libtalerexchange.substvars
+libtalerexchange/
+taler-exchange-dev.substvars
+taler-exchange-dev/
+taler-exchange.substvars
+taler-exchange/
+taler-exchange-database/
+tmp/
+libtalerexchange-dev.substvars
+libtalerexchange-dev/
+taler-auditor.postrm.debhelper
+taler-auditor.substvars
+taler-auditor/
+taler-exchange.postrm.debhelper
+*.debhelper
+*.substvars
+taler-exchange-offline
diff --git a/debian/README-packaging.md b/debian/README-packaging.md
new file mode 100644
index 0000000..ac42746
--- /dev/null
+++ b/debian/README-packaging.md
@@ -0,0 +1,7 @@
+This file contains some notes about packaging.
+
+## Systemd Units
+
+The main unit file is taler-exchange.service.  It is a unit that does not run
+anything, but instead can be used to stop/start all exchange-related services
+at once.
diff --git a/debian/changelog b/debian/changelog
new file mode 100644
index 0000000..f0e914e
--- /dev/null
+++ b/debian/changelog
@@ -0,0 +1,316 @@
+taler-exchange (0.9.3) unstable; urgency=low
+
+  * First work towards packaging v0.9.3.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Thu, 7 Sep 2023 23:50:12 +0200
+
+taler-exchange (0.9.2-3) unstable; urgency=low
+
+  * Improvements to timeout handling when DB is not available yet.
+
+ -- Florian Dold <dold@taler.net>  Tue, 14 Mar 2023 12:30:15 +0100
+
+taler-exchange (0.9.2-2) unstable; urgency=low
+
+  * Further improvements to Debian package.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sat, 3 Mar 2023 23:50:12 +0200
+
+taler-exchange (0.9.2-1) unstable; urgency=low
+
+  * Minor improvements to Debian package, also adds age-withdraw REST APIs.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sat, 3 Mar 2023 13:50:12 +0200
+
+taler-exchange (0.9.2) unstable; urgency=low
+
+  * Packaging latest release.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Tue, 21 Feb 2023 13:50:12 +0200
+
+taler-exchange (0.9.1) unstable; urgency=low
+
+  * Packaging latest release.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Tue, 17 Jan 2023 11:50:12 +0200
+
+taler-exchange (0.9.0) unstable; urgency=low
+
+  * Packaging latest release.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sat, 5 Nov 2022 11:50:12 +0200
+
+taler-exchange (0.8.99-2) unstable; urgency=low
+
+  * Packaging latest pre-release from Git.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon, 26 Sep 2022 09:50:12 +0200
+
+taler-exchange (0.8.99-1) unstable; urgency=low
+
+  * Updating to latest pre-release from Git.
+
+ -- Christian Grothoff <grothoff@taler.net>  Mon, 20 Jun 2022 13:12:58 +0200
+
+taler-exchange (0.8.5-3) unstable; urgency=low
+
+  * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net>  Tue, 12 Oct 2021 13:12:58 +0200
+
+taler-exchange (0.8.5-2) unstable; urgency=low
+
+  * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net>  Mon, 27 Sep 2021 13:12:58 +0200
+
+taler-exchange (0.8.5-1) unstable; urgency=low
+
+  * Updating to latest Git with minor bugfixes and improvements.
+
+ -- Christian Grothoff <grothoff@taler.net>  Sat, 28 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.5) unstable; urgency=low
+
+  * Official release of GNU Taler exchange 0.8.5.
+
+ -- Christian Grothoff <grothoff@taler.net>  Sat, 28 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.4-1) unstable; urgency=low
+
+  * Updated GANA.
+
+ -- Florian Dold <dold@taler.net>  Thu, 26 Aug 2021 16:37:33 +0200
+
+taler-exchange (0.8.4) unstable; urgency=low
+
+  * Official release of GNU Taler exchange 0.8.4.
+
+ -- Florian Dold <dold@taler.net>  Tue, 24 Aug 2021 13:12:58 +0200
+
+taler-exchange (0.8.3) unstable; urgency=low
+
+  * Official release of GNU Taler exchange 0.8.3.
+
+ -- Christian Grothoff <grothoff@taler.net>  Fri, 13 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.2) unstable; urgency=low
+
+  * Official release of GNU Taler exchange 0.8.2.
+
+ -- Christian Grothoff <grothoff@taler.net>  Sun, 08 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.1-31) unstable; urgency=low
+
+  * Fix dependencies in service definition.
+
+ -- Florian Dold <dold@taler.net>  Sat, 07 Aug 2021 23:23:21 +0200
+
+taler-exchange (0.8.1-30) unstable; urgency=low
+
+  * Fix dependencies in service definition.
+  * Minor fixes in upstream code.
+
+ -- Florian Dold <dold@taler.net>  Sat, 07 Aug 2021 20:20:33 +0200
+
+taler-exchange (0.8.1-29) unstable; urgency=low
+
+  * Minor fix in gateway client.
+
+ -- Florian Dold <dold@taler.net>  Fri, 06 Aug 2021 17:17:46 +0200
+
+taler-exchange (0.8.1-28) unstable; urgency=low
+
+  * Service and configuration fixes.
+
+ -- Florian Dold <dold@taler.net>  Fri, 06 Aug 2021 13:29:47 +0200
+
+taler-exchange (0.8.1-27) unstable; urgency=low
+
+  * Update to upstream code with minor bugfixes.
+  * Fix permissions of secret configuration files in /etc.
+
+ -- Florian Dold <dold@taler.net>  Thu, 05 Aug 2021 21:36:54 +0200
+
+taler-exchange (0.8.1-26) unstable; urgency=low
+
+  * Search config file location correctly.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 21:49:42 +0200
+
+taler-exchange (0.8.1-25) unstable; urgency=low
+
+  * Socket permissions.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 20:54:31 +0200
+
+taler-exchange (0.8.1-24) unstable; urgency=low
+
+  * Service dependencies.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 20:17:53 +0200
+
+taler-exchange (0.8.1-23) unstable; urgency=low
+
+  * Fix secmod helper permissions.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 20:01:12 +0200
+
+taler-exchange (0.8.1-22) unstable; urgency=low
+
+  * Fix permissions.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 19:08:56 +0200
+
+taler-exchange (0.8.1-21) unstable; urgency=low
+
+  * Fix service start assertion.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 18:54:54 +0200
+
+taler-exchange (0.8.1-20) unstable; urgency=low
+
+  * Reduce service dependencies of taler-exchange-httpd.service.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 18:44:34 +0200
+
+taler-exchange (0.8.1-19) unstable; urgency=low
+
+  * Changes to configuration structure.
+
+ -- Florian Dold <dold@taler.net>  Wed, 04 Aug 2021 16:41:21 +0200
+
+taler-exchange (0.8.1-18) unstable; urgency=low
+
+  * Support debhelper-compat 12.
+
+ -- Florian Dold <dold@taler.net>  Sun, 01 Aug 2021 18:42:34 +0200
+
+taler-exchange (0.8.1-17) unstable; urgency=low
+
+  * Fix installation of config files.
+
+ -- Florian Dold <dold@taler.net>  Sat, 31 Jul 2021 18:41:20 +0200
+
+taler-exchange (0.8.1-16) unstable; urgency=low
+
+  * Improved default configuration.
+  * Various packaging tweaks.
+
+ -- Florian Dold <dold@taler.net>  Sat, 31 Jul 2021 13:17:47 +0200
+
+taler-exchange (0.8.1-15) unstable; urgency=low
+
+  * New Taler amount operations (set zero, ...) added.
+  * New configuration file structure
+  * New taler-exchange-offline package
+
+ -- Florian Dold <dold@taler.net>  Mon, 26 Jul 2021 11:21:39 +0200
+
+taler-exchange (0.8.1-14) unstable; urgency=low
+
+  * Expose additional symbols needed in merchant logic.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Tue, 20 Jul 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-13) unstable; urgency=low
+
+  * New Taler amount operations (multiply and divide) added.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Wed, 14 Jul 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-12) unstable; urgency=low
+
+  * Fix typo in taler-auditor shell script: clean before building.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon, 28 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-11) unstable; urgency=low
+
+  * Fix typo in taler-auditor-sync.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sun, 27 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-10) unstable; urgency=low
+
+  * Improve database performance for taler-exchange-wirewatch.
+  * Update database schema, fix missing indices.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Thu, 24 Jun 2021 14:02:10 +0100
+
+taler-exchange (0.8.1-9) unstable; urgency=low
+
+  * Fix #6769: have systemd create exchange UNIX domain socket with nice 
permissions.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sun, 18 Apr 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-8) unstable; urgency=low
+
+  * Fix minor memory leak.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Tue, 16 Feb 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-7) unstable; urgency=medium
+
+  * Avoid picking up libtalerexchange-dev as a dependency of taler-exchange.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon, 15 Feb 2021 13:02:10 +0100
+
+taler-exchange (0.8.1-6) unstable; urgency=medium
+
+  * Fixed a few memory leaks.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon, 15 Feb 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-5) unstable; urgency=medium
+
+  * Fixed a few bugs.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sat, 30 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-4) unstable; urgency=medium
+
+  * Added setup subcommand to taler-exchange-offline.
+  * Fixed conflict between taler-auditor and libtaler exchange packages.
+  * Fixed bad handling of non-C locales.
+  * Updated documentation.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Wed, 27 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-3) unstable; urgency=medium
+
+  * Renamed helper/secmod binaries for consistency.
+  * Protocol improvements, removing unnecessary struct members.
+  * Fixed /management/keys caching logic and key revocation handling.
+  * Implemented taler-auditor-sync.
+  * Misc. other minor improvements.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Thu, 21 Jan 2021 12:02:10 +0100
+
+taler-exchange (0.8.1-3) unstable; urgency=medium
+
+  * Fix taler-exchange.postrm crash (prevented uninstall).
+  * Split out taler-auditor package.
+  * Setup user and systemd service for taler-auditor-httpd.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sun, 03 Jan 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-2) unstable; urgency=medium
+
+  * Modify setup to not touch database (too complex anyway).
+  * Fix build of taler-config.
+  * Correct dependencies.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Sat, 02 Jan 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-1) unstable; urgency=medium
+
+  * Fixing various minor issues with the package, in particular how systemd 
units are started.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Thu, 31 Dec 2020 23:00:00 +0000
+
+taler-exchange (0.8.1-0) unstable; urgency=medium
+
+  * Initial Release.
+
+ -- Christian Grothoff <grothoff@gnu.org>  Thu, 31 Dec 2020 00:00:00 +0000
diff --git a/debian/control b/debian/control
new file mode 100644
index 0000000..3ac37c8
--- /dev/null
+++ b/debian/control
@@ -0,0 +1,155 @@
+Source: taler-exchange
+Section: net
+Priority: optional
+Maintainer: Christian Grothoff <grothoff@gnu.org>
+Build-Depends:
+ autoconf (>=2.59),
+ automake (>=1.11.1),
+ autopoint,
+ bash,
+ debhelper-compat (= 12),
+ gettext,
+ libgnunet-dev (>=0.20),
+ libcurl4-gnutls-dev (>=7.35.0) | libcurl4-openssl-dev (>= 7.35.0),
+ libgcrypt20-dev (>=1.8),
+ libgnutls28-dev (>=3.2.12),
+ libidn2-dev,
+ libjansson-dev,
+ libltdl-dev (>=2.2),
+ libmicrohttpd-dev (>=0.9.71),
+ libpq-dev (>=13),
+ libsodium-dev (>=1.0.11),
+ libunistring-dev (>=0.9.2),
+ python3-jinja2,
+ po-debconf,
+ python3-dev,
+ texinfo (>=5.2),
+ zlib1g-dev
+Standards-Version: 4.5.0
+Vcs-Git: https://salsa.debian.org/debian/taler-exchange.git
+Vcs-browser: https://salsa.debian.org/debian/taler-exchange
+Homepage: https://taler.net/
+
+Package: libtalerexchange
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Recommends:
+ python3-sphinx,
+ python3-sphinx-rtd-theme
+Description: Libraries to talk to a GNU Taler exchange.
+  The package also contains various files fundamental
+  to all GNU Taler installations, such as the
+  taler-config configuration command-line tool,
+  various base configuration files and associated
+  documentation.
+
+Package: taler-exchange-database
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Programs and libraries to manage a GNU Taler exchange database.
+ This package contains only the code to setup the
+ (Postgresql) database interaction (taler-exchange-dbinit
+ and associated resource files).
+
+Package: taler-exchange
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ taler-exchange-database (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ ucf,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Recommends:
+  taler-exchange-offline (= ${binary:Version}),
+  apache2 | nginx | httpd,
+  postgresql (>=13.0)
+Description: GNU's payment system operator.
+  GNU Taler is the privacy-preserving digital payment
+  system from the GNU project. This package contains the
+  core logic that must be run by the payment service
+  provider or bank to offer payments to consumers and
+  merchants.  At least one exchange must be operated
+  per currency.
+  In addition to the core logic, an exchange operator
+  must also have a system running the "offline" logic
+  which is packaged as taler-exchange-offline. It is
+  recommended to keep the "offline" logic on a system
+  that is never connected to the Internet. However, it
+  is also possible to run the "offline" logic directly
+  on the production system, especially for testing.
+  Finally, an exchange operator should also be prepared
+  to run a taler-auditor.
+
+Package: taler-exchange-offline
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: Tools for managing the GNU Taler exchange offline keys.
+ A GNU Taler exchange uses an offline key to sign its online
+ keys, fee structure, bank routing information and other meta
+ data. The offline signing key is the root of the Taler PKI
+ that is then embedded in consumer wallets and merchant backends.
+ This package includes the tool to download material to sign
+ from the exchange, create signatures, and upload the resulting
+ signatures to the exchange.
+
+Package: taler-auditor
+Architecture: any
+Pre-Depends:
+ ${misc:Pre-Depends}
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ taler-exchange-database (= ${binary:Version}),
+ adduser,
+ lsb-base,
+ netbase,
+ python3-jinja2,
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: GNU's payment system auditor.
+  GNU Taler is the privacy-preserving digital payment
+  system from the GNU project. This package contains the
+  auditor logic. It verifies that the taler-exchange run
+  by a payment service provider is correctly performing
+  its bank transactions and thus has the correct balance
+  in its escrow account.  Each exchange operator is
+  expected to make use of one or more auditors as part
+  of its regulatory compliance.
+
+Package: libtalerexchange-dev
+Section: libdevel
+Architecture: any
+Depends:
+ libtalerexchange (= ${binary:Version}),
+ libgnunet-dev (>=0.20),
+ libgcrypt20-dev (>=1.8),
+ libmicrohttpd-dev (>=0.9.71),
+ ${misc:Depends},
+ ${shlibs:Depends}
+Description: libraries to talk to a GNU Taler exchange (development)
+ .
+ This package contains the development files.
diff --git a/debian/copyright b/debian/copyright
new file mode 100644
index 0000000..555d608
--- /dev/null
+++ b/debian/copyright
@@ -0,0 +1,699 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: GNU Taler
+Upstream-Contact: Christian Grothoff <christian@grothoff.org>
+Source: https://taler.net/
+
+Files: *
+Copyright:
+ (C) 2013-2020 Taler Systems SA
+License: AGPL-3+
+Comment: Many contributors are mentioned in AUTHORS
+
+Files: debian/*
+Copyright:
+ (C) 2020 Christian Grothoff <grothoff@gnu.org>
+License: GPL-3+
+
+Files: debian/po/*
+Copyright:
+License: GPL-3+
+
+License: GPL-3+
+ This program 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 of the License, or
+ (at your option) any later version.
+ .
+ This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ .
+ The complete text of the GNU General Public License
+ can be found in /usr/share/common-licenses/GPL-3 file.
+
+License: AGPL-3+
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+ .
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+ .
+                            Preamble
+ .
+ The GNU Affero General Public License is a free, copyleft license for
+ software and other kinds of works, specifically designed to ensure
+ cooperation with the community in the case of network server software.
+ .
+ The licenses for most software and other practical works are designed
+ to take away your freedom to share and change the works.  By contrast,
+ our General Public Licenses are intended to guarantee your freedom to
+ share and change all versions of a program--to make sure it remains free
+ software for all its users.
+ .
+  When we speak of free software, we are referring to freedom, not
+ price.  Our General Public Licenses are designed to make sure that you
+ have the freedom to distribute copies of free software (and charge for
+ them if you wish), that you receive source code or can get it if you
+ want it, that you can change the software or use pieces of it in new
+ free programs, and that you know you can do these things.
+ .
+  Developers that use our General Public Licenses protect your rights
+ with two steps: (1) assert copyright on the software, and (2) offer
+ you this License which gives you legal permission to copy, distribute
+ and/or modify the software.
+ .
+ A secondary benefit of defending all users' freedom is that
+ improvements made in alternate versions of the program, if they
+ receive widespread use, become available for other developers to
+ incorporate.  Many developers of free software are heartened and
+ encouraged by the resulting cooperation.  However, in the case of
+ software used on network servers, this result may fail to come about.
+ The GNU General Public License permits making a modified version and
+ letting the public access it on a server without ever releasing its
+ source code to the public.
+ .
+ The GNU Affero General Public License is designed specifically to
+ ensure that, in such cases, the modified source code becomes available
+ to the community.  It requires the operator of a network server to
+ provide the source code of the modified version running there to the
+ users of that server.  Therefore, public use of a modified version, on
+ a publicly accessible server, gives the public access to the source
+ code of the modified version.
+ .
+ An older license, called the Affero General Public License and
+ published by Affero, was designed to accomplish similar goals.  This is
+ a different license, not a version of the Affero GPL, but Affero has
+ released a new version of the Affero GPL which permits relicensing under
+ this license.
+ .
+ The precise terms and conditions for copying, distribution and
+ modification follow.
+ .
+                       TERMS AND CONDITIONS
+ .
+  0. Definitions.
+ .
+  "This License" refers to version 3 of the GNU Affero General Public License.
+ .
+  "Copyright" also means copyright-like laws that apply to other kinds of
+ works, such as semiconductor masks.
+ .
+ "The Program" refers to any copyrightable work licensed under this
+ License.  Each licensee is addressed as "you".  "Licensees" and
+ "recipients" may be individuals or organizations.
+ .
+ To "modify" a work means to copy from or adapt all or part of the work
+ in a fashion requiring copyright permission, other than the making of an
+ exact copy.  The resulting work is called a "modified version" of the
+ earlier work or a work "based on" the earlier work.
+ .
+ A "covered work" means either the unmodified Program or a work based
+ on the Program.
+ .
+ To "propagate" a work means to do anything with it that, without
+ permission, would make you directly or secondarily liable for
+ infringement under applicable copyright law, except executing it on a
+ computer or modifying a private copy.  Propagation includes copying,
+ distribution (with or without modification), making available to the
+ public, and in some countries other activities as well.
+ .
+ To "convey" a work means any kind of propagation that enables other
+ parties to make or receive copies.  Mere interaction with a user through
+ a computer network, with no transfer of a copy, is not conveying.
+ .
+ An interactive user interface displays "Appropriate Legal Notices"
+ to the extent that it includes a convenient and prominently visible
+ feature that (1) displays an appropriate copyright notice, and (2)
+ tells the user that there is no warranty for the work (except to the
+ extent that warranties are provided), that licensees may convey the
+ work under this License, and how to view a copy of this License.  If
+ the interface presents a list of user commands or options, such as a
+ menu, a prominent item in the list meets this criterion.
+ .
+ 1. Source Code.
+ .
+ The "source code" for a work means the preferred form of the work
+ for making modifications to it.  "Object code" means any non-source
+ form of a work.
+ .
+ A "Standard Interface" means an interface that either is an official
+ standard defined by a recognized standards body, or, in the case of
+ interfaces specified for a particular programming language, one that
+ is widely used among developers working in that language.
+ .
+ The "System Libraries" of an executable work include anything, other
+ than the work as a whole, that (a) is included in the normal form of
+ packaging a Major Component, but which is not part of that Major
+ Component, and (b) serves only to enable use of the work with that
+ Major Component, or to implement a Standard Interface for which an
+ implementation is available to the public in source code form.  A
+ "Major Component", in this context, means a major essential component
+ (kernel, window system, and so on) of the specific operating system
+ (if any) on which the executable work runs, or a compiler used to
+ produce the work, or an object code interpreter used to run it.
+ .
+ The "Corresponding Source" for a work in object code form means all
+ the source code needed to generate, install, and (for an executable
+ work) run the object code and to modify the work, including scripts to
+ control those activities.  However, it does not include the work's
+ System Libraries, or general-purpose tools or generally available free
+ programs which are used unmodified in performing those activities but
+ which are not part of the work.  For example, Corresponding Source
+ includes interface definition files associated with source files for
+ the work, and the source code for shared libraries and dynamically
+ linked subprograms that the work is specifically designed to require,
+ such as by intimate data communication or control flow between those
+ subprograms and other parts of the work.
+ .
+ The Corresponding Source need not include anything that users
+ can regenerate automatically from other parts of the Corresponding
+ Source.
+ .
+ The Corresponding Source for a work in source code form is that
+ same work.
+ .
+  2. Basic Permissions.
+ .
+  All rights granted under this License are granted for the term of
+ copyright on the Program, and are irrevocable provided the stated
+ conditions are met.  This License explicitly affirms your unlimited
+ permission to run the unmodified Program.  The output from running a
+ covered work is covered by this License only if the output, given its
+ content, constitutes a covered work.  This License acknowledges your
+ rights of fair use or other equivalent, as provided by copyright law.
+ .
+ You may make, run and propagate covered works that you do not
+ convey, without conditions so long as your license otherwise remains
+ in force.  You may convey covered works to others for the sole purpose
+ of having them make modifications exclusively for you, or provide you
+ with facilities for running those works, provided that you comply with
+ the terms of this License in conveying all material for which you do
+ not control copyright.  Those thus making or running the covered works
+ for you must do so exclusively on your behalf, under your direction
+ and control, on terms that prohibit them from making any copies of
+ your copyrighted material outside their relationship with you.
+ .
+ Conveying under any other circumstances is permitted solely under
+ the conditions stated below.  Sublicensing is not allowed; section 10
+ makes it unnecessary.
+ .
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+ .
+ No covered work shall be deemed part of an effective technological
+ measure under any applicable law fulfilling obligations under article
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
+ similar laws prohibiting or restricting circumvention of such
+ measures.
+ .
+ When you convey a covered work, you waive any legal power to forbid
+ circumvention of technological measures to the extent such circumvention
+ is effected by exercising rights under this License with respect to
+ the covered work, and you disclaim any intention to limit operation or
+ modification of the work as a means of enforcing, against the work's
+ users, your or third parties' legal rights to forbid circumvention of
+ technological measures.
+ .
+  4. Conveying Verbatim Copies.
+ .
+ You may convey verbatim copies of the Program's source code as you
+ receive it, in any medium, provided that you conspicuously and
+ appropriately publish on each copy an appropriate copyright notice;
+ keep intact all notices stating that this License and any
+ non-permissive terms added in accord with section 7 apply to the code;
+ keep intact all notices of the absence of any warranty; and give all
+ recipients a copy of this License along with the Program.
+ .
+ You may charge any price or no price for each copy that you convey,
+ and you may offer support or warranty protection for a fee.
+ .
+  5. Conveying Modified Source Versions.
+ .
+ You may convey a work based on the Program, or the modifications to
+ produce it from the Program, in the form of source code under the
+ terms of section 4, provided that you also meet all of these conditions:
+ .
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+ .
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+ .
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+ .
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+ .
+ A compilation of a covered work with other separate and independent
+ works, which are not by their nature extensions of the covered work,
+ and which are not combined with it such as to form a larger program,
+ in or on a volume of a storage or distribution medium, is called an
+ "aggregate" if the compilation and its resulting copyright are not
+ used to limit the access or legal rights of the compilation's users
+ beyond what the individual works permit.  Inclusion of a covered work
+ in an aggregate does not cause this License to apply to the other
+ parts of the aggregate.
+ .
+  6. Conveying Non-Source Forms.
+ .
+ You may convey a covered work in object code form under the terms
+ of sections 4 and 5, provided that you also convey the
+ machine-readable Corresponding Source under the terms of this License,
+ in one of these ways:
+ .
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+ .
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+ .
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+ .
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+ .
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+ .
+ A separable portion of the object code, whose source code is excluded
+ from the Corresponding Source as a System Library, need not be
+ included in conveying the object code work.
+ .
+ A "User Product" is either (1) a "consumer product", which means any
+ tangible personal property which is normally used for personal, family,
+ or household purposes, or (2) anything designed or sold for incorporation
+ into a dwelling.  In determining whether a product is a consumer product,
+ doubtful cases shall be resolved in favor of coverage.  For a particular
+  product received by a particular user, "normally used" refers to a
+ typical or common use of that class of product, regardless of the status
+ of the particular user or of the way in which the particular user
+ actually uses, or expects or is expected to use, the product.  A product
+ is a consumer product regardless of whether the product has substantial
+ commercial, industrial or non-consumer uses, unless such uses represent
+ the only significant mode of use of the product.
+ .
+ "Installation Information" for a User Product means any methods,
+ procedures, authorization keys, or other information required to install
+ and execute modified versions of a covered work in that User Product from
+ a modified version of its Corresponding Source.  The information must
+ suffice to ensure that the continued functioning of the modified object
+ code is in no case prevented or interfered with solely because
+ modification has been made.
+ .
+ If you convey an object code work under this section in, or with, or
+ specifically for use in, a User Product, and the conveying occurs as
+ part of a transaction in which the right of possession and use of the
+ User Product is transferred to the recipient in perpetuity or for a
+ fixed term (regardless of how the transaction is characterized), the
+ Corresponding Source conveyed under this section must be accompanied
+ by the Installation Information.  But this requirement does not apply
+ if neither you nor any third party retains the ability to install
+ modified object code on the User Product (for example, the work has
+ been installed in ROM).
+ .
+ The requirement to provide Installation Information does not include a
+ requirement to continue to provide support service, warranty, or updates
+ for a work that has been modified or installed by the recipient, or for
+ the User Product in which it has been modified or installed.  Access to a
+ network may be denied when the modification itself materially and
+ adversely affects the operation of the network or violates the rules and
+ protocols for communication across the network.
+ .
+ Corresponding Source conveyed, and Installation Information provided,
+ in accord with this section must be in a format that is publicly
+ documented (and with an implementation available to the public in
+ source code form), and must require no special password or key for
+ unpacking, reading or copying.
+ .
+  7. Additional Terms.
+ .
+ "Additional permissions" are terms that supplement the terms of this
+ License by making exceptions from one or more of its conditions.
+ Additional permissions that are applicable to the entire Program shall
+ be treated as though they were included in this License, to the extent
+ that they are valid under applicable law.  If additional permissions
+ apply only to part of the Program, that part may be used separately
+ under those permissions, but the entire Program remains governed by
+ this License without regard to the additional permissions.
+ .
+ When you convey a copy of a covered work, you may at your option
+ remove any additional permissions from that copy, or from any part of
+ it.  (Additional permissions may be written to require their own
+ removal in certain cases when you modify the work.)  You may place
+ additional permissions on material, added by you to a covered work,
+ for which you have or can give appropriate copyright permission.
+ .
+ Notwithstanding any other provision of this License, for material you
+ add to a covered work, you may (if authorized by the copyright holders of
+ that material) supplement the terms of this License with terms:
+ .
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+ .
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+ .
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+ .
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+ .
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+ .
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+ .
+ All other non-permissive additional terms are considered "further
+ restrictions" within the meaning of section 10.  If the Program as you
+ received it, or any part of it, contains a notice stating that it is
+ governed by this License along with a term that is a further
+ restriction, you may remove that term.  If a license document contains
+ a further restriction but permits relicensing or conveying under this
+ License, you may add to a covered work material governed by the terms
+ of that license document, provided that the further restriction does
+ not survive such relicensing or conveying.
+ .
+ If you add terms to a covered work in accord with this section, you
+ must place, in the relevant source files, a statement of the
+ additional terms that apply to those files, or a notice indicating
+ where to find the applicable terms.
+ .
+ Additional terms, permissive or non-permissive, may be stated in the
+ form of a separately written license, or stated as exceptions;
+ the above requirements apply either way.
+ .
+  8. Termination.
+ .
+ You may not propagate or modify a covered work except as expressly
+ provided under this License.  Any attempt otherwise to propagate or
+ modify it is void, and will automatically terminate your rights under
+ this License (including any patent licenses granted under the third
+ paragraph of section 11).
+ .
+ However, if you cease all violation of this License, then your
+ license from a particular copyright holder is reinstated (a)
+ provisionally, unless and until the copyright holder explicitly and
+ finally terminates your license, and (b) permanently, if the copyright
+ holder fails to notify you of the violation by some reasonable means
+ prior to 60 days after the cessation.
+ .
+ Moreover, your license from a particular copyright holder is
+ reinstated permanently if the copyright holder notifies you of the
+ violation by some reasonable means, this is the first time you have
+ received notice of violation of this License (for any work) from that
+ copyright holder, and you cure the violation prior to 30 days after
+ your receipt of the notice.
+ .
+ Termination of your rights under this section does not terminate the
+ licenses of parties who have received copies or rights from you under
+ this License.  If your rights have been terminated and not permanently
+ reinstated, you do not qualify to receive new licenses for the same
+ material under section 10.
+ .
+  9. Acceptance Not Required for Having Copies.
+ .
+ You are not required to accept this License in order to receive or
+ run a copy of the Program.  Ancillary propagation of a covered work
+ occurring solely as a consequence of using peer-to-peer transmission
+ to receive a copy likewise does not require acceptance.  However,
+ nothing other than this License grants you permission to propagate or
+ modify any covered work.  These actions infringe copyright if you do
+ not accept this License.  Therefore, by modifying or propagating a
+ covered work, you indicate your acceptance of this License to do so.
+ .
+  10. Automatic Licensing of Downstream Recipients.
+ .
+ Each time you convey a covered work, the recipient automatically
+ receives a license from the original licensors, to run, modify and
+ propagate that work, subject to this License.  You are not responsible
+ for enforcing compliance by third parties with this License.
+ .
+ An "entity transaction" is a transaction transferring control of an
+ organization, or substantially all assets of one, or subdividing an
+ organization, or merging organizations.  If propagation of a covered
+ work results from an entity transaction, each party to that
+ transaction who receives a copy of the work also receives whatever
+ licenses to the work the party's predecessor in interest had or could
+ give under the previous paragraph, plus a right to possession of the
+ Corresponding Source of the work from the predecessor in interest, if
+ the predecessor has it or can get it with reasonable efforts.
+ .
+  You may not impose any further restrictions on the exercise of the
+ rights granted or affirmed under this License.  For example, you may
+ not impose a license fee, royalty, or other charge for exercise of
+ rights granted under this License, and you may not initiate litigation
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
+ any patent claim is infringed by making, using, selling, offering for
+ sale, or importing the Program or any portion of it.
+ .
+  11. Patents.
+ .
+ A "contributor" is a copyright holder who authorizes use under this
+ License of the Program or a work on which the Program is based.  The
+ work thus licensed is called the contributor's "contributor version".
+ .
+ A contributor's "essential patent claims" are all patent claims
+ owned or controlled by the contributor, whether already acquired or
+ hereafter acquired, that would be infringed by some manner, permitted
+ by this License, of making, using, or selling its contributor version,
+ but do not include claims that would be infringed only as a
+ consequence of further modification of the contributor version.  For
+ purposes of this definition, "control" includes the right to grant
+ patent sublicenses in a manner consistent with the requirements of
+ this License.
+ .
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+ patent license under the contributor's essential patent claims, to
+ make, use, sell, offer for sale, import and otherwise run, modify and
+ propagate the contents of its contributor version.
+ .
+ In the following three paragraphs, a "patent license" is any express
+ agreement or commitment, however denominated, not to enforce a patent
+ (such as an express permission to practice a patent or covenant not to
+ sue for patent infringement).  To "grant" such a patent license to a
+ party means to make such an agreement or commitment not to enforce a
+ patent against the party.
+ .
+ If you convey a covered work, knowingly relying on a patent license,
+ and the Corresponding Source of the work is not available for anyone
+ to copy, free of charge and under the terms of this License, through a
+ publicly available network server or other readily accessible means,
+ then you must either (1) cause the Corresponding Source to be so
+ available, or (2) arrange to deprive yourself of the benefit of the
+ patent license for this particular work, or (3) arrange, in a manner
+ consistent with the requirements of this License, to extend the patent
+ license to downstream recipients.  "Knowingly relying" means you have
+ actual knowledge that, but for the patent license, your conveying the
+ covered work in a country, or your recipient's use of the covered work
+ in a country, would infringe one or more identifiable patents in that
+ country that you have reason to believe are valid.
+ .
+ If, pursuant to or in connection with a single transaction or
+ arrangement, you convey, or propagate by procuring conveyance of, a
+ covered work, and grant a patent license to some of the parties
+ receiving the covered work authorizing them to use, propagate, modify
+ or convey a specific copy of the covered work, then the patent license
+ you grant is automatically extended to all recipients of the covered
+ work and works based on it.
+ .
+ A patent license is "discriminatory" if it does not include within
+ the scope of its coverage, prohibits the exercise of, or is
+ conditioned on the non-exercise of one or more of the rights that are
+ specifically granted under this License.  You may not convey a covered
+ work if you are a party to an arrangement with a third party that is
+ in the business of distributing software, under which you make payment
+ to the third party based on the extent of your activity of conveying
+ the work, and under which the third party grants, to any of the
+ parties who would receive the covered work from you, a discriminatory
+ patent license (a) in connection with copies of the covered work
+ conveyed by you (or copies made from those copies), or (b) primarily
+ for and in connection with specific products or compilations that
+ contain the covered work, unless you entered into that arrangement,
+ or that patent license was granted, prior to 28 March 2007.
+ .
+ Nothing in this License shall be construed as excluding or limiting
+ any implied license or other defenses to infringement that may
+ otherwise be available to you under applicable patent law.
+ .
+  12. No Surrender of Others' Freedom.
+ .
+ If conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License.  If you cannot convey a
+ covered work so as to satisfy simultaneously your obligations under this
+ License and any other pertinent obligations, then as a consequence you may
+ not convey it at all.  For example, if you agree to terms that obligate you
+ to collect a royalty for further conveying from those to whom you convey
+ the Program, the only way you could satisfy both those terms and this
+ License would be to refrain entirely from conveying the Program.
+ .
+  13. Remote Network Interaction; Use with the GNU General Public License.
+ .
+ Notwithstanding any other provision of this License, if you modify the
+ Program, your modified version must prominently offer all users
+ interacting with it remotely through a computer network (if your version
+ supports such interaction) an opportunity to receive the Corresponding
+ Source of your version by providing access to the Corresponding Source
+ from a network server at no charge, through some standard or customary
+ means of facilitating copying of software.  This Corresponding Source
+ shall include the Corresponding Source for any work covered by version 3
+ of the GNU General Public License that is incorporated pursuant to the
+ following paragraph.
+ .
+ Notwithstanding any other provision of this License, you have
+ permission to link or combine any covered work with a work licensed
+ under version 3 of the GNU General Public License into a single
+ combined work, and to convey the resulting work.  The terms of this
+ License will continue to apply to the part which is the covered work,
+ but the work with which it is combined will remain governed by version
+ 3 of the GNU General Public License.
+ .
+ 14. Revised Versions of this License.
+ .
+ The Free Software Foundation may publish revised and/or new versions of
+ the GNU Affero General Public License from time to time.  Such new versions
+ will be similar in spirit to the present version, but may differ in detail to
+ address new problems or concerns.
+ .
+ Each version is given a distinguishing version number.  If the
+ Program specifies that a certain numbered version of the GNU Affero General
+ Public License "or any later version" applies to it, you have the
+ option of following the terms and conditions either of that numbered
+ version or of any later version published by the Free Software
+ Foundation.  If the Program does not specify a version number of the
+ GNU Affero General Public License, you may choose any version ever published
+ by the Free Software Foundation.
+ .
+ If the Program specifies that a proxy can decide which future
+ versions of the GNU Affero General Public License can be used, that proxy's
+ public statement of acceptance of a version permanently authorizes you
+ to choose that version for the Program.
+ .
+ Later license versions may give you additional or different
+ permissions.  However, no additional obligations are imposed on any
+ author or copyright holder as a result of your choosing to follow a
+ later version.
+ .
+  15. Disclaimer of Warranty.
+ .
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+ APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+ IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+ .
+  16. Limitation of Liability.
+ .
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGES.
+ .
+  17. Interpretation of Sections 15 and 16.
+ .
+ If the disclaimer of warranty and limitation of liability provided
+ above cannot be given local legal effect according to their terms,
+ reviewing courts shall apply local law that most closely approximates
+ an absolute waiver of all civil liability in connection with the
+ Program, unless a warranty or assumption of liability accompanies a
+ copy of the Program in return for a fee.
+ .
+                     END OF TERMS AND CONDITIONS
+ .
+            How to Apply These Terms to Your New Programs
+ .
+ If you develop a new program, and you want it to be of the greatest
+ possible use to the public, the best way to achieve this is to make it
+ free software which everyone can redistribute and change under these terms.
+ .
+ To do so, attach the following notices to the program.  It is safest
+ to attach them to the start of each source file to most effectively
+ state the exclusion of warranty; and each file should have at least
+ the "copyright" line and a pointer to where the full notice is found.
+ .
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+ .
+    This program 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 of the License, or
+    (at your option) any later version.
+ .
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ .
+ Also add information on how to contact you by electronic and paper mail.
+ .
+ If your software can interact with users remotely through a computer
+ network, you should also make sure that it provides a way for users to
+ get its source.  For example, if your program is a web application, its
+ interface could display a "Source" link that leads users to an archive
+ of the code.  There are many ways you could offer source, and different
+ solutions will be better for different programs; see section 13 for the
+ specific requirements.
+ .
+ You should also get your employer (if you work as a programmer) or school,
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
+ For more information on this, and how to apply and follow the GNU AGPL, see
+ <http://www.gnu.org/licenses/>.
diff --git a/debian/etc-libtalerexchange/taler/overrides.conf 
b/debian/etc-libtalerexchange/taler/overrides.conf
new file mode 100644
index 0000000..60296ea
--- /dev/null
+++ b/debian/etc-libtalerexchange/taler/overrides.conf
@@ -0,0 +1 @@
+# This configuration will be changed by tooling.  Do not touch it manually.
diff --git a/debian/etc-libtalerexchange/taler/taler.conf 
b/debian/etc-libtalerexchange/taler/taler.conf
new file mode 100644
index 0000000..1c86ccc
--- /dev/null
+++ b/debian/etc-libtalerexchange/taler/taler.conf
@@ -0,0 +1,49 @@
+# Main entry point for the GNU Taler configuration.
+#
+# Structure:
+# - taler.conf is the main configuration entry point
+#   used by all Taler components (the file you are currently
+#   looking at.
+# - overrides.conf contains configuration overrides that are
+#   set by some tools that help with the configuration,
+#   and should not be edited by humans.  Comments in this file
+#   are not preserved.
+# - conf.d/ contains configuration files for
+#   Taler components, which can be read by all
+#   users of the system and are included by the main
+#   configuration.
+# - secrets/ contains configuration snippets
+#   with secrets for particular services.
+#   These files should have restrictive permissions
+#   so that only users of the relevant services
+#   can read it.  All files in it should end with
+#   ".secret.conf".
+
+[taler]
+
+# Currency of the Taler deployment.  This setting applies to all Taler
+# components that only support a single currency.
+#currency = KUDOS
+
+# Smallest currency unit handled by the underlying bank system.  Taler payments
+# can make payments smaller than this units, but interactions with external
+# systems is always rounded to this unit.
+#currency_round_unit = KUDOS:0.01
+
+# Monthly amount that mandatorily triggers an AML check
+#AML_THRESHOLD = KUDOS:10000000
+
+[paths]
+
+TALER_HOME = /var/lib/taler
+TALER_RUNTIME_DIR = /run/taler
+TALER_CACHE_HOME = /var/cache/taler
+TALER_CONFIG_HOME = /etc/taler
+TALER_DATA_HOME = /var/lib/taler
+
+
+# Inline configurations from all Taler components.
+@inline-matching@ conf.d/*.conf
+
+# Overrides from tools that help with configuration.
+@inline@ overrides.conf
diff --git 
a/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf 
b/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
new file mode 100644
index 0000000..f68c595
--- /dev/null
+++ b/debian/etc-taler-auditor/apache2/sites-available/taler-auditor.conf
@@ -0,0 +1,4 @@
+<Location "/taler-auditor/">
+ProxyPass "unix:/var/lib/taler-auditor/auditor.sock|http://example.com/";
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/debian/etc-taler-auditor/nginx/sites-available/taler-auditor 
b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
new file mode 100644
index 0000000..f74035d
--- /dev/null
+++ b/debian/etc-taler-auditor/nginx/sites-available/taler-auditor
@@ -0,0 +1,18 @@
+server {
+
+  listen 80;
+  listen [::]:80;
+
+  server_name localhost;
+
+  access_log /var/log/nginx/auditor.log;
+  error_log /var/log/nginx/auditor.err;
+
+  location /taler-auditor/ {
+    proxy_pass http://unix:/var/lib/taler-auditor/auditor.sock;
+    proxy_redirect off;
+    proxy_set_header Host $host;
+    proxy_set_header X-Forwarded-Host "localhost";
+    #proxy_set_header X-Forwarded-Proto "https";
+  }
+}
\ No newline at end of file
diff --git a/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf 
b/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf
new file mode 100644
index 0000000..3d3aef3
--- /dev/null
+++ b/debian/etc-taler-auditor/taler/conf.d/auditor-system.conf
@@ -0,0 +1,12 @@
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ auditordb-postgres ../secrets/auditor-db.secret.conf
+
+[auditor]
+# Debian package is configured to use a reverse proxy with a UNIX
+# domain socket. See nginx/apache configuration files.
+SERVE = UNIX
+UNIXPATH = /var/lib/taler-auditor/auditor.sock
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
diff --git a/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf 
b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
new file mode 100644
index 0000000..b81bb81
--- /dev/null
+++ b/debian/etc-taler-auditor/taler/secrets/auditor-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler auditor.
+
+[auditordb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+CONFIG=postgres:///DATABASE
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git 
a/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf 
b/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
new file mode 100644
index 0000000..3ec14fe
--- /dev/null
+++ b/debian/etc-taler-exchange/apache2/sites-available/taler-exchange.conf
@@ -0,0 +1,4 @@
+<Location "/taler-exchange/">
+ProxyPass 
"unix:/run/taler/exchange-httpd/exchange-http.sock|http://example.com/";
+RequestHeader add "X-Forwarded-Proto" "https"
+</Location>
diff --git a/debian/etc-taler-exchange/nginx/sites-available/taler-exchange 
b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
new file mode 100644
index 0000000..9b61a32
--- /dev/null
+++ b/debian/etc-taler-exchange/nginx/sites-available/taler-exchange
@@ -0,0 +1,17 @@
+server {
+  listen 80;
+  listen [::]:80;
+
+  server_name localhost;
+
+  access_log /var/log/nginx/exchange.log;
+  error_log /var/log/nginx/exchange.err;
+
+  location /taler-exchange/ {
+     proxy_pass http://unix:/run/taler/exchange-httpd/exchange-http.sock:/;
+     proxy_redirect off;
+     proxy_set_header Host $host;
+     proxy_set_header X-Forwarded-Host "localhost";
+     #proxy_set_header X-Forwarded-Proto "https";
+  }
+}
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf 
b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
new file mode 100644
index 0000000..d5938f2
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-business.conf
@@ -0,0 +1,50 @@
+# Configuration for business-level aspects of the exchange.
+
+[exchange]
+
+# Here you MUST add the master public key of the offline system
+# which you can get using `taler-exchange-offline setup`.
+# This is just an example, your key will be different!
+# MASTER_PUBLIC_KEY = YE6Q6TR1EDB7FD0S68TGDZGF1P0GHJD2S0XVV8R2S62MYJ6HJ4ZG
+# MASTER_PUBLIC_KEY =
+
+# Publicly visible base URL of the exchange.
+# BASE_URL = https://example.com/
+# BASE_URL =
+
+# Here you MUST configure the amount above which transactions are
+# always subject to manual AML review.
+# AML_THRESHOLD = 
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# For your terms of service and privacy policy, you should specify
+# an Etag that must be updated whenever there are significant
+# changes to either document.  The format is up to you, what matters
+# is that the value is updated and never re-used. See the HTTP
+# specification on Etags.
+# TERMS_ETAG =
+# PRIVACY_ETAG =
+
+SERVE = unix
+UNIXPATH_MODE = 666
+
+# Bank accounts used by the exchange should be specified here:
+[exchange-account-1]
+
+ENABLE_CREDIT = NO
+ENABLE_DEBIT = NO
+
+# Account identifier in the form of an RFC-8905 payto:// URI.
+# For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
+# Make sure to URL-encode spaces in $NAME!
+PAYTO_URI =
+
+# Credentials to access the account are in a separate
+# config file with restricted permissions.
+@inline-secret@ exchange-accountcredentials-1 
../secrets/exchange-accountcredentials-1.secret.conf
+
+
+
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf 
b/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf
new file mode 100644
index 0000000..8294525
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-coins.conf
@@ -0,0 +1,33 @@
+#
+# This configuration file specifies the various denominations offered by your
+# exchange.
+#
+# Each denomination must be specified in a sections starting with
+# "coin_".
+#
+# What follows is an example.
+# 
+
+# [coin_FOO]
+## Actual value of the coin
+#VALUE = KUDOS:1
+
+## How long will one key be used for withdrawals?
+#DURATION_WITHDRAW = 7 days
+
+## How long do users have to spend their coins?
+#DURATION_SPEND = 2 years
+
+## How long does the exchange keep the proofs around for legal disputes?
+#DURATION_LEGAL = 6 years
+
+## Fees charged. Note that for the lowest denomination, the
+## fee must precisely be the lowest denomination, or zero.
+#FEE_WITHDRAW = KUDOS:0
+#FEE_DEPOSIT = KUDOS:0
+#FEE_REFRESH = KUDOS:0
+#FEE_REFUND = KUDOS:0
+
+## How long should the RSA keys be. Do not change unless you really know
+## what you are doing (consult your local cryptographer first!).
+#RSA_KEYSIZE = 2048
diff --git a/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf 
b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
new file mode 100644
index 0000000..4ad7e06
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/conf.d/exchange-system.conf
@@ -0,0 +1,13 @@
+# Configuration settings for system parameters of the exchange.
+
+# Read secret sections into configuration, but only
+# if we have permission to do so.
+@inline-secret@ exchangedb-postgres ../secrets/exchange-db.secret.conf
+
+[exchange]
+
+# Only supported database is Postgres right now.
+DATABASE = postgres
+
+
+
diff --git 
a/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
 
b/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
new file mode 100644
index 0000000..8c8d143
--- /dev/null
+++ 
b/debian/etc-taler-exchange/taler/secrets/exchange-accountcredentials-1.secret.conf
@@ -0,0 +1,17 @@
+# This file contains the secret credentials
+# to access the Taler Wire Gateway API (usually
+# provided by LibEuFin) for the exchange accounts.
+#
+# Each exchange-account-* section should have a matching
+# exchange-accountcredentials-* section here.
+#
+# Each of those sections must be imported via @inline-secret@,
+# usually in conf.d/exchange-business.conf.
+
+[exchange-accountcredentials-1]
+
+wire_gateway_auth_method = basic
+password =
+username =
+wire_gateway_url =
+
diff --git a/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf 
b/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
new file mode 100644
index 0000000..a7a727b
--- /dev/null
+++ b/debian/etc-taler-exchange/taler/secrets/exchange-db.secret.conf
@@ -0,0 +1,10 @@
+# Database configuration for the Taler exchange.
+
+[exchangedb-postgres]
+
+# Typically, there should only be a single line here, of the form:
+
+# CONFIG=postgres:///DATABASE
+
+# The details of the URI depend on where the database lives and how
+# access control was configured.
diff --git a/debian/libtalerexchange-dev.install 
b/debian/libtalerexchange-dev.install
new file mode 100644
index 0000000..0654f72
--- /dev/null
+++ b/debian/libtalerexchange-dev.install
@@ -0,0 +1,35 @@
+# Benchmarks, only install them for the dev package.
+usr/bin/taler-aggregator-benchmark
+usr/bin/taler-bank-benchmark
+usr/bin/taler-exchange-benchmark
+usr/bin/taler-exchange-kyc-tester
+usr/bin/taler-fakebank-run
+usr/bin/taler-unified-setup.sh
+
+# Only used in test cases.  Maybe these
+# shouldn't even be installed?
+usr/bin/taler-bank-manage-testing
+usr/bin/taler-nexus-prepare
+
+# Man pages
+usr/share/man/man1/taler-exchange-kyc-tester*
+usr/share/man/man1/taler-aggregator-benchmark*
+usr/share/man/man1/taler-bank-benchmark*
+usr/share/man/man1/taler-exchange-benchmark*
+usr/share/man/man1/taler-unified-setup*
+
+
+# Headers
+usr/include/taler/*
+
+# Plain .so symlinks
+usr/lib/*/libtaler*.so
+
+# Testing libraries
+usr/lib/*/libtalertesting.so.*
+usr/lib/*/libtalerfakebank.so.*
+usr/lib/*/libtalertesting.so
+usr/lib/*/libtalerfakebank.so
+
+# Documentation
+usr/share/info/taler-developer-manual*
diff --git a/debian/libtalerexchange.dirs b/debian/libtalerexchange.dirs
new file mode 100644
index 0000000..6b2cf93
--- /dev/null
+++ b/debian/libtalerexchange.dirs
@@ -0,0 +1 @@
+/var/lib/taler
diff --git a/debian/libtalerexchange.install b/debian/libtalerexchange.install
new file mode 100644
index 0000000..56b69e6
--- /dev/null
+++ b/debian/libtalerexchange.install
@@ -0,0 +1,10 @@
+usr/lib/*/libtaler*.so.*
+
+# FIXME:  All this should eventually go into taler-base.
+usr/share/taler/config.d/paths.conf
+usr/share/taler/config.d/taler.conf
+debian/etc-libtalerexchange/* etc/
+usr/bin/taler-config
+usr/bin/taler-terms-generator
+usr/share/man/man5/taler.conf.5
+usr/share/man/man1/taler-config*
diff --git a/debian/libtalerexchange.tmpfiles b/debian/libtalerexchange.tmpfiles
new file mode 100644
index 0000000..dcac0ba
--- /dev/null
+++ b/debian/libtalerexchange.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path        Mode UID  GID  Age Argument
+d /run/taler 0755 root root  - -
diff --git a/debian/patches/0001-Dont_copy_license_file.patch 
b/debian/patches/0001-Dont_copy_license_file.patch
new file mode 100644
index 0000000..5b13ab6
--- /dev/null
+++ b/debian/patches/0001-Dont_copy_license_file.patch
@@ -0,0 +1,22 @@
+From: Bertrand Marc <bmarc@debian.org>
+Date: Sun, 5 Jul 2020 14:58:43 +0200
+Subject: Dont_copy_license_file
+
+---
+ contrib/Makefile.inc | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/contrib/Makefile.inc b/contrib/Makefile.inc
+index a563ef4..c737a07 100644
+--- a/contrib/Makefile.inc
++++ b/contrib/Makefile.inc
+@@ -8,8 +8,7 @@ BUILDCOMMON_SHLIB_FILES = \
+     build-common/sh/lib.sh/existence_python.sh \
+     build-common/sh/lib.sh/msg.sh \
+     build-common/sh/lib.sh/progname.sh \
+-    build-common/sh/lib.sh/version_gnunet.sh \
+-    build-common/LICENSE
++    build-common/sh/lib.sh/version_gnunet.sh
+ 
+ BUILDCOMMON_CONF_FILES = \
+     build-common/conf/.dir-locals.el \
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..f192b8a
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1 @@
+0001-Dont_copy_license_file.patch
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
new file mode 100644
index 0000000..819ba49
--- /dev/null
+++ b/debian/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/rfc822deb] taler-exchange.templates
diff --git a/debian/rules b/debian/rules
new file mode 100755
index 0000000..aef4bf5
--- /dev/null
+++ b/debian/rules
@@ -0,0 +1,66 @@
+#!/usr/bin/make -f
+
+SHELL := sh -e
+
+include /usr/share/dpkg/architecture.mk
+
+%:
+       dh ${@}
+
+override_dh_builddeb:
+       dh_builddeb -- -Zgzip
+
+override_dh_auto_configure-arch:
+       dh_auto_configure -- --disable-rpath --with-microhttpd=yes $(shell 
dpkg-buildflags --export=configure)
+
+override_dh_auto_configure-indep:
+
+override_dh_auto_build-indep:
+
+override_dh_auto_test:
+       # Disabling test suite, incomplete
+
+override_dh_auto_install-arch:
+       dh_auto_install
+
+       # Removing useless files
+       rm -f debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/*.la \
+               debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/taler/*.la \
+               debian/tmp/usr/share/doc/taler/COPYING
+
+override_dh_auto_install-indep:
+
+override_dh_auto_clean:
+       dh_auto_clean
+
+override_dh_installsystemd:
+       # Need to specify units manually, since we have multiple
+       # and dh_installsystemd by default only looks for "<package>.service".
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-httpd 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-aggregator 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-transfer 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-wirewatch 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-cs 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-eddsa 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-secmod-rsa 
--no-start --no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange-closer 
--no-start --no-enable
+       dh_installsystemd -ptaler-auditor --name=taler-auditor-httpd --no-start 
--no-enable
+       dh_installsystemd -ptaler-exchange --name=taler-exchange --no-start 
--no-enable
+       # final invocation to generate daemon reload
+       dh_installsystemd
+
+override_dh_install:
+       dh_install
+# With debhelper-compat=12, we still need to call this manually
+       dh_installtmpfiles
+# Remove files already present in libtalerexchange from main taler-exchange 
package
+       cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f 
../libtalerauditor/{} \;
+       cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f 
../taler-exchange/{} \;
+       cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f 
../taler-auditor/{} \;
+       cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f 
../libtalerexchange/{} \;
+       cd debian/libtalerexchange-dev; find . -type f,l -exec rm -f 
../libtalerauditor/{} \;
+       cd debian/taler-auditor; find . -type f,l -exec rm -f 
../libtalerauditor/{} \;
+       cd debian/taler-auditor; find . -type f,l -exec rm -f 
../libtalerexchange/{} \;
+       cd debian/taler-auditor; find . -type f,l -exec rm -f 
../taler-exchange/{} \;
+       cd debian/taler-exchange-database; find . -type f,l -exec rm -f 
../taler-exchange/{} \;
+       cd debian/libtalerexchange; find . -type f,l -exec rm -f 
../taler-exchange/{} \;
diff --git a/debian/source/format b/debian/source/format
new file mode 100644
index 0000000..163aaf8
--- /dev/null
+++ b/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/debian/source/options b/debian/source/options
new file mode 100644
index 0000000..e928a91
--- /dev/null
+++ b/debian/source/options
@@ -0,0 +1,3 @@
+extend-diff-ignore = "^(config\.sub|config\.guess|Makefile)$"
+
+
diff --git a/debian/taler-auditor.install b/debian/taler-auditor.install
new file mode 100644
index 0000000..82941fb
--- /dev/null
+++ b/debian/taler-auditor.install
@@ -0,0 +1,21 @@
+usr/bin/taler-auditor
+usr/bin/taler-auditor-dbconfig
+usr/bin/taler-auditor-dbinit
+usr/bin/taler-auditor-exchange
+usr/bin/taler-auditor-httpd
+usr/bin/taler-auditor-offline
+usr/bin/taler-auditor-sync
+usr/bin/taler-helper-auditor-*
+usr/lib/*/taler/libtaler_plugin_auditor*.so
+usr/lib/*/libauditor*
+usr/lib/*/libtalerauditordb*
+usr/share/man/man1/taler-auditor*
+usr/share/man/man1/taler-helper-auditor*
+usr/share/info/taler-auditor*
+usr/share/taler/config.d/auditor*
+usr/share/taler/sql/auditor/*
+
+# Configuration
+debian/etc-taler-auditor/* etc/
+
+usr/share/taler/exchange/auditor-report.tex.j2
diff --git a/debian/taler-auditor.postinst b/debian/taler-auditor.postinst
new file mode 100644
index 0000000..847e4aa
--- /dev/null
+++ b/debian/taler-auditor.postinst
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+CONFIG_FILE="/etc/default/taler-auditor"
+TALER_HOME="/var/lib/taler-auditor"
+_USERNAME=taler-auditor-httpd
+_GROUPNAME=taler-auditor-httpd
+
+case "${1}" in
+configure)
+  # Creating taler groups as needed
+  if ! getent group ${_GROUPNAME} >/dev/null; then
+    addgroup --quiet --system ${_GROUPNAME}
+  fi
+  # Creating taler users if needed
+  if ! getent passwd ${_USERNAME} >/dev/null; then
+    adduser --quiet --system --ingroup ${_GROUPNAME} --no-create-home --home 
${TALER_HOME} ${_USERNAME}
+  fi
+
+  if ! dpkg-statoverride --list /etc/taler/secrets/auditor-db.secret.conf 
>/dev/null 2>&1
+  then
+    dpkg-statoverride --add --update \
+      ${_USERNAME} ${_GROUPNAME} 640 \
+      /etc/taler/secrets/auditor-db.secret.conf
+  fi
+
+  ;;
+
+abort-upgrade | abort-remove | abort-deconfigure) ;;
+*)
+  echo "postinst called with unknown argument \`${1}'" >&2
+  exit 1
+  ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-auditor.postrm b/debian/taler-auditor.postrm
new file mode 100644
index 0000000..639e324
--- /dev/null
+++ b/debian/taler-auditor.postrm
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+set -e
+
+if [ -f /usr/share/debconf/confmodule ]; then
+  . /usr/share/debconf/confmodule
+fi
+
+_USERNAME=taler-auditor-httpd
+_GROUPNAME=taler-auditor-httpd
+
+case "${1}" in
+purge)
+    dpkg-statoverride --remove \
+      /etc/taler/secrets/auditor-db.secret.conf || true
+    deluser --system --quiet ${_USERNAME} || true
+    delgroup --only-if-empty --quiet ${_GROUPNAME} || true
+    ;;
+
+remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear) 
;;
+*)
+  echo "postrm called with unknown argument \`${1}'" >&2
+  exit 1
+  ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-auditor.taler-auditor-httpd.service 
b/debian/taler-auditor.taler-auditor-httpd.service
new file mode 100644
index 0000000..9aefab6
--- /dev/null
+++ b/debian/taler-auditor.taler-auditor-httpd.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=GNU Taler payment system auditor REST API
+After=postgres.service network.target
+
+[Service]
+User=taler-auditor-httpd
+Type=simple
+Restart=on-failure
+ExecStart=/usr/bin/taler-auditor-httpd -c /etc/taler/taler.conf
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-auditor.tmpfiles b/debian/taler-auditor.tmpfiles
new file mode 100644
index 0000000..37e214a
--- /dev/null
+++ b/debian/taler-auditor.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path        Mode UID  GID  Age Argument
+d /run/taler/auditor-httpd 0755 taler-auditor-httpd taler-auditor-httpd  - -
diff --git a/debian/taler-exchange-database.install 
b/debian/taler-exchange-database.install
new file mode 100644
index 0000000..da8b0dc
--- /dev/null
+++ b/debian/taler-exchange-database.install
@@ -0,0 +1,8 @@
+usr/bin/taler-exchange-dbconfig
+usr/bin/taler-exchange-dbinit
+usr/lib/*/taler/libtaler_plugin_exchange*.so
+usr/share/man/man1/taler-exchange-dbconfig.1
+usr/share/man/man1/taler-exchange-dbinit.1
+usr/share/taler/sql/exchange/*
+usr/share/taler/config.d/exchangedb.conf
+usr/share/taler/config.d/exchangedb-postgres.conf
diff --git a/debian/taler-exchange-offline.install 
b/debian/taler-exchange-offline.install
new file mode 100644
index 0000000..617715d
--- /dev/null
+++ b/debian/taler-exchange-offline.install
@@ -0,0 +1,2 @@
+usr/bin/taler-exchange-offline
+usr/share/man/man1/taler-exchange-offline*
diff --git a/debian/taler-exchange-offline.postinst 
b/debian/taler-exchange-offline.postinst
new file mode 100644
index 0000000..337bfa5
--- /dev/null
+++ b/debian/taler-exchange-offline.postinst
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+case "${1}" in
+configure)
+
+  if ! getent group taler-exchange-offline >/dev/null; then
+    addgroup --quiet taler-exchange-offline
+  fi
+
+  if ! getent passwd taler-exchange-offline >/dev/null; then
+    adduser --quiet \
+      --disabled-password \
+      --system \
+      --shell /bin/bash \
+      --home /home/taler-exchange-offline \
+      --ingroup taler-exchange-offline \
+      taler-exchange-offline
+  fi
+
+  ;;
+
+abort-upgrade | abort-remove | abort-deconfigure)
+
+  ;;
+
+*)
+  echo "postinst called with unknown argument \`${1}'" >&2
+  exit 1
+  ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange-offline.tmpfiles 
b/debian/taler-exchange-offline.tmpfiles
new file mode 100644
index 0000000..5f9dcb0
--- /dev/null
+++ b/debian/taler-exchange-offline.tmpfiles
@@ -0,0 +1,2 @@
+#Type Path        Mode UID  GID  Age Argument
+d /var/lib/taler/exchange-offline 0700 taler-exchange-offline 
taler-exchange-offline  - -
diff --git a/debian/taler-exchange.README.Debian 
b/debian/taler-exchange.README.Debian
new file mode 100644
index 0000000..cce5d9f
--- /dev/null
+++ b/debian/taler-exchange.README.Debian
@@ -0,0 +1,34 @@
+taler-exchange
+--------------
+
+Note that the configuration is incomplete, and that Debian cannot launch an
+exchange with this minimal template. You must:
+
+* Configure the Postgres database for the exchange, ideally including
+  remote replication of the database to the auditor.
+* Run `taler-exchange-dbinit` (also after package upgrades).
+* Edit ``/etc/taler-secmod.conf`` to must setup the currency and denominations
+  details.
+* Edit `/etc/taler-wire.conf` to provide details about the bank account access.
+* Run `taler-exchange-offline setup` on your offline system and add
+  the resulting master public key into the ``[exchange]`` section of
+  ``/etc/taler-exchange.conf`` under ``MASTER_PUBLIC_KEY``.
+
+
+None of these are done by the Debian package because we cannot provide the
+required complete configuration details.
+
+
+Once you have done this, you can use the following commands to start, stop or
+restart the Taler exchange:
+
+  # systemctl start taler-exchange-httpd.service
+  # systemctl stop taler-exchange-httpd.service
+  # systemctl restart taler-exchange-httpd.service
+
+To permanently the exchange whenever the system boots, use:
+
+  # systemctl enable taler-exchange-httpd
+
+
+ -- Christian Grothoff <grothoff@gnu.org>  Mon 28 Dec 2020 11:37:14 AM CET
diff --git a/debian/taler-exchange.docs b/debian/taler-exchange.docs
new file mode 100644
index 0000000..62deb04
--- /dev/null
+++ b/debian/taler-exchange.docs
@@ -0,0 +1 @@
+AUTHORS
diff --git a/debian/taler-exchange.install b/debian/taler-exchange.install
new file mode 100644
index 0000000..38b3d55
--- /dev/null
+++ b/debian/taler-exchange.install
@@ -0,0 +1,39 @@
+usr/bin/taler-exchange-aggregator
+usr/bin/taler-exchange-closer
+usr/bin/taler-exchange-drain
+usr/bin/taler-exchange-expire
+usr/bin/taler-exchange-httpd
+usr/bin/taler-exchange-router
+usr/bin/taler-exchange-secmod-cs
+usr/bin/taler-exchange-secmod-eddsa
+usr/bin/taler-exchange-secmod-rsa
+usr/bin/taler-exchange-transfer
+usr/bin/taler-exchange-wirewatch
+usr/bin/taler-exchange-wire-gateway-client
+usr/lib/*/taler/libtaler_plugin_kyclogic_*.so
+usr/lib/*/taler/libtaler_extension_*.so
+usr/share/man/man1/taler-exchange-aggregator*
+usr/share/man/man1/taler-exchange-closer*
+usr/share/man/man1/taler-exchange-drain*
+usr/share/man/man1/taler-exchange-expire*
+usr/share/man/man1/taler-exchange-httpd*
+usr/share/man/man1/taler-exchange-router*
+usr/share/man/man1/taler-exchange-secmod-eddsa*
+usr/share/man/man1/taler-exchange-secmod-rsa*
+usr/share/man/man1/taler-exchange-secmod-cs*
+usr/share/man/man1/taler-exchange-transfer*
+usr/share/man/man1/taler-exchange-wirewatch*
+usr/share/man/man1/taler-bank*
+usr/share/man/man1/taler-exchange-wire-gateway-client*
+usr/share/info/taler-bank*
+usr/share/info/taler-exchange*
+usr/share/taler/config.d/*
+usr/share/taler/exchange/templates/*.must
+
+# configuration files in /etc/taler
+debian/etc-taler-exchange/* etc/
+
+# Terms of service / privacy policy templates
+usr/share/taler/terms/*.rst
+# Translations of ToS/PP
+usr/share/locale/*/LC_MESSAGES/*.po
diff --git a/debian/taler-exchange.links b/debian/taler-exchange.links
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/debian/taler-exchange.links
@@ -0,0 +1 @@
+
diff --git a/debian/taler-exchange.lintan-overrides 
b/debian/taler-exchange.lintan-overrides
new file mode 100644
index 0000000..b11557a
--- /dev/null
+++ b/debian/taler-exchange.lintan-overrides
@@ -0,0 +1,3 @@
+# internal libraries are not split out into a dedicated package to avoid
+# micropackaging.
+taler-exchange: package-name-doesnt-match-sonames
diff --git a/debian/taler-exchange.postinst b/debian/taler-exchange.postinst
new file mode 100644
index 0000000..6278dac
--- /dev/null
+++ b/debian/taler-exchange.postinst
@@ -0,0 +1,81 @@
+#!/bin/bash
+
+set -e
+
+. /usr/share/debconf/confmodule
+
+TALER_HOME="/var/lib/taler"
+_GROUPNAME=taler-exchange-secmod
+_DBGROUPNAME=taler-exchange-db
+_EUSERNAME=taler-exchange-httpd
+_CLOSERUSERNAME=taler-exchange-closer
+_CSECUSERNAME=taler-exchange-secmod-cs
+_RSECUSERNAME=taler-exchange-secmod-rsa
+_ESECUSERNAME=taler-exchange-secmod-eddsa
+_AGGRUSERNAME=taler-exchange-aggregator
+_WIREUSERNAME=taler-exchange-wire
+
+case "${1}" in
+configure)
+
+  # Create taler groups as needed
+  if ! getent group ${_GROUPNAME} >/dev/null; then
+    addgroup --quiet --system ${_GROUPNAME}
+  fi
+  if ! getent group ${_DBGROUPNAME} >/dev/null; then
+    addgroup --quiet --system ${_DBGROUPNAME}
+  fi
+
+  # Create taler users if needed
+  if ! getent passwd ${_EUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home 
${TALER_HOME} ${_EUSERNAME}
+    adduser --quiet ${_EUSERNAME} ${_DBGROUPNAME}
+    adduser --quiet ${_EUSERNAME} ${_GROUPNAME}
+  fi
+  if ! getent passwd ${_RSECUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home 
${TALER_HOME} ${_RSECUSERNAME}
+  fi
+  if ! getent passwd ${_CSECUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home 
${TALER_HOME} ${_CSECUSERNAME}
+  fi
+  if ! getent passwd ${_ESECUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --ingroup ${_GROUPNAME} --home 
${TALER_HOME} ${_ESECUSERNAME}
+  fi
+  if ! getent passwd ${_WIREUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --home ${TALER_HOME} 
${_WIREUSERNAME}
+    adduser --quiet ${_WIREUSERNAME} ${_DBGROUPNAME}
+  fi
+  if ! getent passwd ${_CLOSERUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --home ${TALER_HOME} 
${_CLOSERUSERNAME}
+    adduser --quiet ${_CLOSERUSERNAME} ${_DBGROUPNAME}
+  fi
+  if ! getent passwd ${_AGGRUSERNAME} >/dev/null; then
+    adduser --quiet --system --no-create-home --home ${TALER_HOME} 
${_AGGRUSERNAME}
+    adduser --quiet ${_AGGRUSERNAME} ${_DBGROUPNAME}
+  fi
+
+  if ! dpkg-statoverride --list 
/etc/taler/secrets/exchange-accountcredentials-1.secret.conf >/dev/null 2>&1; 
then
+    dpkg-statoverride --add --update \
+      ${_WIREUSERNAME} root 640 \
+      /etc/taler/secrets/exchange-accountcredentials-1.secret.conf
+  fi
+
+  if ! dpkg-statoverride --list /etc/taler/secrets/exchange-db.secret.conf 
>/dev/null 2>&1; then
+    dpkg-statoverride --add --update \
+      root ${_DBGROUPNAME} 640 \
+      /etc/taler/secrets/exchange-db.secret.conf
+  fi
+
+  ;;
+
+abort-upgrade | abort-remove | abort-deconfigure) ;;
+
+*)
+  echo "postinst called with unknown argument \`${1}'" >&2
+  exit 1
+  ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange.postrm b/debian/taler-exchange.postrm
new file mode 100644
index 0000000..9edf548
--- /dev/null
+++ b/debian/taler-exchange.postrm
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+set -e
+
+_GROUPNAME=taler-exchange-secmod
+_DBGROUPNAME=taler-exchange-db
+_EUSERNAME=taler-exchange-httpd
+_CLOSERUSERNAME=taler-exchange-closer
+_CSECUSERNAME=taler-exchange-secmod-cs
+_RSECUSERNAME=taler-exchange-secmod-rsa
+_ESECUSERNAME=taler-exchange-secmod-eddsa
+_AGGRUSERNAME=taler-exchange-aggregator
+_WIREUSERNAME=taler-exchange-wire
+
+
+if [ -f /usr/share/debconf/confmodule ]; then
+  . /usr/share/debconf/confmodule
+fi
+
+case "${1}" in
+purge)
+    rm -rf /var/lib/taler/exchange-offline /var/lib/taler/exchange-secmod-*
+    dpkg-statoverride --remove \
+       /etc/taler/secrets/exchange-accountcredentials-1.secret.conf || true
+    dpkg-statoverride --remove \
+                      /etc/taler/secrets/exchange-db.secret.conf || true
+    deluser --quiet --system ${_CSECUSERNAME} || true
+    deluser --quiet --system ${_RSECUSERNAME} || true
+    deluser --quiet --system ${_ESECUSERNAME} || true
+    deluser --quiet --system ${_AGGRUSERNAME} || true
+    deluser --quiet --system ${_WIREUSERNAME} || true
+    deluser --quiet --system ${_CLOSERUSERNAME} || true
+    deluser --quiet --system ${_EUSERNAME} || true
+    delgroup --only-if-empty --quiet ${_DBGROUPNAME} || true
+    delgroup --only-if-empty --quiet ${_GROUPNAME} || true
+    ;;
+
+remove | upgrade | failed-upgrade | abort-install | abort-upgrade | disappear)
+    ;;
+*)
+    echo "postrm called with unknown argument \`${1}'" >&2
+    exit 1
+    ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/taler-exchange.prerm b/debian/taler-exchange.prerm
new file mode 100644
index 0000000..3ba9986
--- /dev/null
+++ b/debian/taler-exchange.prerm
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+set -e
+
+if [ -f /usr/share/debconf/confmodule ];
+then
+    . /usr/share/debconf/confmodule
+fi
+
+db_stop
+exit 0
diff --git a/debian/taler-exchange.taler-exchange-aggregator.service 
b/debian/taler-exchange.taler-exchange-aggregator.service
new file mode 100644
index 0000000..246cad5
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-aggregator.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-aggregator@.service 
b/debian/taler-exchange.taler-exchange-aggregator@.service
new file mode 100644
index 0000000..bfc44a9
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-aggregator@.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GNU Taler payment system exchange aggregator service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-aggregator
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-aggregator -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-closer.service 
b/debian/taler-exchange.taler-exchange-closer.service
new file mode 100644
index 0000000..97a385c
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-closer.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange closer service
+PartOf=taler-exchange.target
+After=network.target postgres.service
+
+[Service]
+User=taler-exchange-closer
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-closer -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-expire.service 
b/debian/taler-exchange.taler-exchange-expire.service
new file mode 100644
index 0000000..250f210
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-expire.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange expire service
+PartOf=taler-exchange.target
+After=postgres.service
+
+[Service]
+User=taler-exchange-expire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-expire -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-httpd.service 
b/debian/taler-exchange.taler-exchange-httpd.service
new file mode 100644
index 0000000..3671bdc
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd.service
@@ -0,0 +1,33 @@
+[Unit]
+Description=GNU Taler payment system exchange REST API
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd.socket taler-exchange-secmod-cs.service 
taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-cs.service 
taler-exchange-secmod-rsa.service taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+
+# Depending on the configuration, the service process kills itself and then
+# needs to be restarted. Thus no significant delay on restarts.
+Restart=always
+RestartSec=1ms
+
+# Disable the service if more than 5 restarts are encountered within 5s.
+# These are usually the systemd defaults, but can be overwritten, thus we set
+# them here explicitly, as the exchange code assumes StartLimitInterval
+# to be >=5s.
+StartLimitBurst=5
+StartLimitInterval=5s
+
+ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.taler-exchange-httpd.socket 
b/debian/taler-exchange.taler-exchange-httpd.socket
new file mode 100644
index 0000000..adbfb93
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd.socket
@@ -0,0 +1,14 @@
+[Unit]
+Description=Taler Exchange Socket
+PartOf=taler-exchange-httpd.service
+
+[Socket]
+ListenStream=/run/taler/exchange-httpd/exchange-http.sock
+Accept=no
+Service=taler-exchange-httpd.service
+SocketUser=taler-exchange-httpd
+SocketGroup=www-data
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/debian/taler-exchange.taler-exchange-httpd@.service 
b/debian/taler-exchange.taler-exchange-httpd@.service
new file mode 100644
index 0000000..e024689
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd@.service
@@ -0,0 +1,27 @@
+% This is a systemd service template.
+[Unit]
+Description=GNU Taler payment system exchange REST API at %I
+AssertPathExists=/run/taler/exchange-httpd
+Requires=taler-exchange-httpd@%i.socket taler-exchange-secmod-rsa.service 
taler-exchange-secmod-eddsa.service
+After=postgres.service network.target taler-exchange-secmod-rsa.service 
taler-exchange-secmod-eddsa.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-httpd
+Type=simple
+# Depending on the configuration, the service suicides and then
+# needs to be restarted.
+Restart=always
+# Do not dally on restarts.
+RestartSec=1ms
+EnvironmentFile=/etc/environment
+ExecStart=/usr/bin/taler-exchange-httpd -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.taler-exchange-httpd@.socket 
b/debian/taler-exchange.taler-exchange-httpd@.socket
new file mode 100644
index 0000000..e1d6b6b
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-httpd@.socket
@@ -0,0 +1,14 @@
+[Unit]
+Description=Taler Exchange Socket at %I
+PartOf=taler-exchange-httpd@%i.service
+
+[Socket]
+ListenStream=80
+Accept=no
+Service=taler-exchange-httpd@%i.service
+SocketUser=taler-exchange-httpd
+SocketGroup=www-data
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/debian/taler-exchange.taler-exchange-secmod-cs.service 
b/debian/taler-exchange.taler-exchange-secmod-cs.service
new file mode 100644
index 0000000..3b5e074
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-cs.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange CS security module
+AssertPathExists=/run/taler/exchange-secmod-cs
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-cs
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-cs -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-secmod-eddsa.service 
b/debian/taler-exchange.taler-exchange-secmod-eddsa.service
new file mode 100644
index 0000000..e8fba17
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-eddsa.service
@@ -0,0 +1,19 @@
+[Unit]
+Description=GNU Taler payment system exchange EdDSA security module
+AssertPathExists=/run/taler/exchange-secmod-eddsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-eddsa
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-eddsa -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
+
diff --git a/debian/taler-exchange.taler-exchange-secmod-rsa.service 
b/debian/taler-exchange.taler-exchange-secmod-rsa.service
new file mode 100644
index 0000000..10a9585
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-secmod-rsa.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange RSA security module
+AssertPathExists=/run/taler/exchange-secmod-rsa
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-secmod-rsa
+Type=simple
+Restart=always
+RestartSec=100ms
+ExecStart=/usr/bin/taler-exchange-secmod-rsa -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=no
+PrivateDevices=yes
+ProtectSystem=full
+IPAddressDeny=any
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-transfer.service 
b/debian/taler-exchange.taler-exchange-transfer.service
new file mode 100644
index 0000000..e26af20
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-transfer.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=Taler Exchange Transfer Service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-transfer -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange-wirewatch.service 
b/debian/taler-exchange.taler-exchange-wirewatch.service
new file mode 100644
index 0000000..7b74737
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-wirewatch.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target postgres.service
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+RuntimeMaxSec=3600s
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
diff --git a/debian/taler-exchange.taler-exchange-wirewatch@.service 
b/debian/taler-exchange.taler-exchange-wirewatch@.service
new file mode 100644
index 0000000..85bb926
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange-wirewatch@.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler payment system exchange wirewatch service
+After=network.target
+PartOf=taler-exchange.target
+
+[Service]
+User=taler-exchange-wire
+Type=simple
+Restart=always
+RestartSec=1s
+ExecStart=/usr/bin/taler-exchange-wirewatch -c /etc/taler/taler.conf
+StandardOutput=journal
+StandardError=journal
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+Slice=taler-exchange.slice
+RuntimeMaxSec=3600s
diff --git a/debian/taler-exchange.taler-exchange.slice 
b/debian/taler-exchange.taler-exchange.slice
new file mode 100644
index 0000000..b5bb71e
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange.slice
@@ -0,0 +1,7 @@
+[Unit]
+Description=Slice for GNU taler exchange processes
+Before=slices.target
+
+[Slice]
+# Add settings that should affect all GNU Taler exchange
+# components here.
diff --git a/debian/taler-exchange.taler-exchange.target 
b/debian/taler-exchange.taler-exchange.target
new file mode 100644
index 0000000..65ec77c
--- /dev/null
+++ b/debian/taler-exchange.taler-exchange.target
@@ -0,0 +1,13 @@
+[Unit]
+Description=GNU Taler exchange
+After=postgres.service network.target
+
+Wants=taler-exchange-httpd.service
+Wants=taler-exchange-wirewatch.service
+Wants=taler-exchange-aggregator.service
+Wants=taler-exchange-closer.service
+Wants=taler-exchange-expire.service
+Wants=taler-exchange-transfer.service
+
+[Install]
+WantedBy=multi-user.target
diff --git a/debian/taler-exchange.tmpfiles b/debian/taler-exchange.tmpfiles
new file mode 100644
index 0000000..c2a7965
--- /dev/null
+++ b/debian/taler-exchange.tmpfiles
@@ -0,0 +1,8 @@
+#Type Path        Mode UID  GID  Age Argument
+d /run/taler/exchange-secmod-rsa 0755 taler-exchange-secmod-rsa 
taler-exchange-secmod  - -
+d /run/taler/exchange-secmod-cs 0755 taler-exchange-secmod-cs 
taler-exchange-secmod  - -
+d /run/taler/exchange-secmod-eddsa 0755 taler-exchange-secmod-eddsa 
taler-exchange-secmod  - -
+d /run/taler/exchange-httpd 0750 taler-exchange-httpd www-data  - -
+d /var/lib/taler/exchange-secmod-cs 0700 taler-exchange-secmod-cs 
taler-exchange-secmod  - -
+d /var/lib/taler/exchange-secmod-rsa 0700 taler-exchange-secmod-rsa 
taler-exchange-secmod  - -
+d /var/lib/taler/exchange-secmod-eddsa 0700 taler-exchange-secmod-eddsa 
taler-exchange-secmod  - -
diff --git a/debian/upstream/metadata b/debian/upstream/metadata
new file mode 100644
index 0000000..2a281b5
--- /dev/null
+++ b/debian/upstream/metadata
@@ -0,0 +1,4 @@
+Bug-Submit: https://bugs.taler.net/
+Documentation: https://docs.taler.net/
+Repository: git.taler.net/exchange.git
+Repository-Browse: https://git.taler.net/exchange.git
diff --git a/debian/upstream/signing-key.asc b/debian/upstream/signing-key.asc
new file mode 100644
index 0000000..d70f731
--- /dev/null
+++ b/debian/upstream/signing-key.asc
@@ -0,0 +1,637 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFSG/g0BEADfUtc2WA8+OWiNVuNuaU5CIFB/6Netaem0tXAc5VF8c/Dr/Bbt
+eSG4ZAWgCGioO/sqQ08XbYSdot1/zybFqAaD2Tlz99+GFLDYSMSDv6SkaAww0cGb
+objkAO3h1ojeR8gwj2+V2DuM9VLsmB0ITH3zXlLg1wbDUeIpOtk12DWqOTFN0v6x
+hV3JVdFsMmiM21iyo14FIxZmRTJulrwQFi/LcrUR7kDSjuwv3GzmVy6KSArri6fS
+Zec4os6WJM69+N3kV3SwoWxjikfUodaF+kOMXRyfEDX2ebyvveIvMl2BxNu7JUnF
+Y0AHXnxeNbfkpLCuFnH4cVvK14I+hHOa/JTnF77f7sWb+E0588YLL7geWucJfw94
+OzM1z4l/BLSyYiY3PJWRUHwkY7FV3cQGgTfrvbX3afa9Vi2bKHbgsgnOpe55FFJT
+RhZlGJMrgeNsoRKeivFaSa3HLhkV56VG268IM7iao+soVfeWKTOOSQGVeG6VrY7M
+UjhNfBbYfuSOW9CdF3p3XbI8DF68id0OQRUIihS42+kSGCZVY31Mx8+bZj+7+Qhs
+hZrARdrdmDg5IvJykEpn7aKpfyhf1sCfu/gwrpZ90IcaYoeafk6qWcf8JL+5VYHe
+wWjfZ7pFtlurt+hlrdNbqDQ9oHtIsevbgsPlh40BZ0kv2vLK5b+hQ5gd3QARAQAB
+tCtDaHJpc3RpYW4gR3JvdGhvZmYgPGNocmlzdGlhbkBncm90aG9mZi5vcmc+iQI4
+BBMBAgAiBQJUhv4NAhsDBgsJCAcDAgYVCAIJCgsEFgIDAQIeAQIXgAAKCRCTnmvh
+4p/DzBgKEACH0CAulDnMvk5hh9Ndu2QvHDAfKWtoj2NsMFw8YCC+Jb5PqmDL8Ddn
+ddRWrVxEfYf2DnHQI/wiy0HUJaZQstyHUbENtC2kC+HtQAiQlZyb6qL2ByuQfg8Z
+bSJYc7hdwSPRt52qXTMh6TPAzoHEWeEWUmYtQTsRna55A6Zo8HnKzLmspq03kx8w
+MjO/xtfRzToQNNTNh3Yg5F59sMUqiycrJxuF+y2L3jQLphEWg+yXjak3ruX3Rc4H
+pBqdPV36LQ5K+BZp8bzb0Ph2BDZ3t7SFI3SzCAlPl+R+lBtElwe367db+rRo4YPA
+bPchWXgZ7GIq+t7mVr4dffePEkdFVP8obR8mRtnnhx9Jvsi+6HYSsiBZ/csj1kRO
+XdtTrY56nc0maWLqVdvrwDlfrWNZxc7doUWBz0nB7VenzDIuBPCiV+jbafXNtNlu
+drjt0RYGvmnad3TMXxQbJsSmpDjSPAeZfaPtZC77BKt4yY2TvQJL9ZuPh7v59UXB
+wjJAiEP1YacANHExTqk1ShTVy6QNALN0eGifWkogmCtve5rQ1gkqN9TmqnCPGeyZ
+NVzz4j1W/imMRq7+MOVJcpBv0SWDpeFt13efnajdy4xFPUNXVhuIzE2CzcwdAq4f
+KG8QLvFnMN5yUo7kcjxAf4WMFkeuo8ofQNHMcFFvBaqMFWR1I0b347QoQ2hyaXN0
+aWFuIEdyb3Rob2ZmIDxncm90aG9mZkBnbnVuZXQub3JnPokCOAQTAQIAIgUCVIcH
+1gIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AACgkQk55r4eKfw8y6NxAAwzuB
+TvWUsBtnVjFas0n5SRdhtUnTUtAJ8z9Qe2Ab+ljao7cA4WG6OLcWYs/kd3tEUoLo
+XFERwmtRFbExKwVPcx1ffqdJhid9dK4bLd0OeKV3UElQFPMLXio4IUaF/k59HZXV
+X6MEXWDR2G/oNUXrg3Ole8mVd/093UDDoODE42W2RgBeammE5gfE6H7r+cbbKqF5
+I9Ie9ahDBGwW5HpI2cGFt+WsJaBXyBFxQDOnemQRw0PkyaE2TfkRgL0s4qxkyoYU
+vdbw7CfeA2qD4lramkJueXAcWGWP1DA0nxpbL5GQ6hnk/mi/7gZ7yztyop2DwrWE
+W1c2hLWida/grGZJwfXg7hu5Ls1RzCPB5Mqg/wmkynOapOWtvLz73G5LqWc7K6iQ
+7v3twg9enCUrcISzO/fglaf4oQ1EvMhogUu+kTn8DqiOp4lsPqLYu6Bonm90CMZ0
+amMQ1G+lDntJrxnK8MXa4p9Urb3FvR1YIa7zeDMfhHNVLO0jnK8m3S+iC4LvczCU
+xSXpj4ri+gBmS5syd5t7k7tdFpKphukY+H1Obe7wczbRXY9xOt+40jB9hYJM9wLY
+a2nePvbTAZbyV6czSb2GdhMwCFyzWDgiOQo4c+Q4LkiASWHNRM04MAj0L+MNEIOW
+opPQ3tuAx2oIbHV6yNy9ZO/JvPJI3bwc7t35hM20MENocmlzdGlhbiBHcm90aG9m
+ZiA8Y2hyaXN0aWFuLmdyb3Rob2ZmQGlucmlhLmZyPokCNgQwAQgAIBYhBNhCO8sy
+bHkHAzkpx5Oea+Hin8PMBQJZpUuHAh0AAAoJEJOea+Hin8PMyskP/jTGxVE4/9Yx
+BbqbfDlm399nP7JPdMK4rD8ERx87mlxoFWHKaRoyOf6pjHWfEGGOFReuDtVlmb5o
+RYflLjo224ehMur+Xudc65X5b5FExqv8maDXKRor2QI7X/JIB8wGxiXWQop3COiL
+lCqmI8a6RtMaoM3n+cxKcDumDNpckDgnWgtUolGsaJx8NmbeS/p4o1TYVsXwf2Du
+gdeoxEJSYUr7gZBxzI2VW0auG89sQ0/iuE5MvXthoYeECMyFazBBhkJWTtLCU+UE
+ZggLa7r1bBFVT0W87cXZ8dWYkWISJos+h70kwnjk9EFTqGlzaCgNG6GX8QqBnhOm
+zIEo7sp+i8PGsv5G0vnQeE8oVg3wxeY1xrUU5f6JBeLmIIoeG5ivC6rFzBGcV1qL
+uQ/mnhuo6SiP6kWXtKKsF2QuJHsDBnnDyLDJX9IVqumXeoTsqM18PJrv4JDOjeBJ
+wSny33ms6vOcub5CEmjrhDWLp7pgTWzIcH0fPqVxS9qop7HtMZOw0lGkyBHQLMjn
+o1/EDDE/FyUCzYhAlkvV0/3kgDSpXWRzKHb5MJmct0Z4HwfD6io4ZWkJUKqrNun9
+oDms16tKqfc3e+bylHHzM3io2rh0BfVgzot9uub8q9WWoeiRh4hwl5OUzs/+f0yA
+ab/9D4yGDi0fFO2tt8zz76UW+tTTRDqdtCVDaHJpc3RpYW4gR3JvdGhvZmYgPGdy
+b3Rob2ZmQGdudS5vcmc+iQJOBBMBCAA4FiEE2EI7yzJseQcDOSnHk55r4eKfw8wF
+AljtD1oCGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4AACgkQk55r4eKfw8ybEBAA
+ooVs3RYP9sPdDUNdgDXtB6DxlR0kchRlWWr/HR1bRztKiV52atrAextg3PCDcKdB
+G+tdHyCkdLdtFH+wmfPXTfsmr8KiCYdZq3xq/siFSN2jARNShk46fZinosvvieNg
++NOUJXg0QKy5LdgCgWKJzqwK7rS3k/BvEeXsVUGsgJVWF1757cHJPQs/eSs4LjEk
+XT+ga1HuFhM2G9LePbsBVi87Unh2uv+uQuD+Ya8FHlXW2+IMdupTODQdqxtlYO7l
+iPK76h9yxjeCPJ26WZ1UHrG8h1a2wwyTxrpcbMYbMOZW2TjLzLB9H/lGcWN+VomU
+eymr1w9HuUPEMrKn1jNmk7LXWJOS1okvEOyV7NT7EBEJbQpzrdCLP9wUNZTciUsO
+51OO82JlnznWtzQ5DN+XeReTR2rxh/utUZszy8aqyAytkwpxO7dXBr8EOMMjZ62G
+44svOHrDuORfzgozlcRR3EQ9a0uR7nLkF2PM2pSr025ds1OneSKhxXXo2UGRhiaN
+5IqbfpwhHlVywrrCjZYjvvou+O9BWvslcqzBkUsQrU/Umu/XaTx3hRf+UFqmDBdW
+fd2u6nQEP8YR7kL9b/KhA9CH6QDOCo+0RJwj6TRA22R8qvbXXGB3XlQ3X5gvHo7U
+L+HKDhM7RLGfKWEtKGmH+glrWlG/hBdAnj1iadjOp7G0LkNocmlzdGlhbiBHcm90
+aG9mZiA8Y2hyaXN0aWFuLmdyb3Rob2ZmQGJmaC5jaD6JAk4EEwEIADgWIQTYQjvL
+Mmx5BwM5KceTnmvh4p/DzAUCWaVLdAIbAwULCQgHAgYVCAkKCwIEFgIDAQIeAQIX
+gAAKCRCTnmvh4p/DzLdBEACnbI7USar/n5GHIVVu+nA8rw5fs3qHhSVUv7zQiCkC
+kwZS5yGYC4/wo0B6IdEXnEFmijnLhCzcLlwWPqoqzI7ZLbrhxg9duPT+ntBIIuYH
+/+Nr9DIZub5MsKuwSCFcSopch9VFojauBPOnXYfxZr0UI+bY0DpLUu9tgrA6nmJe
+x9Mre5RYS0pxIMv3ZlHXsW+PkJ1dVJisqJ7sr5XfkADTBm+Q94L1F4Jq30ftkan9
+C2zjj8jHurwnKaP+8/bHDQTHxGHpAUc7clw/dS3iuFo79rRerlLdEdLnmziBu1YL
+VwU9CRS5H9GkGbC4XWrobBHaLu12GXZQLgLFiO4JETxkh428yAyXOcPV8YDVORU7
+49xgx+gWSIGAdv4qwjH/xov6JMYGacmzfxWUHmNlW5CBJ7P5Rc6ktKqXffCdiSRw
+NX0F16LeiFxFNeSFFXK3jQfrIDdh2qmcv3bELmgJtMSorBBMecx4XZINXixLT+NO
+Qh8B/pKUXbS9+jvngQORIuDcZxtc81DJP1V7jOU+X9ywpSoX8bJoFDAA36Zn57/Y
+wTxo++6kM4i1WX4XF9NCH5HVlWHDcwQuAOkpEIGV5p0cNbm17VSPrMorr0W4IHeW
+OUoFyOlBGWSmXBRwI0iF+nE1XUj8iKirQ3TaUZrWTZPgt4/+mdCUNqqooj5jytR2
+wLkCDQRUhv4NARAAoi0SvMUnd5XSZVSmbwfge2p9KeGVVcaz99fgrUTgCwfovVd1
+MEXh8FCtxja4xZiuwSGUARuPAXpzhcK1L9vai25GV+y4SALp3wg1/GrsHtEsm+wm
+7AeIq0utXnjfnUzfliIIKwt0aGW/zGp/8rHNKh7JVUo0mPSMQfe+6tE2XOnuGDHj
+1ZyZalmBjVLJYMwsI0tfAzU1fa0MOSnhvyP5TFFj6PWKSajEOsFuIR/zceZFtJbN
+24lbXYwohBDBY2Ajb0y8uYBi/h350UY2mwjKHYM3mxJD3AogWIBz5HD+ueWGUTBp
+KwLYmN7zVxDMdL7FqGonSw9NV1XxJ3IN1DYPPdFKStRIUiSMzyj/pp6410ms+N1M
+tPXDIDdcOcmNHqcnkWqBYHXGi+sYyFpe+825N75dotpEipCnIcTCBjn3RdqFOzT4
++airtL7eOkzmooqtPwvNO+4Uza8+W1PLibXqXWqD0uyi1Wn29asF+uOEfNA4TpTX
+T6Df5B1X88eoHccCpPUhiNqs7dX1ye78m9oicD9IoXj3PZ0le2tHXuFclXjuffpO
+W6Wt+rbqMrFp4LA4H4UXafai9B5F1JMp+xdK+V0YUT0aQSZwdHyvNsGReRnuuZKH
+be0xokpVM+ndra2EpsV0C3csoDOWyu7yjUyFeTfAlYBb8rn8WuLnT8xzSJEAEQEA
+AYkCHwQYAQIACQUCVIb+DQIbDAAKCRCTnmvh4p/DzKGQD/wLhO70IEI06MqaP41i
+m4X7suk4zGOAcBXAcsZONq450CA/WHvoMKFoCPHfoC4e1jsoifG8+emfTQhWKwW3
+a5G/H90a8lY8pH9tqkVUPds5m6fbWf16xkWUQpH8QQyLwhBIF8onclrDWAHPflpn
+Wp+wso1vxN+WRh5vL1k8dpQLUkOBmE1ovl79/z1zzOYDkOWdQ1crU2EbOXalCmOA
+SmiFhWiYk2aosBxbzGX0JKX5NyIUzz56i9vDYqjkDFYcMMx1Z9YXsvTjglMwnIfw
+PmvBBgQlwqg+LOts7XF0ZoBZ3NBLpIES0wheVjXtG/T7kZey7XABVbxK2B4mIRFI
+vXnHbTEGzSyY7hLCshyCMQTDCoHDOKiNZmteqhHU4zXVgyhrxkYG9iIDj9yb6PCj
+aFwgp42rz0lLqTgmpDEIrz1MaCglhTB68wTsHYx3SH+ClNGmgWTa8dS+l/s0hgE+
+WknVGn6ShMkdyYLn3QxTRhZSmRv2hG7AYSemtLxi4lLoJ3kDHLMYAponhzxLYOtc
+8IyNrrRU4Tj4keG2ssHSkC9kDIMqzX53ObGkVWN6Rvu+pmZ9iumrNqI/4PyrPi3m
+OE7ooIkh1L/MEu2cLNWaTG5QmOK0VtYN+3G2qzcjKEpQPIDgRdZ6i7fO6jgb0iy1
+UJUbAoLQgUTaX99KUKeyCuiGUA==
+=17vI
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFj7l1QBEAC7WLUVNL9eQM9EpD4eTTBxs9W8IvCnZs3nT8cNm/a0rMBx4Vfk
++TKtI4tPbJRoM0GPMEUy0cyIztm7kSCcxJTgm8OIjIqloH0kL3YKqryQ0d1NHdgI
+z6zgBKLvbldG8+vQensMQm9D2xCDeBQGWACyyvtXsU35PeTmbW7GmYc9d2bwDWLH
+poO7GdMOQYETP8VOPUxtRoyJ2oSTPkHt/TFIAKEIEuVwPb4e+0XoRNdkdEebcjKv
+FW9hLJG4Cy5ur0GrQs21KlT/Yoz65MgK3jNrb9WJG8XBVAYxUq95FjD88ECIskRY
+KU8PM117MujSCOARh+jYUwG/m4Cz2atP3UOVkBGor21T9GF+KACiO/FTQboout0Y
++mwxyJkWQC+dZyg6oeZDa0mxCj1TO/1o9E/drgrxya3i9P5WVp8Ab6vAV9tk0jtd
+O7gqqqJGwW4hSBbcaYcZrST14EE7Xhc90f4lI8wYB1opC9lNstIbCF/5WPZBr3JQ
+/VQTdqk+b6W2XtpPqrPN9D43/aAlr7phgLlQWoUQYsYTjkx/CvrxK2davLtuvlov
+yzNZzsA/tm7+CBquY1rnaZfy+d61gsPj+9VXYc0edUPCCGPKI5m7XztFCAYRG6av
+yJT0vVsZDaXYwkSrx54D/rLGF/1dBavWApikLQER+CVyzO4dAJ9oGh+XNQARAQAB
+tAxuZzBAZnNmZS5vcmeJAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgMCAQACHgEC
+F4AWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSF
+iHgoD/49sgbWjXHVCa2O/bs5OWb0QaF1WMS+4fJkemkuvb4HqyzreSYPim6yIbx8
+/X2MaNeSvOZCMAvLKFfkqWm2B6Jngvxf+ZtVWrK895QXQoOZd5E91qb/zR0g/T5H
+K0GviRWAvL52+P2yfj7tswOq1Txdes+azwd6+yYUC1Fue50q/psoxXhfKib5NjPp
+4OGnx2YotAAdYrMBSQXDd5xYEt2OtzqwlQ8tsU+zeymzicPMEK2HdqBWRub4Y1O2
+bYmQ983iak/mrXBxOx6kbjXZBoMrYG/27JF/W8nrxLLIBqWvuWmnQcjs+5AQETdq
+be5+wsu2hiLtyi576vsvlR3kz4XYntD2Uhe7xJ7uR970suk5/fYSr5XpF0Cj0NCu
+9iFr/VTyFLKW2Wb9oiUbriE4jvlfIgw7JeT1C/3aRkbjyqDd9zwDHIFPQwBca0BL
+pAKyjGjx4QNDiTTZLvj0JL88Deikc/RVqn8AgjsuviVX/5xiQ8wX54UKiz4WpfY7
+ENgBkogg0WF8raxSHihBRlrcA2otlw+UUz64Uw6R4yMmemUEBl6/VMr/vB7+KykP
+Jxek8hb53fHEpCDcmniiwLK3ZaQz/VQ4HarCVVucc/oFB3XZuR55P1zhfcXf9JpS
+I5wvCmpkInPqav1aSKVLFdZY3eOlew7p/1aQNeF+ZaftGdLLX7QObmcwQGtyb3Nv
+cy5vcmeJAkQEMAEKAC4WIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWyLJ/RAdIGRv
+bWFpbiBleHBpcmVkAAoJEOIvm7/uNIWINNIP/1mJWlL6HfVJUy5L2mzsvdJZ3B/7
+9FZqb0YnP13HzPkPHjPKYgSAHRa8rGqn9UY/1nA5wYqXbnAqZJahO/zik9vd607Q
+kKZuYahqnymQOihNI4eWB99uSIdxrMGwh/HzJVzE8fhZld19az9KcVGf9KOl7Pl8
+be87mP6qkInrEt6QoyvrXwcZU6fOpEDRje4GGGRFry9FyLFE1UcICMpU2thSukym
+ziaamww7HjWtrw55cU7PaY+qWqH+MwLuyqt5jHGEsMaQHhkZCGHJn6sQ7Ci6DyhY
+JpOUgPgDzeTCzLIJrxrAiK6lwGel4aj6JhoXvQaYUZId27l/W9ItrhCRp9kDXza7
+yYoLO0ekqtpN/WpBWuXatwTaLk/zeJFdYdpQ+Hk7dlgTWhTPWhwQaUTbP8jqtjzG
+I7/Gq5EIG6r+6j/WFcKg7D4MrYQel9q9Sgx9oGLEtZgteK0wo+A3U2oIPugXWQeE
+5alpSIurNcsEAFowrGf/qd2JzImoI1zaJQbz0o/h5cxTOuWH3CbbPQ3BBSrM8Sqy
+evMFsfaAOpIqL3n69rlHaxn1cHaRYM8JcazFlp89pbqZ1Q4ZRFp7+8oO+KPThg5g
+sqizrOxKjXnWJX78qczBIEzD+KHGn4avsRgIsT1Ciz+ctjUiJvAZ4bcHpLwY3SFW
+jxpvlruI/XlZm6FjtA9uZzBAbGliZXJ0YWQucHeJAmcEMAEKAFEWIQSojIrdEpgo
+1+rALlLiL5u/7jSFiAUCWUKqDjMdIFRoaXMgZG9tYWluIGlzIHNjaGVkdWxlZCBm
+b3IgdGVybWluYXRpb24gaW4gMjAxOC4ACgkQ4i+bv+40hYiREA//SsIg/4Gfl4B/
+346bW5Gezh/Y0VqinNqFu/XG9HAuQ0AWNxr5hbFWNAZWEb2NUMiy+lMhNtJQYqpY
+Vsxmfcv1lM1xd+kyeOAjEdLI/TnnxrKI+eN8RgWNvtnfDoukOFm+aDP9DiyMdciS
+1GYgy/SrPnp+jxAMvjZ48prZPy8zEAiU0uBvYYlSHt4YqEr2XfJr1Sh2gs/ZYLE0
+2/8HKkEPAWYPk6dqeh1HITA4WOGPq4k+nTK/uHmm8WPVbsz8syOXGudn+vP6X4Lw
+7adoufVTbbr/0KP1N0f5kzk2WVL1y7l24W14ixQWQSH1GwcItj1Oin42JJ2ezHkr
+FVMCCbR8QhJxOlg+VCQkfHsY+gnbQGLW3bcMfQSXpLR8w/octEgOSkDHUTw93aNy
+IBZyZy6IlUVgjsUq+G9naQr2Jj44B7LaRYyanQFbuDT/vZ3nx1k4VvjzIXpMcLGr
+Jiq6keOSqVgVeBdSRcqVKwJEzmwmVVPPWsw1efaOID/CINAHvhe2h13CPFWQCGPS
+pk64NN1vEqxi1uGP4QdBrlLbVweusLNOHo9wksriSbs+Xj47n+Tr1gx1XezRYf13
+VhaaHYolbKYMR8fheCzMNgNw2VuP0RnYgIpVkk3p0roAqoWzlPIUjQnKxVoA9ITY
+MAPefuhM4qhRnW92egjSbHqOMj0PPS60Em5nQHByYWdtYXRpcXVlLnh5eokCVQQw
+AQoAPxYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJZQqpNIR0gVGhpcyBkb21haW4g
+aXMgYmVpbmcgZmFkZWQgb3V0LgAKCRDiL5u/7jSFiNxrD/4n75Ymlm0fLwG4B8U8
+w3xidcxS6yewOyZJKYrVtd7MDFrOHf89FytCvXDj4LbGP/KfCyHTvUBaQEFUYM9g
+1sx7v5m5V5SijHMCg3zJ63Lrxe5/lWM/O6Y5jiRtilOqzDI5CGqEHDohVbzfULZd
+9izyWQcZ3CGye7yrdp6LYDw/cMqgh1d56kuMXYyLcXgrUXeH9wQkaw1TJfWPSCvI
+JfESu/PJQ4TviBXZRfBuvDnjjbdYXvOCR3vQhSSuU8SX7f6wYNEBkWEj4Maj/HWJ
+x+442yzCzYb+Ix4ChJfjCLRtXs9OYcu4jT/9gxkPQISmjpjLGfSG5XcNcHirqxcs
+poT23LkSxB2pvtCw1MwAJHtk2o155Q2ZB+Kfu0fL74A/EpsilOHSym62ELkrude3
+Cy3x3kGFbRkwZd2tXbVlhgbnsl+YznXV+Mmx+pAoPvPtuzy/17yAbqhR5N5u3rC6
+hbZME/YCkoMns4+Dcab8iCxJl/UYYeCGwllbPMlzzvZ2SjRofNJ/5hydGg+dcPN/
+f7Nuh7bwN57vUSEatHdpqehi7avC7v1l6Xgijf1cN7nCM2JvDg9tR5G+fTOnQaU4
+0tpWYQNSpsYdLTs+M7QEzfH+jfS7tEhtdmyUkwWWudzKLPmSIVKYqWNytiJuFXdK
+j5+3tSfFLiCd3/piKn240L+GDbQTbmcwQHByYWdtYXRpcXVlLnh5eokCWAQwAQoA
+QhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJZUuYtJB0gYWRkcmVzc2VzIHdpbGwg
+YmUgbm8gbG9uZ2VyIGFjdGl2ZQAKCRDiL5u/7jSFiCDvEACM2rM9zS7DUPHQ+dCF
+hH9c6ZcfdFQ0lzCT6DEZJs/PKhX0Ofgm/7BZm5zNotWaPyphHNspFrIv90EwPpBu
+XZfPbmewDpYxOuUMTCnHVMLzIXCmKqro1K6ofi9CvAm1kx0k/xKjB3c7XITnABy6
+sUhEKVojI6f4IgwkSYFZgT6E/GOV5jJYRyVUB49cXYQx5vvWj5yVTmTTDf1IUEsk
+ZpW74+zFt3WA4TJUugAarXPiEPPrcgkHDLAa9oPatyqAd4QU+0oaQuJC4GYfkIHk
+6fxZg0Mw+oKUjJEE0+o/WGv3SWSmd66urRcVxHASI6okeffz5ufGaYJNZTaHMmy6
+ztJJWfplhMe3wxLxZ1S7L3l947GbHgWXrC31kDY1D2LY5/7pr7R38B94CXbX2EIf
+ORWg4eUWvVfuPZ5Ew1TmUwcDS2w4EeS2psGfmUDbne5m4nn0iPPykXtw28ZbH7bN
+n9rTwW3yVIdyiDUvGlV63Os4tHVtbrFADKd1meFaMmg1gagdYof4lB50Vh5ChD6o
+7GkSvOshY7f4KNEfZLqCPTXmJKd3XLfb0eNY1APCYm8GI30K8OT8cdJwy3+zA7Oj
+/Duvo95pCyXx0+4xpo2eM2XPDdeVdMOkpGyFnKo3ApRMW5HHkw7sdpaNDwmtV9Wk
+RMBRAKXkvjnirKO6ZxTaIBUDIrQUa3Jvc29zQGF1dGlzdGljaS5vcmeJAlQEEwEK
+AD4CGwMFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrdEpgo1+rALlLiL5u/
+7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSFiHKcEACK34todRbyC37pvOGKYSU0
+WJGtVGSCxE6fOQzUb+G5n8oq/mLg50IiQL+BPd6flRBABrJ9RDi/z4i6tmgoE8u4
+t7oTj6vuF0XLhzbQ8wUS2CgWMuf7S4s9UN9yUG+zbATPpdYXo+m6hDYJxulmv9VA
+Xwc2k9acspsk9TCRgooHucpj/iTvFO4Qlg8AiVvLRsNd1dB0FMBMOs/Pa0LoZvOr
+oJZlFZdtVKZ7IMsgTfmLpRKrVR3LxJ7S1+7TGI96KGSBmB90QSBSWxwm4nsV5R5S
+q8vEPyb92XLJc1+j5GALcwhzX5gZ6bLqKFAO9OcFhB9ETSuujf4ksmLdntAj+DEI
+I2d+s8bFapg5p9/fVfrT4BExTh3yScOxG1UPAJNTQ/bXGFYKxh2cNzaYdbxli+xe
+nGwZivmpspM594I5dE3GfPdmiTQ7Mm0BTFa7A5xi/ftGICm0xS07UJ5eDWP3gVz/
+XmfVPhh8RUAGJgoOShDOO0CMG8+oYI4SRLMYI0pRB+ujCwRJnROOw4u9ATBVnLgQ
+H134ZNx0P+PIPHHQcotmjtYrko5Wg4hOvVGVjBkD8CCiPn4VhdcgM5RiKBnYe10D
+cEtLpjxTHD81X3X4hziuq70UiW9myBjsyPY5KgozeICN+GmXXdZTJYs6WWudAwgD
+9BA7vpBDKk7JYqdwKTJVU7QUbmcwQGluZm90cm9waXF1ZS5vcmeJAlQEEwEKAD4C
+GwMFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrdEpgo1+rALlLiL5u/7jSF
+iAUCWnZKwAUJBVOD6wAKCRDiL5u/7jSFiAH1D/0f9ocmx5BsIiQL7j/o0zQrOpi7
+vL7RIT3ZT/UOlOiqBHlvgTAa6FyIfjxrvBDwQp6PiEnCikWmCJcHbYnEeiJoXSui
+TigmzgEbgQ7nzrZGWozwvVsmdH5XpcdGTuR2mJaUYJyNHSvPKL9pneIMGmxI7VIy
+blO8IXGXO+TMre4bGXqTmYCYMPmfqWCTBSorXWsdBth2S23Rum5d2RWK7alVrw5+
+uAgnAo7xz7e/o8P/+UuaFaJixgbyCpf8MC4FTYIU+rayjGbAN80lr1khMrXYvhfM
+9KS5CADrqncBBUZwHA4Bk+Rs9ZEm+q4EJl0lXFf3m+FSN1K4KXAxQQMrDUf/YxD7
+FNqUS9T6KloGZOdRYhZmzz4b1zXT9dCuHjtO2V809M+kpLJbIYu0PKMzyB6Lizli
+1Un0yFKb9CbZ/pVWOMLppKfZ9Sngkz+6Ppng8PMBFwO9eEwNaPP8wFH8IO7PMvqH
+MaoiMnNMeIFfSd1jIYvKdsFb33jcXFG+C4sYi066a83oxTmd9euKs+9jKzQDKqAX
++TxK+UK7W9KVbgx/9jrQ9UrGnucV50fcG2gowZrKaKvYj2jKKiGTCspKufL1fsM+
+WXjKH3M4uNeGKLyfo0c55ACBV1/C7mzHcJNuLYgfFYAgstdSiFZb6T/oN2DA8kp3
+IVirL3252Iw6mTgm/LQXam9icy5uZ0BwcmFnbWF0aXF1ZS54eXqJAloEMAEKAEQW
+IQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWUKpXiYdIFRoaXMgZW1haWwgYWRkcmVz
+cyBoYXMgYmVlbiByZXZva2VkLgAKCRDiL5u/7jSFiH7OD/0T9sIrJiH9/JC3QGzU
+a2iYvMwcHulbODqP+dugZzG0o+GrUWTF41KCpNxsSBaKJhXvpKmzTMArw29Z4fhi
+gMVkW+E459httukG+L4AMGmfr+xQ97wsp8bTDGq6Hi/ifhICCCDI9PKsxTCmdJZQ
+EVkwZqxq/mvpg112rYaDr4TvX6kFIi8mEGt/bNSxRY6WK+NMetr/QLtsWlcDsRB2
+/vuVCo7B7/KdEG4vFPpavaK9BSW9pJhzyjuUEAa2pPj8aXC/TlnhyArRoJxEWyWl
+Tc+Z4w6W4pXGedhFt6aqDbkyWvl3IXuA63nTCRqC0FD5h66Nn0ilfD2XQOBshBrP
+DN8xw5Dddty0b3pFCVrzS6l/mgBumZumLJYGHNCcvEZJO8IlIdhQcGVGwNxqiOLA
+EeCvTEwnRdopwGUHEsqi8x5N4+oTdbeUMlapotIC/wZLNHhUINySj1SW4TAS38/M
+s7wNm2ytXbM3ZHtkKSYaqrh/WfG1YXOhUtexRvajIh+JfNve0B6Z9mv1IQ3xx7t7
+ihzPjQxFJakDT42IudEcrreRYPAIQVtD4KnDyfowHnj6U++d8BZS7F3XrtsvEbN9
+/hL7NmmMUSUd7WO0v+kuCXRNM4tujv/+d9X96UgY5IXGC80KFzkfrXPIm79Grbzl
+AxGw/pIyClyb0dYbg5qfJQMdQrQZY29udGFjdC5uZzBAY3J5cHRvbGFiLm5ldIkC
+VAQTAQoAPgIbAwULCQgHAwUVCgkICwUWAwIBAAIeAQIXgBYhBKiMit0SmCjX6sAu
+UuIvm7/uNIWIBQJadkrABQkFU4PrAAoJEOIvm7/uNIWILaYP/jng5jmn273qBGR5
+UktjTB/K6MDUXPXlL60yfgpgO65Qw1LdfgpuM+sKeukFS/z/tUo2BRiUdJcLoeMY
+Fwcx/5bV4/ZsaYV9++8EZTEgCFazEi6HGcou254QXvisRP0Ig6F2yAoU2UHvvebs
+1UPJXt0KTV3C+CNVA4Tb/sZiVxDCuxnomlIzvtYjM+sw5qjyuj8AO50qDYEBBLxI
+fUVq98bXwV/wE+SNoVxZsPGQIYbd6SNNZ3rOU7rAW6l5GlvMBT/uZ2BleZsbVs5r
+OaGmb8HN63D67eqDR8wIUeCmXv1iokq7qabtI1TJslJ+Ip7cMrglTRS9qmPfPobZ
+syx7wjZ0BPNGUercbRzc8zG9jdAxW4CNxuLBVGhZYV9bjUJKVABET8zao3h4lIpq
+gFJCnh3DTnlm0BQ2wOZj8qHQmrnZo8d9Wc6xwmVegbOAfN26ituwW/wcd6wWqbhs
+AmlpHWjmiFF9shpJK2N6ouwK6r9llMzlXQsP1ysXJhb9xus95vkNW7u1/u9PRGwH
+www0hM0x4c9UheF+pn1nsLfQQPlUeFQxXxrY+dx8eVlPsDvJTAKaY4zPlGdrkBBf
+Yp7u8PL6PxZPwgEXKDrgjanqtQUsQjseyZPbqWZticcWx2cWTQak83MkEat39nIA
+fU+cctUoxdFKCsk95JOwxFl9eIgDtBxuZzBAbm8tcmVwbHkucHJhZ21hdGlxdWUu
+eHl6iQJYBDABCgBCFiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAllS5i0kHSBhZGRy
+ZXNzZXMgd2lsbCBiZSBubyBsb25nZXIgYWN0aXZlAAoJEOIvm7/uNIWIK5wP/Aja
+q+jDthUY6ODVc3RGgu07oIwBVbcXRn1OPv1gxRDXTSz/8+g5fJL+vq75GxHW3kFj
+mhXZ8VcR3OjzkuBWXeY6bfI3CDg7QcS2rdUHq+fSjJyZVQpVpQOmoCrhV4lm8Cub
+KVbD8mk0H5kJCHEkp2mIBnosTc/7zwVO/oa7qNuMkgVsffzwe5hsA9fTSu1htEUd
+W8t+5Sv3VO/Lg31dxnQFhgZ1BuRQjP/vCjRt+pGC3gpxV28tWwzyj+2xz2iTkOUB
+9UWKPAOzxHdbPgsOMSe4fi2csk5FOSA+UJXlCmq13reBC5t0XfaRG5BoK6bWKRzR
+DPnwoZUKI2Vf8MXIJITC0tk4RzHnWCkArtZCKfOz5QjBPtf1hO1NLr69xULUk1ir
+yq5P9h4VL4k8LY45e+NW7CpITZSuquCfn6+Bs9zIyW/czFppaxWHq/9Q/Z7+IpwV
+IKywfbrJP0cm9OPTyxzLp1TyCLKCpMP2iPUdxLSfM+IIvkLZMPTU+1MCJR8S99Ny
+hXM0jI8W22adumYZtCYWsGveEFCrWmGu6xFRpmkF+5d0YBXnUYToyOPgTQydpDfi
+2OMB82U7ow8wkOBDhN7Ky8+2pfNli/p0/XXacNSQShqNmiZ5kkUpX26Y/ErDmR3l
+DVDQ7A+nEzh0ISXCMRYrHdGtl6KSNhgEQLhCc/C7tB1uZzBAbm8tcmVwbHkuaW5m
+b3Ryb3BpcXVlLm9yZ4kCNgQwAQoAIBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJa
+QQajAh0gAAoJEOIvm7/uNIWINdYQALg1RmGkiKGeEcun2srGci96rFmE5HiAqfkr
+K+QeDHuX8nUd/uqtVF9L3jYDqyuSyPHRE4JAbT/XmZaXy5rzlJ3LJcJ+EqumeWKh
+1ee1+UXvC6ONH1WASSFmAnX2VySmuzLvTK+L7M0aCyZ/NSGfETSvAD4R3I+LqdPm
+jv/X3SwiO+aiZbuaSw38Kp/E64QOhvj2n0/Z4mcyeMZBw2h+kc4uan6+2P74sfE8
+8dnV9L0eWI8f9MZ/0cN0s/Yey+WfHhSxEjUz8nRwaZk87rJ38kyzUpZUL+EZJavD
+MsT3GvALsspDwsvXUw53hBt/bdql7l68wfV7/A/Rc7JqlvRk1DHNaCTj33Jea/ca
+jTLxwvRTRoK9oc92wdFhChagbztSO/YozNwEUC+qqYhR6n7vVkhq+onCuaL67tIM
+E8HZhdvC4d330026n01FwmC+tDt61QiFugsON8xp+KLulLm0eTZL0NM9e9jPLXlz
+RwcpXzSw2dhz8nRmXgAXKWuKRTO5MXFygXpXnAT0eC7gkpNOBhSnauAiHVXBpGKf
+HdiosfXT4KcslB+GvCsqRnWltXbzowAnW2HZkenRnR03RGUNm4igNmmcfG89QiKr
+tAQURVZ0FytKuRrRNC1zKGz5s/qNdjSU6pm5xHUWz4XBOxIWjf8tXxvgv/WVvpvR
+uXioD2M7tAluZzBAbjAuaXOJAlQEEwEKAD4CGwMFCwkIBwMFFQoJCAsFFgMCAQAC
+HgECF4AWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCWnZKwAUJBVOD6wAKCRDiL5u/
+7jSFiJUpD/9Ym/U03uqoJX724xdaeXd123dIzbpPlPUA99Apst0KrtE4uBOHJCO0
+1LyJyLz/cDneOJR9Xko8s9qGnhVfjfEJmDxP3ynAsFRQDd1gZWdjqJt8aQB8MlEf
+DvrK3Z85f+oU94nZxhTkUhpWbh3mifhxim4aVidfsZQTWMd3Akz/W09j5UPR+VzO
+APdoqPahb2GSBvsSMeN9G9wCKAsoHOAzw7fIz+CCdRBheJh7SfH8D66RKZVNL334
+DdgLXtlhYkPxhNBhqcJD8oFsu7/p0efRME11YKPT2yqrXkYbv5gSMMpbmvW7YNez
+SGnux0qmGKpw/oCvvEyNuDwmrR9DDXNFtq1hOpf1ii7XZZAyaQHaB+iShVdfIWpP
+q6lkLl4UThoN5ZbEsJuYGiZA05vyw6oWr5x51Ii+keicidGtlljtv2c6HIL942QP
+3vVcOIYtRKAWX3g83MIupHxB68kfXt70l5a+QHxB/rNRnt+CJdi3dcrKOVIWeBUO
+tasPKkNmPUOcHUtKQE9Yd45YKi78QCrmxflsIbbkQBdOkeqPCXN5bC+N+ocPLYXX
+GFi00yjwTTh4XEJDjbPSarXCVtOt1cm/9164WStI5da0DPxqaJdiRgGYxJs94YsF
+QnoG84NqQ4mFWGHXxp3aPpRp+xpFOmy89PPJXd/aE13R7VONGzLtqbQMbmcwQGNy
+YXNoLmN4iQJUBBMBCgA+FiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAlp6NKACGwMF
+CQVTg+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AACgkQ4i+bv+40hYgK5A/7Bwi7
+jwlSEPbsO5FXXb46SdLuQSNV5yscV+HH4i8vrbDXuPNnSgci7eJ4nMvLvx5TNiju
+8f+NFfczv0q3AttTdHNy9qgUI0pUGDktMt5L4ZFJLFCKXYNzjFukAjkm8ynX/MbX
+SLvu1qvZsgZGykcT77qkEVext3QaUOcv8rNJy7kZMF0tvxY+h3wFTdwisg4YRrIZ
+6XWHsbmCvmPCwqxWPIzKZGqlRr7nQyb8rYdnk4Lam+shG8UtJt+2Gk5ZPI0XDB2z
+TkZ14uqFyQV1foIQAhIzwcbjpBWAGUo4Ppo1RSgN/ixShyEwNM5uWUbfdtYUawkT
+pcVnRe74AUCTCHBl7A9FzbWwn8+4xPrfAEfkvZ89GYKq0JxDAXgCjmzmwShcTZjP
+Jc3vBpZLEOTnnNEipseL9Shb4kdK6WUeIlSTkuxYomgidcLRcOYK8+Pze3A1zwOE
+5LSKpDMucvq9bGCveRONQeHCQE+zzY7lUBPohXj0HT215+YX2r4dqz5KYSWjRAXO
+hM7ZfUK7sRlqsnye5iLUVNQj9LJM+LcXHOOiFVpqyHk4iqVYDNsdhjsMu6Xl/uOJ
+j0kHED43ResOMZa4n60bYNSmj8p7ar3eNdTRR6FEYkz9EeWdw0GMVEwceJo7bX1o
+D/VxpTIYX3IYVvna2vX1r23ERN5V3lLx2DrjpzC0Dm5nMEBnbnVuZXQub3JniQJU
+BBMBCgA+AhsDBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAFiEEqIyK3RKYKNfqwC5S
+4i+bv+40hYgFAlp2SsAFCQVTg+sACgkQ4i+bv+40hYiiYQ//UnNfHDFJ0YwJVmKe
+xyhkt5VX/Sjs79c83d64ULQJSCWNlZYvmozZNitAzNs9lwSLIL3Bz7+uxXe1RlFV
+pPXTZMHNT2ONP9o8bmrSr0tx52RW/8ZcAgdXR9GUCBbYfJkda45qk5mP3igExb4i
+EQ5DjW1yQBe3GTOUjgpClKsgfXd0x4SFDnJYh4NEsT8VTAaAMuFLF+O4rz00woGq
+BXO10P77Vh1XW3b5YGaMmh2JemoaG+9qKHZ6wyk2E8mh582AtBMYXMgMAwtRyCty
+WUlQhDdRNzo7oyspZuWOK0NARouWF3t3JjfNDqTyF2sN06Es+EwG0H9+NiQxcDWD
+WveQG0J7Ny1a0dPiVT//HmTakgFBC6ErPB0r5Z+gHx876EORLw3YBOZzxatbdD+y
+2Dvho9i4WVmh53bB5gLrcmtWsIvHC9L34CrQDi9M5XLNrIcrRnkWttiNsH3LgCZT
+C6PTaXyN99shpRUcuaeK5sIpyf7yEiUBD6Tg2ctF+SifwAoKccTW51wCzfuqFCNg
+BGO98cjdpo//oMMXrQgEFBFEJggrjWX92bMVoS8yBKRyygwoFRadb9VtdE9E3252
+HPxbL9WThhHB79qoYWoDlPVbeyi98sVpr+/rvo/Td4Mj+Zh2BZ+ZVfVQwcMx712T
+cwXQrflzKtg2Zz+gINqFwaM0giu0Dm5nMEByaXNldXAubmV0iQJUBBMBCgA+FiEE
+qIyK3RKYKNfqwC5S4i+bv+40hYgFAlqINbgCGwMFCQVTg+sFCwkIBwMFFQoJCAsF
+FgMCAQACHgECF4AACgkQ4i+bv+40hYhsnA/+OQEYgEwAhmmSx84vUPLyCzDuJNPw
+qjNRH0EjRDWM4ea/iAhnTdYN4GQoJcIS7jZdD0saVCzteWn2cP8Mu68IzcSjOq0t
+jdNSuyM5aCNpd/BV08RM00sOAxGdBiG68cJ2FlYqHYZ47BzxlRtxSKHaC4GxQMtR
+lNIcYdSq1++y26Fb0L8/DhiRmzCCxG76Aklg2zV10Zj7ao0Bk9I/sO3HdWPyxZRQ
+ST0a26bOOonqKT9AQJuupI9qTL2o212KrVTm/pBQBrBG1TFVm25F4vNg13qvVqNo
+nl7f+tWDIo8hh4/+mFP1SSiwop6agO7suRA4GaBRuklaTeYsc2wle5SpZ4dBCYPZ
+OC5wAiBPnNKfr2CZxk7NyPji447UfbMj6SFYhLBfQVvnqCazx6G8YOyjWFFqerbv
+4U61ODscpG5Ri03qkHv2z1roTo+GhvEhf5ru+UgGXh8A4Iu57YOcmqpM85qiwwDQ
++xCRm3xWrqk21q4svxS25Z4DsuXdRGb9qskYdQTFwh57TQ+f1EWdBEDsQAUAA/po
+ZWM7pQ9s4f2HhwdrBy/qvlDSk7GjfVquuKKu2/JzX6L7YIkFZPiQmPBfKmPVK7GX
+fVvyQs5RpPXr+vcoRFHDWx9GfB9OFJzpsKbkMsFw1RgyQ1d5YAoWbIb9XzCJBFmU
+pwTVHO/GxAPdIce0EXg2ZTY3MzBAZ21haWwuY29tiQJFBDABCgAvFiEEqIyK3RKY
+KNfqwC5S4i+bv+40hYgFAltyhdkRHSBhZGRyZXNzIGRlbGV0ZWQACgkQ4i+bv+40
+hYhT4g//Yqu0cvNTgdxvbZ9ckmQvLDAQOiNTkjmMO3C9jMSJRWXYFyz7FXbrutzg
+AZmEW/CkM4zOlXra1A8BrWsAnV5b/j6X91F69TsVbMne1YScftHEs4V8K213XHwk
+J5+4BKLG+IGf3+Hjgx2Tz4tj1jX6TzDJ3+W3nJpCmOO5JDWnQlNweYcv4YjU01aq
+zI3HMuULKYsHvv6XJpcnFiu/MY4+zcJCbDHxmB77itxHaeayTCbXinSTrblvsuzG
+DHornawq53NAGh6YF/9/RljoAqx55AZCUN0crfR5gNOKEx0QTEF7NenifiFxxS8c
+QoR77rPTG19EVP1wQFC44brPeKNphDRKCAa7Mst1gMeWfPFm6Uz4osxYye2dhlNc
+Ho1pU0fCRTE8cHPNmrSyRBQ4Tvfd7gXX7/KnfwGDQZ2Dcmjb167Er1InAho8eYqX
+8xye0kj3pRJAk3VaYPARzPRalEX3rix+BcQTRqm4H11or8e2nMRXrt/B1x1sAYYu
+Tn/x+ut1Atk4NpLrNapDdB+byQt2l0vRqv10zV9ZnQh1AU1wXiaZEwFcSn+KxQVX
+9zsY/LV4kn26RngPreeveXFIXW5FCKNVbNVhJCHR3K0z/egyTU0l5CgsrqbMoW7K
+8ZM/AxSQaNY2b7LmujBbSpLigqPpcfULpZaClp5D5NDPreunQA+0GG5nMEB3ZS5t
+YWtlLnJpdHVhbC5uMC5pc4kCVAQTAQoAPgIbAwULCQgHAwUVCgkICwUWAwIBAAIe
+AQIXgBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJadkrABQkFU4PrAAoJEOIvm7/u
+NIWIF+kP/RpsEcwAdvWsUi70Eu+ME18bWnH9Io9p3mxz/wDoaTh/Ej+J549hKCMz
+BZAubB8ud189JyYzQv0CUqDbD+1STa3Da8LCz2uAN3Kuo5+gR8m0vCxE7pRjoemM
+f+HYYGoTsUCVKtN54Voar/3htjMq2sQ8bh1HKdkgKSys169Pz20Fo1auz8t0Y05u
+JRJbMMHWplbXEA9aX/VRelIMWjvUOtAFu8Rp/dngAVcwGG+xqb7vm1AM0Mdhm0He
+a08v8HtvdHKHRc3SkHmrVO4WYBHJALSRn/7oTt2DxTARHe6Qp7QBIXN1u7Ajja00
+4K7hmhJixVBZjWolDFg6i2viqkVe+6n0GUcqOs0AEjnkDJKMg2AXzIggo9c3qZmg
+VbcO4xYB2acbusHYSEP+B8tugpNxREWydOU6w18hoVPth0oD7v/gWfY7PmYZnQAu
+CUOKbwz/jT0s69VXqwtaaCi6TjkzYIOFy8zl08stEsgwN0bXujk6X6vZu6u6tkyT
+QJIjObYi1NoFqDtH1UQG3IsQLJ0zN3KoBOB7JjfofF3Ytz4uKdAuA9hVaaAJ/QWn
+EUAAWHKH4KV0arRlYT/GVf0cqkt62NaDxql72u1Fh9B1zx8FCufgLrVlbhyX6RP3
+x9K+Ax9qjLvbCW1+ch1Kl9K4qVLF5MpgorZt1SA/c/UH6Y8e+xiytBlOaWxzIEdp
+bGxtYW5uIDxuZzBAbjAucG0+iQI2BDABCgAgFiEEqIyK3RKYKNfqwC5S4i+bv+40
+hYgFAl0sdMUCHSAACgkQ4i+bv+40hYj9mA//SstcJPuIznSOzxBOncaG2iNTl+8Y
+8OAuXovLF9RIBSz6LCszjdgXdXRuXrTOLKonZFrnCC4T9gPLyu+Mp2GKrwNhN2/s
+bcHJzrWJ3Yr4eaDSHEaiLZHsFQUgAKfZUMWMXT/ATVLsWDPhhB6jGVTk8jjXvoMx
+7ylGDpux4QNl19YSJ6mqFRv46qWkZBlUwKDXBb3QwaLG68rMRRba9phYcKpQVCHf
+7lIXYh1Ds8JoEQcyBkpWolBxl24f9CUgYvPd6ZACNXrodTBr3bGXiCAlceWggKwK
+tbjwoWYCA1OgQpt7F+OeQ3S8i5yHwFpJx8d7/d3fd2lArMRGJ6wliBwuh6fhN4tZ
+T278HqRaHNfXwWt3N2nUGBbYIiurJNSKqTRV5EQykCh06Du36FSfAguq3OAA+Q7/
+uQMBP4eYanhiP1TpPt4HQVIEFV3N914o8DXnifBOhTieW9Uwa+ltUegyiVhTvmH+
+RwHwcp70rWJ+xrV3jS5B8HV8yybx/c7sJioygYhiIQ1SSzuWb1yJT+etYZ4P6D1X
+WdvDFA3zxt7DxrxHarSQGNzbxYSkwkWmQ0LtaZLmEdkgdETZbTgn8xfvdV2g6uQM
+mJizGZvP/u4jD9sLR3nVnFXlL/WrC6lBR3UkHNiMAp3FT0W/HNZBof2KDMHhmhWn
+Q0pOSoRW2XMXvUm0Hk5pbHMgR2lsbG1hbm4gPGdpbGxtYW5uQG4wLmlzPokCNgQw
+AQoAIBYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJdLHTFAh0gAAoJEOIvm7/uNIWI
+7vYP/R/Ds5wvuIXk5d4rgvr5OIJAwDUdYu8NbFITt6wE8o8G1eJFajE6t05xqp7N
+blYqLykQUH4LEajn8B5qDrepgOQEpYlzhFhASWSzE2yh7H6fiynFpDeXGeh1bLsR
+9fswKOAg7Ch4UrN+K3Fx2rt7ivOaRcsMhSK9TLFyR8gcSIUSWE4s+YgGtGGD/IzC
+LL5i36AulJotVfI67N+RxTJieW9y5KPXYzNo/04Yn+aQrQWVn0Hxf1+QWNEGo1c5
+AO2L09Wzy3bpEw4VlckE27YiKcxnqXt5PtoStbhIw7GtRl2CpMZHj6OXJnlzTntx
+/J6A7Ys4MFcjJEu5VhjVDYnKKe1ksxvp9QOONzz3t2VoC443V54QLV9qY1UNxXTZ
+C2zGJ9YgOIfIfIP55kc/x051++rBAWianmIyFs3MqVAPXoIdOXm436TfrI3Uk5b6
++RH08dpkCYbUEoh1y4fjFHj4MUuWyxo1ByghHfn6J0BEF6hfqvL/WfQPhtvUY+K1
+OikdiwGYh/lzjiSuTT+W68ltzM0J4sF35XELAvL/7QutCCBFKXHfto/LtlUjnxnw
+FRoLg1Kq2LuXrKFoeCcbhzJuMF88dKD1Rf2r6hwqmZDLjupP7CsSvN5iC9vtaGz0
+33HkfiKNCh6lQ41UBy57n/iquiHZLZh1Pr4OgadnQIwHhlcFtB5OaWxzIEdpbGxt
+YW5uIDxnaWxsbWFubkBuMC5wbT6JAjYEMAEKACAWIQSojIrdEpgo1+rALlLiL5u/
+7jSFiAUCXSx0xQIdIAAKCRDiL5u/7jSFiP+IEACuGt+Fmo//CdHBB5HM1KSkGtHC
+9VuQAVMCE6RxGHcDrG4vCAeSeUTF/s64F9D2zLEJdBg1WUZbDSEKdsz33CCxEiMp
+XcvWCjI+SSwPs78kKoIhYTwYW9dWE5exfJ7878pMCQMCq/UReZyQptiICW1AwOuM
+7xp3Qbk/VcTYtxC0UJ8VGhr6sEzjgO6JOxKBhCNcLFOD+3O96MXFYuitu/v8Awm4
+Z+XeDe0FH0IALx5+3yWVbj/sPoMx3kgf09BZ59a0KgDrmDPlrdAjavk54DSXfuOm
+GXAOJgBMCRc30rHsGnB2UiiTNZu0BG34s7yc+s1Wdv65i9CJ1OhcmGcLRjNDq13N
+IY7lyRaKsE5XMrj5e6gIr7I8wKONRQZBt8bE2nI8xUkdHBshTfmna1pXkBDO3qRK
+3zK1iyyVYirm6zPjT9KNiQ8AFQquQayYxEqR9RbQOUhcTtz12mopY3+FEz71hvte
+dogldkuPq9hMBpeBNWw2IYYtXPvbuf9soyvn2JhPlY+T5BO1m1Ys9rrAueUUIRb5
+3jZSWCX+sHGatrt7WdV00QxZF57zfeB0axKKLToAE85R1imNEZF1TyRqMzxsQbWO
+HJ4WgA4J3NpdbFpWe+Al47qoLxT6pPJbWiNZpj4p0189itQNEA7g2nfz47LBjZYv
+lyyIXMIlZe4t6rkpO7QjTmlscyBHaWxsbWFubiA8Z2lsbG1hbm5AZ251bmV0Lm9y
+Zz6JAjYEMAEKACAWIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx0xQIdIAAKCRDi
+L5u/7jSFiJBcD/4ilWSQRaxbgWms9PXZ4HTvfrFEb2TTn86BYiYxEN3aT7xk1Tor
+6bzEmLVkhraN2NKgaYgCFt1/Wzzo9N4o9Ojtq6ES3SFx0ZSpnlIU6YaLB++710gj
+mP9KW8sBcGrNBYa0eisZuwqM68GApGDxbdzqo9K7O3alYdf6UsmTal7iWHCRCz4d
+RShxFYWJbOk842rL8e1AWkF6nvWIq+7/eKLYyD5XAIXKw/ZB/hlcdgd8m0eO4pZ7
+s+fhuaVPFryVzAlAnLQRdMStUo8VeGbOqFCfS0ZB2cPt/D5d8xRvnaCeSae3xMLp
+sytCjDFz53fSTx62ZiwqOZoncPmjZDzw26vOaX4Y3fVB6cQKYRGke7V7pagIeH5N
+W60mruk2IVoA/lgOSZmNT7/iVojHpYQnbaIZZRx0a+VjfgtNjcVouo9Hkll14397
+vPRMLszDHmpvG6/WAcFkG8uJjRihqwVzY+gsAKP5r94IQpQ6rKVCJYxmko5xpYOD
+g+FRnt21GQ3XzUnOjdD+9ayPULA8sm3/IYraP6k2hB3SCKhMpEjno8otabv9h0M3
+FlN8qxJbXKWmMS3Ttq1DxpK5JwJ5jaBE9WfeDtXM1cnCju+qzlYSXogje9dc508s
+wSIO1PLzTYFCanCDUKqNaNX5OqvjtUWslKc/hUPdqtptolGvrXiGhnrc1bQpTmls
+cyBHaWxsbWFubiA8Z2lsbG1hbm5AaW5mb3Ryb3BpcXVlLm9yZz6JAjYEMAEKACAW
+IQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx0xQIdIAAKCRDiL5u/7jSFiInyD/9m
+/qoOIXFmAKlGjNVs2nYaP6vQCCAzcOm1WrwP6DYoeG1JWjNlFr0kKhJa/mzR0hFY
+b2+49+Er06l7N0txPRcUhoD/8kKTIx8ljX27icd7BpD52XBqLfnX3Th3OHJA5CtZ
+epQ4/bgUqXkHqUPsmVQHyjlq7liYIlZWwMBfA+L6eLF4MQhcnIoHkfVLyHvJ3B07
+VpJAH5wBBC3NEaPTCAeHbXdSY7vVea3kSZZ+dHcmcIcpBcFFTggruGlPdBfUlt8/
+aeRdfAause3xkQ3/n7wTXugYPRcIM1MsDmHuFaADWEx9eEEg0WY+Fb9TD02iwTyj
+n2zISITI4k/FGIW+8bG+qrE5EDxVxeuociVNi76byaPN3Wx2JhDq8nKtfVWLCbZN
+NN0HYpgfoHYUtFR7SCDbHvL0MVW607Alyubxo5n/WN6RC6jxmFaE1CZpl1jVp5cx
+/Uw5NBv5OwQzd/OqLiTXisM0Jou20c777XRL8nTrFniW2ym38xDkC7q17yhDPesU
++00TR0PWO7RFpw1mzxggGXxmuxA+OZeR/fHdc6CpGmEj8YlpeOOoRw/58/w1LwQr
++zKUzuNo0wKvcn+K0rHoSVOX5O3XAxTg2FRpa48YCY2U4kUH6RITHZRg+Eh5LHHs
+Yj4ow1BsIfW9VAzHkt7b0MxdWsUwqFuXtsaMAg2HGLQObmcwQE5ldEJTRC5vcmeJ
+AlcEEwEKAEECGwMFCQVTg+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AWIQSojIrd
+Epgo1+rALlLiL5u/7jSFiAUCXSx1iQIZAQAKCRDiL5u/7jSFiIP0D/4i40WiFeRM
+E3l9El3UOE/AsoldFGSfzXtkfad5X9arry4C4UFRFcQ3OtJnGvfW3Nr/mMNa6w+x
+3z4KnQRqhx8hw3dvQXHZQUjOEAQMg2K5ns38Wixk0OD5Mmqv9qyImwm5A7MOZt5p
+uZqGv21YS8BXQsMw+CLXRsapNAqI4N454bDBoA+l34tOiN4jPgG0jBy9aPZKWh+7
+nLHIn/DJ7xxT9Z3P7gSPcv8cwEumiBA4x79Ac9YszhafMiqwkk3b20DGjAyXZmEK
+l2annmwce/u3P8oQd/+hmcprQZMgh1vxA0wm9yQZ2zUZXAYZASVew9v4KdNVJE+g
+wKfJd4TiPrOkKoll91ikGCY38PPfLCyvGobdrE2LW+6ApQtcTrKu8nzkCGdZiyZ/
+SE2gHMOrYAmCpDH6ujD0z76ywo3a3Ta3njId3S+LTPtpBJNq7jRgXJPTw2OVjt23
+7QrM0QbNTvbjy5OR60BSFhvd4F7LCzhM1b/T9P8oFf+uO2hQxtLnNqdYR4idIRFP
+gXjD5dYpEfTOp1dZviiWqXm6jJFsLJy3FtRvtICLzram0nSO3xA0bB0QH6MqDp4c
+4kcZ8JEunBvsOkIVlzT83CGrBCOaydw+1d3wheFQItPOydVrqVtH+0rGHUTpapL7
+BXttYTzl6n7YIpNDqBufZcWhmqBF2FPTWLQWdGVrLm5vLmthdHplQGdtYWlsLmNv
+bYkCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJc3Zx9AhsDBQkFU4Pr
+BQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/uNIWIZ0YP/RM+jV5mCCf9
+Yj4KIZ7u4FTUQ4k9KqfvEw134efgZAR5n3+3MvG09glEunMaeoGQDZ3E7qHJs9MT
+cSA8RSfXT/Xp5+dDDpobV7TKzV5heTK6+PjsKH1KlvT3mTDcnRFnwRMQaK6BP1KB
+bLTpxW6kDFD50OChVC+iEBunkgplVl593KLLkqeAmuIzThKIbZLOT6H+bGthSu+V
+IGLp8MfVtjw4dflK0TbvyIVQRVxqQPv/egKIaLAQwah+21svfXib2tJ5kxKlFVbC
+K9IpTcBXljiS7ZQo7/Ok1Z4IyySMeA9CpRZjL0zSygNpHwk8Ml8UcNmcvd1Jbe94
+jUOJk6YIOhAs2LkzxoZdzuxx2RrB4kTHBkM+zMKSJGMXspIdelTOYfjOPvS+wb55
+QobWo6tey4z5LEWt1ymeGTs3pCnAY6Y9FNV2x3+MExhFK9AumKXgfTa9H0ew36wf
+Iju4oAMVWHk6nz+Mv5IwwcFGiH4QZNLZj0r0F0dAelngphersaPZLK3sjqlZNY0L
+atQn8BOZoY8n9JN++JPIwq0LZH5k8Fp5mcGcOVNukXghj59kpXDLJTxnlzdqAQ+R
+9ank2vf9K6aEkzfg+7zjd2r/gkaluTfELmu2SE2zOPKQqwPI3+k+LU7TGAMOVFQv
+wnqwjo4JWx53R2sfHzJiLIeefZ2dGtDRtAluZzBAbjAucG2JAlQEEwEKAD4WIQSo
+jIrdEpgo1+rALlLiL5u/7jSFiAUCXSx09AIbAwUJBVOD6wULCQgHAwUVCgkICwUW
+AwIBAAIeAQIXgAAKCRDiL5u/7jSFiH+hD/9Cp3c2QVCVfEF+eKIPwyKB5jgbmY15
+rsBb75xcNAYK73O4DrYn69a0GdiazxYghafQeg8FOLmxF5XB5e1vvzXOTe0cEOaO
+nnsr/0JhTU9CHIaLeOtgNI6XTF3RQ/r4pxmZ/kIpsHzjrc5t5SlOnGdcElkhT5mi
+YpeLywJyBjwJvnTQr6ks8QF4RGgUpp8V04we2vCkdsuSGzcCDm40NyDfEeLa8tCp
+h93almEtDFI3gcXHr7JiAxj0yxVWi0KaejwphwnudxWu8kyfQUEAKmn+J9gOkOXs
+vTMvRc1f3z12QRSWd6DnZzL9EctDbYXgG7F55me1zLBSCuZP7rj3gw1VfW5+XFEH
+Giq8hqf/V/e7Gq6Dtx5NpAj7TCmBOUOAL80Z5jjOaPSBhALlE0RLuFF4OMQm+hg7
+2tZC06AUJQKmEtdxWt96DKe2lD3pNdsb54T3mxjdgHyO6sxDYHeMGu+D6OhP+L1Q
+WnPnkv4hrsjEnbbHUp98nbiGA53NWTLllsHXmWIkeokau1VmF6ad45z+d2Dtjgo4
+kphux0lYAOqHV6zL0DfTT2MyCpm38dHpHEKzAQVaeppeCetg8HXOMdvbe7b5Tsk4
+HQNryD5B2PD3elvD09amc5SAske2z5ZSYKdjDnrNjUhOESRb5eDEE0b9f5wYzQxW
+qV10dXdgccCIfbQNbmcwQHRhbGVyLm5ldIkCVAQTAQoAPhYhBKiMit0SmCjX6sAu
+UuIvm7/uNIWIBQJdLHbsAhsDBQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheA
+AAoJEOIvm7/uNIWIbAsQALJNb8HvAPNySloMN8tkBy6jjfzndJGq0JkA5rhT4Zu0
+U3rolSXoo3RC4uXK9vWQXAzE0H9csmC0+4JquXOIJ6Rbm5oRRwtX51MdLJKxb+L5
+kuqkkIbFgnqjWNCqAKB5IRnI4nHroqBwi7ZUWAvR9SXsFT7jNFv/DK0stTf7OEs0
+XsZfwnVZlGzn26JSeFrQEYyJB0/ecrcKZVohge0/FMi6LddjfiJqR8dREiW/mNZS
+lzxWyteluxd1CzIhPUCoJfLJu9g1BFZv5kjurVUsoXcNomYvQksT4mNEVj/wwIPk
+yJZZ6vAQHts3kDy1rRSiJCOShG305m+jc98wRBKhr+YJf+bZzBtMf+Rgfgav1ywx
+3fh8kzds+hFCektsPzGp4CgyT4jdn4Rh3QdqjMH5ah5rWbwxA8G0q8duXc7UUsog
+lMmmXiNiUD7Bg/TJoQPmL1Pj+RPgqH2xeVwnSSwDCfr/U2tHxBpgB55KfrH92U18
+EfW4oW+w2UUhaBEZyrruKdaPUhv8gtx8YEEarWKi6w0e31KXmyPvqS5GnjOZ9PX3
+RtBHQkqg7iiUBBPz9TM+kMX5oHuKymv9w7aVMNPmnT6jKlFJur/hkBn9yxfAWWyV
+yEBAuMW04YjSYRbxjbvH+0Bh0cDpK9bTp6maCLnP15IIOmz6a8uH3M3y00wf5elA
+tA5naWxsbWFubkBuMC5pc4kCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWI
+BQJdLHTXAhsDBQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/u
+NIWI7Q8P/3IB/0RHO/oRz/1bclxGuCJYFZ/xa+SEnnE/XjAYvQAUn1jSfkdC8yRX
+3Lq2s321hjo0xQXgst0zV4adPOfSyGKFoodnO5mFY6a5pYq2CdFGmuQ6pQD+/OvQ
+O4PLLbtC3z6EtA2GGSlXgNTz0krqjaUX9FwBlbzgNoycFQrhVwx9PpXKDlORjgYU
+4m/VjOi0f0yLzZ7BWDrenw45Brv6zZuFjfqxSHV+u6ecGpnEx4GjB7o0w8MGV/v2
+XZm2p591cJ4agmewjQhfBLP6D+fw20ENsTxgvYYCX+k7dTeFRSrKyariSy702ryu
+QYsh3c9+0EDomQOA0PH5/063f7msXNNwn9MsGqXQJ8umHDRnmC0aMdzQG5m8Orsg
+ZnPBRhvQmDuQRUVworotAqqEfoQ2WWFedNxRUgd3dClaDts5Uc65Tzzd8JA+zYIo
+byKqE9fP3vtPhO1y3+BV2W3YLsmgQtVInzPJq0HBjPXuulFN73eA1PJanRT0n8Al
+730Y10CKmxAMP+pQ416OrYUHwFSCAD5v8EL6AET/NgnI4d19imyoAcjR/W4MyNTx
+YXRlQ8v38Av1B6pvNC3/9ktPtyf6RRY8GVhMVQ7DUSLm2JOVx9vQj2jYM9a79i1+
+sx5OfDGnSnFfQgg+koqyiR82KSqwIZImiDhWWyDkVfnWxY9TvuaxtA5naWxsbWFu
+bkBuMC5wbYkCVAQTAQoAPhYhBKiMit0SmCjX6sAuUuIvm7/uNIWIBQJdLHUDAhsD
+BQkFU4PrBQsJCAcDBRUKCQgLBRYDAgEAAh4BAheAAAoJEOIvm7/uNIWI6ykQALBm
+K4s0xRE8T+MzxoeSV0gM+bQPqcBJW0FzTFHMOw8U0iJU3kDd/DolliEj2qSnyzxW
+Wp5WJVAp9UVyDxRb9MVp5l1qsniAkyeOVknFD6xbMOohY+5AYgwb6TzwIXXyKxCq
+O3nLE/qzTQE8WI4uLU+PXFQW2KkWY1+XzSGDGtP7c25mA+Rsy46Q9KskEe2S5m/j
+/Xt44pTU1AeMMdN4b4PzM8X60tlHAa/mLg06RT838cQEtJvYJn4nUj595f1aNmqW
+2OQ8AYa9AWBTaQRZls+zSQ3pDp0vEn7pvAn4gJ0cC2CLE86HaloIgBYUeXfc78u3
+XXn9RGdvgNJ9wKI541YLaH+GS+tezXP7qHtVeoLjqYve840Oj/D0fA+HqfnR0AjY
+h4aYc034ShiVUlEfWv2JjvRLIn7QegT1GSW17CftVmp7Na9UyYtizYra4bTVTaUJ
+fh2oKAfLwQRaAxq0knboBkFbXlvLBLgLks6o0hok39VWcaikd9veT8c1QlGID7FU
+Inleo/RAMaI1uJyZgCoXtlmCBz2/cOolTn+YeVfa26x4Yemy+habFkKoeVobwl/8
+1LN1q1L2CJn0sPB6xfNwIKEyab3Nx5CeQIuXZIk4Zi+wwkD8Y1K5SlNW/shEpQCd
+/bPrxvf32IOIfTSOnwK93MQc01r1Z2R3BRc664/dtBNnaWxsbWFubkBnbnVuZXQu
+b3JniQJUBBMBCgA+FiEEqIyK3RKYKNfqwC5S4i+bv+40hYgFAl0sdSECGwMFCQVT
+g+sFCwkIBwMFFQoJCAsFFgMCAQACHgECF4AACgkQ4i+bv+40hYgRXBAAm9A7Go5a
+2PINqMnVob2qJ+uwe+CXnvCnAkIagshn/MXZ3s0+SEyDsK8+zpSXw9IZ62kYPaqG
+Ao9FrB2sQXPfrhWw74OKcHG5GcT3ep49PUV2CcmyGzFV+YoJm7lsy4wbrl4i4PpY
+ijrkT6mICwXFjWaKakEbFKlnKDvGHEWvPLCJhWCszsfnfbqcRKwUykmEmwiD/Te+
+B2H4iavQbgzY1y2UPxCKZFWEhp9IMVGaZFW5H/H6XwzAGNWiOBLSsEQenN1C8TQR
+UgLN64oAuFtt2KY5SQCVnI+cHnHZzBq0Mpi5WdLhQfhXZh7lr+EzMuOlvqQg0zTz
+yH9jgXuh0Hi92Hx7ctzFtCK4DPNatMLlxQbo4TAseqBIy0ltQaIqrn0V1Qp0Tvnj
+jIT/maKfcJZrmCQQT5vsEgPNlsaK5R2pNDWjg3QtvK2+AtavSf3/d08fDIlneNlC
+/8E6XfZ0+0jJdEw2axyFymNhD3uDPEZ9Zo9ZCQQ/lwP1fqjVQhxrWLcSWdpGnhlu
+VWd4pJ9VB2HdXdbKYmyNakmFTbmTeu1CwePcS65mzEs4Eoq2CRxf3t8t3525cQWT
+MG6CPm8pRC7iueDVS6O+qnlV6BARr7wBinp9X2+uG2M9+P7JIBioFc7DCG0DIjDE
+ykq8ZGR5E4+Zv1cUBTWsw2N9EjF5rxvDbnO0GWdpbGxtYW5uQGluZm90cm9waXF1
+ZS5vcmeJAlQEEwEKAD4WIQSojIrdEpgo1+rALlLiL5u/7jSFiAUCXSx1VQIbAwUJ
+BVOD6wULCQgHAwUVCgkICwUWAwIBAAIeAQIXgAAKCRDiL5u/7jSFiIO+D/9foiQJ
+w9fpkXQQD6gP5fPZmgKuM0OHRuw6pEF26JgJFxtrDSx5SPbQxs4PlqavmXk0fbtz
+jWHd8w9X661yf890XxnzSSRZNItCfc7P/DZYTK48YN/13qv30zl5UlZv9p7Zca/f
+2C8VKICIJrSgbWNhW9Ctgh8r3pWCHA1rwtOWvwiKoyrQmG1/L9juOH4SlXtbeT/B
+/xxZxwq3EILVZl5rA0zVI9Qf326ElbDfLwxzpFCaSNVPB1HNSyBRSEdMe3s30IQ6
+LYBDM2ZqQJW1LOUhJIiiEyYTMfTpQROmAQ8k7GuGVPt1YekPjPdn7kyZdW/kbJAE
+CLT4isHgdi+O3PVIUUZn2ZZG2abUnPEdDPuT5lKS5FUfBUcWlHX0jtmtMsQwOwJo
+isNo9fS+8TRsWyGrNxO3L9KEu8ZSZK6frGMvJ+y6nJW0F6yTFVikUEMxYoHRZ+A0
+krenG9/ypHEAi54tSAhKLg/t0yae01M9rZn4YuztWdgu8uIEW9JA4tWDXyqJj7r8
+YZ8/A3oK9qiFRXdZ7oS8njnQzmi8fjFTWiTp7l3z9MHn6+DFwYJMN9adx2lgUvHg
+1Cc9fVOuYt6rSTEwURzJSOc+3uFxH1Cc6MI6b6CiZDe2ulHBh726ng+/8rFKhk4o
+z5BtRWCYPwDrkYok5VeAkdOgFvPrxvZXOKuvq7kCDQRY+5dUARAAvoopO5WMdMk0
+DY5su+KR4V3fR2byaCSoZjbirSZanAUj7PYa4qM8EcJ3zj4FsZDPymKPo/J5XIa5
+Cn2mj9K9n9Y3vVf+tUIl6/lKuJxkBsPSqzM/6gltWb0oA5QkBFX7+pZTW2pURbWo
+vpv6H1aMQJoYyBdbSx+Zu5d/6cVPwO1ZXPiNDcWiHdLVeFMNqL4LHdgMk+igzfQP
+22XTOMUB1CCF8umaR3qkrmyZsJZe4XyJQC709QENHqsUQwfz2grGA8pvKo/ZPImT
+sjDydc9EsHzwwTkD0t08b1ft7aseuYjrY7/cP8vWwYUaNTrLwZHcEv91gV0aIjE0
+U2tpV21Vm4p3f15kqRUPfP8lUiI0inFTp3pu/+4cTBJCmASSQyvoSTlwtGdmDgWF
+L+x7Db//xtvIvfmibVcazcL4eoaI9FKqZvMyxySLYXbibBO+G/g4aQHDCycyFOp8
+dtJ/skOb1ADufFHXnzkEUKJ+5H4YN1Qklw9WT9YwvY/vWcOI2chVD+L8x4+H/LMg
+7OBjVQIySlYHK1qvFSFgM2nNhMwxbtUMSpzXrfpJRdUZ76LZu3IqcV+WHCrCwxIZ
+n6CJ5QXC1423B38GsUterRzI7K9ug5Wvq4yuRrAK2MjgcgpOjsVv9wMwxQ4APtR/
+hQbXN4rLAXhrj0QcUMnEP4W7gqBXTVMAEQEAAYkCNgQYAQoAIAIbDBYhBKiMit0S
+mCjX6sAuUuIvm7/uNIWIBQJZOVxvAAoJEOIvm7/uNIWIWsIP/jAYpQcV/fn/DVs2
+vBfGTgFVKyaYIQJvXxVpapcytKWngWwb01K6r7x1d4vIrUbmAhDbA6n9Tv+NWgIY
+H1O67431TZPOP+iXRTSJyRLopcJXY8xrNFd2tviQ4sglo2sH4l8gar9zXOSh68a2
+foSzLb9ZCsaNnvwFagnNXLwzBFGM+v/U0xkwU2xU65sh/TiyQ1DKgG6JQ4v0TtGb
+ae/B0wJlOHtRXyCtrmmTshGLXQsJc8iduHyRo0we+rneG2yhy1BMig8MjQXY+8bR
+P0wVW4WqRrwfykXjAtW6Qx8wU6fz46q+ve2Qhq/Ok9KpHzlGMgP6c6mThAZLW+3c
+GIdQBZlFsZ2KL62C6s8ynbab7C6yCNT7k9zHAstoDNgPORx5m/mArmvUqMdXycLl
+XPwlTAuA8YUsOGxEkmrZW+GGgq53uy+TQGmWqkSeO5uyWSs5yXbRf292PC6ZzAQX
+xHsVFXScqibBU49ELWTqUvDUV7zMOwjQy+8VBSMrXGGsLE5tj5On+HPHi26vri5W
+e5QzYrai384FSRSpdtnqqCysIQGPb+D6AgLJHdtnp+C6/0OULRrRIHF7JfVeuTde
+sc2h5G07wqj7SCNBRZC5VP/0JvpdGUHfa4WGEDUCXEZiMXiIVLMblfS04pAr4qzB
+1vCWqzb8xCxA+wTd8lTDFobV/pV1uQINBFk4U2wBEADgRM0G+Dnl/wlrHNb9sr3/
+yW9tHA8weIbwvfly/NRW6LHSLIPvsLksabVQsYbUH6i2aK2ZlE3Oo+H/R2wrs7dm
+VCo57O4MbZk8Kb0fatN3qhq6g/+bNobVIexS5XN6g5JcmXM4ZzR8Q0rEd46oaxFW
+y8nDSw4RR1d+OU5/Z/LHR1VUTCQKU0Q1Jv//4YFVq/BEf6oj4SU9+/Li9kUo9f++
+i4PaiWyrQDm1FAYtMGW5MBKH3ohO1dlPgqNjdeqTjZfgvCMPdbyV6Xwtz7KVkCR0
++r9u7JefCCKUXL3Ap4VPtjhyCLoRuqJ+ZIp9XR2wf3rVGR6KRcLWPEXLkGfAPCs+
+7uAnfReBxNiWYt+FHuQpeyUld8u8E0G8u9FSf/l25A85QrQK0EUrVHdFc1q8tcCe
+q0EomoIPl7GnwtDIwYmkWtViCz0ivVRvNBUTXvq0XtI/9kLgcBgKfzap8dLeVSXJ
+rUhYlbcOZNnstzkmut1ce8my5TwSRzr2dxgUF8563cM3cdLu+C9bdMWvR/s4xwu6
+Q5opbehdFHd2Hj/Lnqv+xwNKNFkhZCHiyum8L/VKQAsboXgJ7/sB7CHsEcBif73R
+Wj3bFcMnPHHlJgxXB1aOH4kM+y6fF8wW/bGC/9gGiYXzovdbopv3B89oyuT73aoX
+g4TIPz6gv6Bg1OiGpfseGwARAQABiQI2BBgBCgAgAhsgFiEEqIyK3RKYKNfqwC5S
+4i+bv+40hYgFAlk5XBkACgkQ4i+bv+40hYiyTA//XnNjay+uK31lmven3gLQ/BRu
+liydFotXRugBMExTNhBmv279krSpREbvZfFLSXshMcTDclzCY4SrjOgrXfFS8Eqd
+RCnxmqVxBQsS7T2ObAMQQLp1yyFdupT2M7no5bnDWetH9c/CBZP0bZa4Ar3oFZXn
+CKvs2ACef1BJ8f5vgx7noi8bpggUP6uDZF1gyyB2yLZEvlV8AWz3hN1otzkSmVgx
+bvD9d+ugcqOgAMv6JpbxxiNvqMX2CVjjoBeQC5/uKCkJW51NnZzHXtDHqcKKKlbp
++WrkwxjoF05uqRDFXnnCTTC3PUrg1stYkn7M+52dAG8HAFBtReuTZoZ3AL1kdtsp
+XoG8PtWHGzu1NNrPrxCL6KLn5jzjLGkWR17uGhmflKwdvRgrbWO97XhZoHaocaTE
+1pbRBRhJ8bhmkw08Yaatt9qcUqJLPyj0cEeNIU0I5h9n7n1CpTyns3Ow28H4k1p9
+8Sf19UqEOaUIeGtU/Gf/7qbD8q6lmpgZ5/0/7wNoBYM5MdvfMDU0NJJ3qESLiEm6
+lCGq4nq+GsLyFmIXyTeFbsyO/Ion1WJdtrGvEgkQRuVUB8HqsFQ1b4+EJAiBTMsF
+z+yxUcZJNEngQegSMaNyXFptrFrXzZHOXFADYmuTgtk5SbF0V5am4IfineWNSmM+
+nlBNsWyazlZNA3vuAvc=
+=bioM
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFZlTN8BEADIXdWebdUepgP8YkULGh2EClt/q2Nkh5QB+V88ZtWVdEfz6ELb
+KeKE/39yllXso20H56OfWGgcU2SF6EKdT+FDir5pDxM+RQiIjrYHLMj9MG87LBcW
+65PHny6hmXtrfrWISXq7x2Si5G9pMz33jp5Dsx/IMTbTPbdK09b34S9aqIjTkpQ4
+yqByi07nkRcYgSOzx1Dr/7oatKn5/tTRQm9CQ2pqcYYD5Rqg1jcNpKRUWFX/m+LR
+d3iQ6ZF/F2W9hR6BYWRUi3eJOFYX/ngWrSj3q3c3zQgPy7R/4weZRT/WYjwccHyv
+LHbw3YFVLDgM2RAu2q765+3iWrH4RvYxS0eMDan7uK6q3+83KB83ofnH8IEt6PWK
+3tmmQJ1vYbQDSqeLxiptPlOgoQuaJCCAFJaBIwamLZJq0BPmncDzZ3bGksROgV31
+qqFYsdKfyUnKQZZpEVsdpOz1oMK0RSlqW2j759C8E4DrsqCBoBm63lZPQsYp94s4
+gT5W2D3vfPqF3dOht6nByGVYvwh3ildcBtKcU8vctlms+izbb0p94pviM10/vIuu
+AzerB4Pb8qMN8+KuSfIUtTWprD/D0NAPRBpc7Uiv8sSufldNhN+A4GdkkXe409+A
+WGusKMlZO9fP3BYf+J3jDxlbRoVoEyl67dioT0QbFdhOqQt1EjJH9XT77QARAQAB
+tC1NYXJ0aW4gU2NoYW56ZW5iYWNoIDxtc2NoYW56ZW5iYWNoQHBvc3Rlby5kZT6J
+AlcEEwEIAEECGwMFCwkIBwIGFQgJCgsCBBYCAwECHgECF4ACGQEWIQQ9EQY8EPmN
+FL0k0UcLCZjvhvWbagUCXkKjswUJHDOYZAAKCRALCZjvhvWbakZOEACqn3cj7vG+
+jjzbnWsohTwN9kJnvpRAtWwFw2mPYLRdFkYgBwP7AroDUS9nESzajx/sK4wUrfYk
+C3hK020Bx+Elg48mdSAy9O1/gUDY24rANTnfisqtO2IILsEyd6tJJXa0XHziH3Tz
+dggsCOEEICCOxLOkDi3Syk3P5yUL/OHDkLJ8nv4QJBGjjuuX09CErX2NYQnkqa0Z
+MOecfTtv++jO/jAXGR6Hl3c4lf2udt6fYV9zrtSkcv/NPFvJ7P0GcxA1Xws4OftN
+z/8rgz1TfVuho9mBIvvUKVT17Z80wQCTfaBNkChbHccDzaQPSDRkoG2ohvYrJveP
+lKM9NfMpPqrjceaO+rx+Ft5mBU9uSL8Oo8lJ2sMsxqmEbym1Xxdm96P3D+GNjZ0H
+Gnl26DprWTBHjpGSotV5rzncRh+9oTcvmzkO7hvgUGICHCGeyS3wM7qiiY2M1wHl
+5ChlOv5Ske2oA+EHoMKxJQ2iJpkfeP6rHckHkVD7vDDCaiXUYrfjCb17CSOUHuPq
+sdGbfHyItTM0cWpB5Jq/P6Mi9xymnxVpCeIkB2v05gszzGcF3+hLmRtdTzExilAC
+zmWKXLL/mD2SvnENXLOJ5lzJCD7yQ+KkzMDPqkg4JPeinyT/MX8q2uWKa7pcOHJJ
+9Hb4fMNwvUSsx01JCHrUS96JSssGiroaFLQpTWFydGluIFNjaGFuemVuYmFjaCA8
+c2NoYW56ZW5AZ251bmV0Lm9yZz6JAlQEEwEIAD4WIQQ9EQY8EPmNFL0k0UcLCZjv
+hvWbagUCXJn9KQIbAwUJDSYvygULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAL
+CZjvhvWbaqPbD/sGcPJJKK9siqY2o7w8zh5joKypSsJh2GZEpFMMIsIeVa1EW+ZO
+fXp9YQkO9ezBKxHgtZQxZ9dHIbB8Tn3+VnLJHd9wY0aE0OTPO6GNtADJWbzx6Cec
+75Ddg+WkCiTdn4MpMbF2jQKvy6nPfk7ZSbSL352VNVwrraLBjBJ3aXE+cPzXe76X
+oUc8kkNICvPkMSMljR2ayGk/wd8u8O78GW/8LUMVz2cwHsnbJrEQu5WeI6LVoK/+
+kWpHDjiNhFwIg0ZjgoOl42QOEAYz2lmGAhyBhrKd7qZ0NpSoPfU1InMy9j2AoStE
+hIOgdwepgXp8b71vzJEQiYMDvg6LJiANzohlJjzXQblzdu/gYaRjSNY1Rm3LV1ze
+MrPMqYeFeiQEY6Hic9VrKiXczIYhbHv4lBefwUJkuTk2y237G4CyIvBvXyA0lHDU
+5yumD4GDc66Rvyl9tZqKX+5Hss2dEO86QL+OcTYlPsGWq676c/T8mbdvfz+ENzy1
+iBa77WYFQkDSR1ND1f/GBE1fu7U1PuIDSo7uQarU6WJq3cQ+mtw3ncw9RfOpwqeF
+GTf/2eHAW980zhyo7BIhBYxn+SMvBV96gYVS3f+Ye6vV8C6eWNaOzouIe4+dLOFx
+kZAHkqCD8DaU5XalXwmeRTa5y1rzN9HMxNjfYeFrZu392myFjG55VYYG37kCDQRW
+ZUzfARAAsHyOwNqnALHCpk9i+5BkyiS0tLMpgzN5LC3RzN3Dm2CEyQPWISNrr43a
+FuoTMPEFkqsg0FX6LzVGZvqEtcS2E5DdvuVWJWBK+gLMxUWnm/p21ouhvLNcglYE
+FlvIscYmwVwggwJYm9TBru872gTT7s6NDVSLFXxkPf3hPDWCYeaooKcRzuXZWb85
+E1HFgUOR0uZCfBtPGG/tniyqP64g40gAV88WuyN4vkF9Nh3jfpjgQ8eYzhmWAAso
+EC6pBn8kjek6GnplYqkuy97FR87nXcb4d8zameC8rynlquNk9B4fsAVe36upQPJb
+GMF/VYjc1ubPQeawMrpYWPZboOK+oulSPaH7AQNixBpqSxdVNw9jHNZPSGPq2yPp
+pGTgI1wLWlGQlAEjBcPgEWWzWgW5os4oULn2D8i8S9pi8OhSQiteKZiojRD0q9D7
+TfSXA4XSZ75+uYxi5T3DTSSRa+pEufl5BMphVyJKvqjX+Ek6dCodUzfGE69qfKTM
+Vi3peEUMVMrsM2FoB7BA4l8Z/1UhoF9jD2yrW/+oJEWsWbJcGxsskzHNGr1ntk31
+u/MC+O8O6VFuuTjfpjpbS7rsbZZRtl1u/rhoCRpURz7AillX2hhl+5U4MOnYgZQ3
+c5Xh+5+mD8C0nMGz9pg5+6XK3fRfiN6ajHLcJJeN6bXKN8Pr06MAEQEAAYkCPAQY
+AQgAJgIbDBYhBD0RBjwQ+Y0UvSTRRwsJmO+G9ZtqBQJck7EABQkNHkaxAAoJEAsJ
+mO+G9ZtqMt4QAJznYvhb1P2TXkq4eJ+wt0E5SWilT6+tjIooYA4p8oIDi8nl+nHH
+MIo5IllAYnWXGkaxARVSzA3Ci8CoETX4hGdKnHy7hRvYR2psATapfVts1Ouj9vqu
+0zDpBATJhkom5xgTjWkT1ZgVIEbVHZiNIpSgA2OI4FqpL5rDw7uvMmttyR855s3/
+ufyhAjIXJMC6/8/7JG7Cu4d2pY/tumoeLjks69hUlqsM4RptZij/sC2m0BH5JOY+
+rj8YKGlliBciUbSkoTjOTExQoipLjpwgADmKu85TAL0X0PIqvM23n4K1IjiZjmNl
+9vjOwdtugOH7AYJV3RNjGLRxy4gJP+jlXL7rWEFFvL2WxSRuy1EqMRNzDlx/5xM3
+1PJsmcc6wIhyLDq40m0gdyh43Lk6EeaLjf6+QJrn2+AwTGAc3k0KOu50hLnSHPKZ
+0dYfhqD6iJOkByAc+usyfHNQ2+IQWy/F+AQc+ST0p/A+xiC3D7OHbaZJM+Mmqepc
+aUIt5jJ2IylxPet7yZBfV8f+6NUGGbNJy0Xd6qv2EE3osBMd0XyaEHPSxnSvGJfx
+a7KJQLOr/WpfSJeZglW3fQPWwhAjeEFFBibwso/D6vXxK6x/N8axUyRiJHOmLKNp
+UFEhZpET3FoAMnC3vxYynv3ooYw3oLxl1V2TVHN4s4zlDS7dkAokPX63uQINBFob
+JskBEAC9bcDtSKWB85zmXbIztVQF/73mSJQBZiPfNpQqTiClsQ56qMHIUsqLw2qG
+cgDj2cv8U5NPxoLQc2w6HMqcD9ASmSa6DePUPpADp7HVPZB4GnBcSu4IEjO6dlif
+rH098eBoEIZzU4ghvpDzIBmfBQ5pveUGqvqt/2e2xtJug0FmpuWXYlQlV9Sj4Xar
+s3sPhLekXaRZ7fDULnS14DZRuBMdRNwyhOPz5xFCK1JiahfZ6pALS9xvWyaD1Wa0
+/IhJzIA3vDGR96KJVX/EtnggWuC9csoq8QoIqwxbcbKwlceE5EGSJTpceB63z3s+
+nM2OECGlQlg1oktfLdw37QFyh5uHqEi9kJVconb1Z0vt0WtZmarzYRIJDwoIE2aC
+EM1bmXijQIl/W8elcLDCL7o4m9v2fdYTk+xqJ5x165E6N4xKKL+B5zKTcOocg2rr
+s1hFV/LIRUl/rYB+58WTzvorym14ZdcLiu2/xWa4M4Qc7sIu8Hk69g+zKTS22eRD
+Mo0q96jNGfa/5Qu20Iz8eKK4lDsGpbbRnA7+U1ayxzTV36fxI0L5Ru7spq0rHJ3h
+c88v5IG9RCyxJIug0ZbLX4+P/M0yKNDj73o1nbL81TI1tPsuUFsygN2PN+RowoVN
+vmDoXlKWbT4eMfMiCbw/PCm3ZEVz/m9M3VjoRrb1T6S7DalqXQARAQABiQI8BBgB
+CAAmAhsgFiEEPREGPBD5jRS9JNFHCwmY74b1m2oFAlyTsQoFCQlobNEACgkQCwmY
+74b1m2qDBhAAnIyHlZGTgbiVTVBgjrIEYasPWn+59I/zULVGGe0yEvHzUoAeWoKE
+MudtfIUMb6Ypcoxwo8AHVvSsCSuLWiMDysu6Y3+p9B/iNDVlCU/3eA/BjCpD5ofU
+482Dyv5hpqdfv8nLehBjSnlfLnIf9b4kIAuTI1hM2kQFkM3/Eh4mfB2XJBFQxzS3
+gedWLrZoUp3qUp/BOkIroRPeu2N96d+6a9b35S17GJxWehgVjEwLZyhKCHliOYTk
+k4ibMc964iDSIdjpTAszHj/dMkt82Ovv2Q7IpFB6dhd6Mb3Les02f6lNyTBixud6
+/1ADj4LzyUwYyrlF8Mhjg/vJn++gAPFRqSrY5pwwsqci4Wr1/mgrM9WQd1wnkGZp
+0eM2q598b9fBgNvDnk5N8rCLqxRaxfUrvVEnCb5KbWtAwzp6GJ447KGHQRpfGN2B
+yXXtekurH2tuixSWSVnCwN7oN5hqXxhA60puyVSQlRZ5oqq/DTY5Gl+8HO/6qjaa
+iRD6frB32eB3/eIUHE+HhqMkVKcvoz1PUdjDO+YArRdkdREpQ7OBgqdI5/WkmDez
+DZ8s/8LH7NmWyaDiYmQwZzDJw/286pTn+U0JvAvMU98tSQKD163iYcUprdkMEgWB
+bm9msTujYyUbqJg/epAVjJahjtYwnCFhuJKvoIAlOXAqNksqPDoPwfU=
+=tbKl
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/debian/watch b/debian/watch
new file mode 100644
index 0000000..10ca7c1
--- /dev/null
+++ b/debian/watch
@@ -0,0 +1,3 @@
+version=4
+opts="pgpmode=auto, uversionmangle=s/pre/~pre/;s/rc/~rc/" \
+ https://ftp.gnu.org/gnu/@PACKAGE@/@PACKAGE@@ANY_VERSION@@ARCHIVE_EXT@
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644
index 0000000..f7b3a16
--- /dev/null
+++ b/doc/.gitignore
@@ -0,0 +1,29 @@
+*.aux
+*.dvi
+*.log
+*.out
+*.snm
+*.toc
+*.vrb
+*.nav
+*/auto
+version.texi
+texinfo.tex
+stamp-vti
+taler-exchange.info
+mdate-sh
+taler-exchange.vr
+taler-exchange.tp
+taler-exchange.toc
+taler-exchange.pg
+taler-exchange.pdf
+taler-exchange.log
+taler-exchange.ky
+taler-exchange.info
+taler-exchange.html
+taler-exchange.fn
+taler-exchange.cp
+taler-exchange.auxtaler-exchange.cps
+cbdc-es/cbdc-es.pdf
+cbdc-it/cbdc-it.pdf
+audit/response-202109.pdf
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644
index 0000000..b1dd349
--- /dev/null
+++ b/doc/Makefile.am
@@ -0,0 +1,74 @@
+# This Makefile.am is in the public domain
+
+SUBDIRS = . doxygen
+
+AM_MAKEINFOHTMLFLAGS = $(TEXINFO_HTMLFLAGS)
+
+infoimagedir = $(infodir)/images
+
+man_MANS = \
+  prebuilt/man/taler.conf.5            \
+  prebuilt/man/taler-config.1            \
+  prebuilt/man/taler-aggregator-benchmark.1   \
+  prebuilt/man/taler-auditor.1             \
+  prebuilt/man/taler-auditor-dbinit.1      \
+  prebuilt/man/taler-auditor-exchange.1     \
+  prebuilt/man/taler-auditor-httpd.1       \
+  prebuilt/man/taler-auditor-offline.1    \
+  prebuilt/man/taler-auditor-sign.1       \
+  prebuilt/man/taler-auditor-sync.1       \
+  prebuilt/man/taler-bank-benchmark.1   \
+  prebuilt/man/taler-bank-transfer.1   \
+  prebuilt/man/taler-exchange-aggregator.1 \
+  prebuilt/man/taler-exchange-benchmark.1  \
+  prebuilt/man/taler-exchange-closer.1     \
+  prebuilt/man/taler-exchange-dbconfig.1  \
+  prebuilt/man/taler-exchange-dbinit.1  \
+  prebuilt/man/taler-exchange-drain.1  \
+  prebuilt/man/taler-exchange-expire.1  \
+  prebuilt/man/taler-exchange-httpd.1       \
+  prebuilt/man/taler-exchange-kyc-aml-pep-trigger.1 \
+  prebuilt/man/taler-exchange-kyc-tester.1       \
+  prebuilt/man/taler-exchange-offline.1    \
+  prebuilt/man/taler-exchange-router.1\
+  prebuilt/man/taler-exchange-secmod-cs.1\
+  prebuilt/man/taler-exchange-secmod-eddsa.1\
+  prebuilt/man/taler-exchange-secmod-rsa.1 \
+  prebuilt/man/taler-exchange-transfer.1\
+  prebuilt/man/taler-exchange-wire-gateway-client.1\
+  prebuilt/man/taler-exchange-wirewatch.1   \
+  prebuilt/man/taler-helper-auditor-aggregation.1 \
+  prebuilt/man/taler-helper-auditor-coins.1\
+  prebuilt/man/taler-helper-auditor-deposits.1\
+  prebuilt/man/taler-helper-auditor-purses.1\
+  prebuilt/man/taler-helper-auditor-reserves.1\
+  prebuilt/man/taler-helper-auditor-wire.1 \
+  prebuilt/man/taler-terms-generator.1 \
+  prebuilt/man/taler-unified-setup.1
+
+info_TEXINFOS = \
+  prebuilt/texinfo/taler-auditor.texi \
+  prebuilt/texinfo/taler-bank.texi \
+  prebuilt/texinfo/taler-developer-manual.texi \
+  prebuilt/texinfo/taler-exchange.texi
+
+
+EXTRA_DIST = \
+  $(man_MANS) \
+  doxygen/taler.doxy \
+  $(info_TEXINFOS) \
+  prebuilt/texinfo/taler-auditor-figures/auditor-db.png \
+  prebuilt/texinfo/taler-auditor-figures/replication.png  \
+  prebuilt/texinfo/taler-bank-figures/arch-api.png  \
+  prebuilt/texinfo/taler-bank-figures/auditor-db.png       \
+  prebuilt/texinfo/taler-bank-figures/exchange-db.png \
+  prebuilt/texinfo/taler-bank-figures/merchant-db.png \
+  prebuilt/texinfo/taler-bank-figures/replication.png \
+  prebuilt/texinfo/taler-developer-manual-figures/arch-api.png \
+  prebuilt/texinfo/taler-developer-manual-figures/auditor-db.png \
+  prebuilt/texinfo/taler-developer-manual-figures/exchange-db.png \
+  prebuilt/texinfo/taler-developer-manual-figures/merchant-db.png \
+  prebuilt/texinfo/taler-developer-manual-figures/replication.png \
+  prebuilt/texinfo/taler-exchange-figures/auditor-db.png \
+  prebuilt/texinfo/taler-exchange-figures/exchange-db.png\
+  prebuilt/texinfo/taler-exchange-figures/replication.png
diff --git a/doc/doxygen/.gitignore b/doc/doxygen/.gitignore
new file mode 100644
index 0000000..145a3fd
--- /dev/null
+++ b/doc/doxygen/.gitignore
@@ -0,0 +1,2 @@
+html/
+taler-exchange.tag
diff --git a/doc/doxygen/Makefile.am b/doc/doxygen/Makefile.am
new file mode 100644
index 0000000..cde28de
--- /dev/null
+++ b/doc/doxygen/Makefile.am
@@ -0,0 +1,18 @@
+# This Makefile.am is in the public domain
+all:
+       @echo -e \
+"Generate documentation:\n" \
+"\tmake full - full documentation with dependency graphs (slow)\n" \
+"\tmake fast - fast mode without dependency graphs"
+
+full: taler.doxy
+       doxygen $<
+
+fast: taler.doxy
+       sed 's/\(HAVE_DOT.*=\).*/\1 NO/' $< | doxygen -
+
+clean:
+       rm -rf html
+
+EXTRA_DIST = \
+   taler.doxy
diff --git a/doc/doxygen/Makefile.in b/doc/doxygen/Makefile.in
new file mode 100644
index 0000000..582dba3
--- /dev/null
+++ b/doc/doxygen/Makefile.in
@@ -0,0 +1,550 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \  ]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs  ]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+subdir = doc/doxygen
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_compare_version.m4 \
+       $(top_srcdir)/m4/ax_have_epoll.m4 \
+       $(top_srcdir)/m4/ax_lib_postgresql.m4 \
+       $(top_srcdir)/m4/ax_prog_doxygen.m4 \
+       $(top_srcdir)/m4/gettext.m4 $(top_srcdir)/m4/iconv.m4 \
+       $(top_srcdir)/m4/intlmacosx.m4 $(top_srcdir)/m4/lib-ld.m4 \
+       $(top_srcdir)/m4/lib-link.m4 $(top_srcdir)/m4/lib-prefix.m4 \
+       $(top_srcdir)/m4/libcurl.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+       $(top_srcdir)/m4/libgnurl.m4 $(top_srcdir)/m4/libtool.m4 \
+       $(top_srcdir)/m4/longlong.m4 $(top_srcdir)/m4/ltoptions.m4 \
+       $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+       $(top_srcdir)/m4/lt~obsolete.m4 \
+       $(top_srcdir)/m4/m4_ax_python_module.m4 \
+       $(top_srcdir)/m4/mhd.m4 $(top_srcdir)/m4/nls.m4 \
+       $(top_srcdir)/m4/po.m4 $(top_srcdir)/m4/progtest.m4 \
+       $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+       $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/taler_config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+SOURCES =
+DIST_SOURCES =
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+am__DIST_COMMON = $(srcdir)/Makefile.in
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DOXYGEN_PAPER_SIZE = @DOXYGEN_PAPER_SIZE@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+DX_CONFIG = @DX_CONFIG@
+DX_CONFIG2 = @DX_CONFIG2@
+DX_CONFIG3 = @DX_CONFIG3@
+DX_CONFIG4 = @DX_CONFIG4@
+DX_DOCDIR = @DX_DOCDIR@
+DX_DOCDIR2 = @DX_DOCDIR2@
+DX_DOCDIR3 = @DX_DOCDIR3@
+DX_DOCDIR4 = @DX_DOCDIR4@
+DX_DOT = @DX_DOT@
+DX_DOXYGEN = @DX_DOXYGEN@
+DX_DVIPS = @DX_DVIPS@
+DX_EGREP = @DX_EGREP@
+DX_ENV = @DX_ENV@
+DX_FLAG_chi = @DX_FLAG_chi@
+DX_FLAG_chm = @DX_FLAG_chm@
+DX_FLAG_doc = @DX_FLAG_doc@
+DX_FLAG_dot = @DX_FLAG_dot@
+DX_FLAG_html = @DX_FLAG_html@
+DX_FLAG_man = @DX_FLAG_man@
+DX_FLAG_pdf = @DX_FLAG_pdf@
+DX_FLAG_ps = @DX_FLAG_ps@
+DX_FLAG_rtf = @DX_FLAG_rtf@
+DX_FLAG_xml = @DX_FLAG_xml@
+DX_HHC = @DX_HHC@
+DX_LATEX = @DX_LATEX@
+DX_MAKEINDEX = @DX_MAKEINDEX@
+DX_PDFLATEX = @DX_PDFLATEX@
+DX_PERL = @DX_PERL@
+DX_PROJECT = @DX_PROJECT@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@
+GMSGFMT = @GMSGFMT@
+GMSGFMT_015 = @GMSGFMT_015@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+INTLLIBS = @INTLLIBS@
+INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@
+JQ = @JQ@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBCURL = @LIBCURL@
+LIBCURL_CPPFLAGS = @LIBCURL_CPPFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBGNURL = @LIBGNURL@
+LIBGNURLCURL_LIBS = @LIBGNURLCURL_LIBS@
+LIBGNURL_CPPFLAGS = @LIBGNURL_CPPFLAGS@
+LIBICONV = @LIBICONV@
+LIBINTL = @LIBINTL@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBICONV = @LTLIBICONV@
+LTLIBINTL = @LTLIBINTL@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MSGFMT = @MSGFMT@
+MSGFMT_015 = @MSGFMT_015@
+MSGMERGE = @MSGMERGE@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJC = @OBJC@
+OBJCDEPMODE = @OBJCDEPMODE@
+OBJCFLAGS = @OBJCFLAGS@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+POSTGRESQL_CPPFLAGS = @POSTGRESQL_CPPFLAGS@
+POSTGRESQL_LDFLAGS = @POSTGRESQL_LDFLAGS@
+POSTGRESQL_LIBFLAGS = @POSTGRESQL_LIBFLAGS@
+POSTGRESQL_LIBS = @POSTGRESQL_LIBS@
+POSTGRESQL_VERSION = @POSTGRESQL_VERSION@
+POSUB = @POSUB@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SQLITE_CPPFLAGS = @SQLITE_CPPFLAGS@
+SQLITE_LDFLAGS = @SQLITE_LDFLAGS@
+STRIP = @STRIP@
+TALER_LIB_LDFLAGS = @TALER_LIB_LDFLAGS@
+TALER_PLUGIN_LDFLAGS = @TALER_PLUGIN_LDFLAGS@
+USE_NLS = @USE_NLS@
+VERSION = @VERSION@
+XGETTEXT = @XGETTEXT@
+XGETTEXT_015 = @XGETTEXT_015@
+XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@
+_libcurl_config = @_libcurl_config@
+_libgnurl_config = @_libgnurl_config@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+ac_ct_OBJC = @ac_ct_OBJC@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+gitcommand = @gitcommand@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+EXTRA_DIST = \
+   taler.doxy
+
+all: all-am
+
+.SUFFIXES:
+$(srcdir)/Makefile.in:  $(srcdir)/Makefile.am  $(am__configure_deps)
+       @for dep in $?; do \
+         case '$(am__configure_deps)' in \
+           *$$dep*) \
+             ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+               && { if test -f $@; then exit 0; else break; fi; }; \
+             exit 1;; \
+         esac; \
+       done; \
+       echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu doc/doxygen/Makefile'; \
+       $(am__cd) $(top_srcdir) && \
+         $(AUTOMAKE) --gnu doc/doxygen/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+       @case '$?' in \
+         *config.status*) \
+           cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+         *) \
+           echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ 
$(am__maybe_remake_depfiles)'; \
+           cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ 
$(am__maybe_remake_depfiles);; \
+       esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure 
$(CONFIG_STATUS_DEPENDENCIES)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure:  $(am__configure_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4):  $(am__aclocal_m4_deps)
+       cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+
+mostlyclean-libtool:
+       -rm -f *.lo
+
+clean-libtool:
+       -rm -rf .libs _libs
+tags TAGS:
+
+ctags CTAGS:
+
+cscope cscopelist:
+
+distdir: $(BUILT_SOURCES)
+       $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+       @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+       list='$(DISTFILES)'; \
+         dist_files=`for file in $$list; do echo $$file; done | \
+         sed -e "s|^$$srcdirstrip/||;t" \
+             -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+       case $$dist_files in \
+         */*) $(MKDIR_P) `echo "$$dist_files" | \
+                          sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+                          sort -u` ;; \
+       esac; \
+       for file in $$dist_files; do \
+         if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+         if test -d $$d/$$file; then \
+           dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+           if test -d "$(distdir)/$$file"; then \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx 
{} \;; \
+           fi; \
+           if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+             cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+             find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx 
{} \;; \
+           fi; \
+           cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+         else \
+           test -f "$(distdir)/$$file" \
+           || cp -p $$d/$$file "$(distdir)/$$file" \
+           || exit 1; \
+         fi; \
+       done
+check-am: all-am
+check: check-am
+all-am: Makefile
+installdirs:
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+       @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+       if test -z '$(STRIP)'; then \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s 
\
+             install; \
+       else \
+         $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+           install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s 
\
+           "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+       fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+       -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+       -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f 
$(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+       @echo "This command is intended for maintainers to use"
+       @echo "it deletes files that may require special tools to rebuild."
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+       -rm -f Makefile
+distclean-am: clean-am distclean-generic
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+       -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am:
+
+.MAKE: install-am install-strip
+
+.PHONY: all all-am check check-am clean clean-generic clean-libtool \
+       cscopelist-am ctags-am distclean distclean-generic \
+       distclean-libtool distdir dvi dvi-am html html-am info info-am \
+       install install-am install-data install-data-am install-dvi \
+       install-dvi-am install-exec install-exec-am install-html \
+       install-html-am install-info install-info-am install-man \
+       install-pdf install-pdf-am install-ps install-ps-am \
+       install-strip installcheck installcheck-am installdirs \
+       maintainer-clean maintainer-clean-generic mostlyclean \
+       mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+       tags-am uninstall uninstall-am
+
+.PRECIOUS: Makefile
+
+
+# This Makefile.am is in the public domain
+all:
+       @echo -e \
+"Generate documentation:\n" \
+"\tmake full - full documentation with dependency graphs (slow)\n" \
+"\tmake fast - fast mode without dependency graphs"
+
+full: taler.doxy
+       doxygen $<
+
+fast: taler.doxy
+       sed 's/\(HAVE_DOT.*=\).*/\1 NO/' $< | doxygen -
+
+clean:
+       rm -rf html
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/doc/doxygen/logo.svg b/doc/doxygen/logo.svg
new file mode 100644
index 0000000..ddb8425
--- /dev/null
+++ b/doc/doxygen/logo.svg
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd";
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape";
+   viewBox="0 0 180 40"
+   version="1.1"
+   id="svg14"
+   sodipodi:docname="logo-2018-dold.svg"
+   inkscape:version="0.92.2 2405546, 2018-03-11">
+  <metadata
+     id="metadata20">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs18" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="1916"
+     inkscape:window-height="1041"
+     id="namedview16"
+     showgrid="false"
+     inkscape:zoom="1.8833333"
+     inkscape:cx="91.061947"
+     inkscape:cy="20"
+     inkscape:window-x="0"
+     inkscape:window-y="18"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="text12" />
+  <style
+     id="style2">
+    .ts1 { fill: #aa3939; letter-spacing:0; }
+    .ts2 { letter-spacing:0; }
+  </style>
+  <g
+     aria-label="❬Taler❭"
+     style="color:#ff0000;font-weight:bold;font-size:36px;font-family:'Lucida 
Console', Monaco, monospace;letter-spacing:0.2em"
+     id="text12">
+    <path
+       d="M 15.978516,31.285156 H 12.234375 L 5.6953125,18.154297 
12.234375,5.0058594 h 3.744141 L 9.4042969,18.154297 Z"
+       style="letter-spacing:0;fill:#aa3939"
+       id="path3725" />
+    <path
+       d="M 35.085937,29 H 29.900391 V 7.2910156 h -6.66211 V 2.7558594 h 
18.509766 v 4.5351562 h -6.66211 z"
+       style=""
+       id="path3727" />
+    <path
+       d="m 62.817188,19.753906 q -2.882812,0 -4.02539,0.738281 
-1.142578,0.738282 -1.142578,2.53125 0,1.335938 0.791015,2.126954 
0.791016,0.791015 2.144531,0.791015 2.039063,0 3.164063,-1.529297 
1.125,-1.546875 1.125,-4.30664 v -0.351563 z m 7.171875,-1.986328 V 29 h 
-5.115234 v -2.197266 q -0.931641,1.300782 -2.390625,2.003907 
-1.458984,0.703125 -3.216797,0.703125 -3.357422,0 -5.238281,-1.775391 
-1.863281,-1.775391 -1.863281,-4.957031 0,-3.445313 2.232421,-5.080078 
2.232422,-1.652344 6 [...]
+       style=""
+       id="path3729" />
+    <path
+       d="M 86.171486,20.791016 V 5.6035156 H 80.950783 V 1.6484375 H 
91.321877 V 20.791016 q 0,2.320312 0.720703,3.287109 0.720703,0.966797 
2.443359,0.966797 H 98.59922 V 29 h -5.554687 q -3.673828,0 -5.273438,-1.898438 
-1.599609,-1.898437 -1.599609,-6.310546 z"
+       style=""
+       id="path3731" />
+    <path
+       d="m 127.59609,28.033203 q -1.79297,0.738281 -3.65625,1.107422 
-1.86328,0.369141 -3.9375,0.369141 -4.93945,0 -7.55859,-2.636719 
-2.60156,-2.654297 -2.60156,-7.628906 0,-4.816407 2.51367,-7.611328 
2.51367,-2.7949224 6.85547,-2.7949224 4.37695,0 6.78515,2.6015624 
2.42578,2.583985 2.42578,7.294922 v 2.091797 h -13.34179 q 0.0176,2.320312 
1.37109,3.46289 1.35352,1.142579 4.04297,1.142579 1.77539,0 3.49805,-0.509766 
1.72265,-0.509766 3.60351,-1.617188 z m -4.35937,-11.074219 q -0.0352, [...]
+       style=""
+       id="path3733" />
+    <path
+       d="m 157.31367,14.744141 q -0.84375,-0.773438 -1.98632,-1.160157 
-1.125,-0.386718 -2.47852,-0.386718 -1.63476,0 -2.86523,0.580078 
-1.21289,0.5625 -1.88086,1.652344 -0.42188,0.667968 -0.59766,1.617187 
-0.1582,0.949219 -0.1582,2.882812 V 29 h -5.15039 V 9.3125 h 5.15039 v 3.058594 
q 0.75586,-1.6875 2.32031,-2.6015627 1.56445,-0.9316407 3.65625,-0.9316407 
1.05469,0 2.05664,0.2636719 1.01953,0.2460938 1.93359,0.7382813 z"
+       style="letter-spacing:0"
+       id="path3735" />
+    <path
+       d="m 164.43282,31.285156 6.55664,-13.130859 -6.53907,-13.1484376 h 
3.72657 l 6.53906,13.1484376 -6.53906,13.130859 z"
+       style="letter-spacing:0;fill:#aa3939"
+       id="path3737" />
+  </g>
+</svg>
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
new file mode 100644
index 0000000..a9a2bfe
--- /dev/null
+++ b/doc/doxygen/taler.doxy
@@ -0,0 +1,2699 @@
+# Doxyfile 1.9.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables:
+# doxygen -x_noenv [configFile]
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for 
all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = "GNU Taler: Exchange"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = 0.9.3
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO           = logo.svg
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = .
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output 
format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, 
where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = YES
+
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and 
every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+# CREATE_SUBDIRS_LEVEL   = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, 
English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file 
list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = ../..
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name 
of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    = ../../src/include \
+                         src/include \
+                         include
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular 
Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER         = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING       = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits 
the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a 
new
+# page for each member. If set to NO, the documentation of a member will be 
part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value 
part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for 
that
+# language. For instance, namespaces will be presented as modules, types will 
be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE  = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, 
and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc 
files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should 
add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING      =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all 
comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS   = 5
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set 
this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and 
collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public 
instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will 
make
+# doxygen to replace the get and set methods by a property in the 
documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If one adds a struct or class to a group and this option is enabled, then 
also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
+GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and 
unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and 
unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML 
and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically 
be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can 
be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too 
small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid 
range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to 
use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS       = 1
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL   = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or 
internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface 
are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name 
of
+# the file that contains the anonymous namespace. By default anonymous 
namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = YES
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = YES
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, 
the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
+HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+# SHOW_HEADERFILE        = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp 
brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = NO
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in 
the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting 
brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the 
hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = YES
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list 
will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match 
between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the 
todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the 
test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in 
the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. 
This
+# will remove the Files entry from the Quick Index and from the Folder Tree 
View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically 
from
+# the version control system). Doxygen will invoke the program by executing 
(via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the 
file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the 
LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files 
containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in 
the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this 
flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings 
for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+# WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions 
that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = YES
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop 
when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# Possible values are: NO, YES and FAIL_ON_WARNINGS.
+# The default value is: NO.
+
+WARN_AS_ERROR          = FAIL_ON_WARNINGS
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
doxygen
+# can produce. The string should contain the $file, $line, and $text tags, 
which
+# will be replaced by the file and line number from which the warning 
originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
+
+# WARN_FORMAT            = "$file:$line: $text"
+
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT       = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = ../../src
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
+# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
+# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
+# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
+# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
+# *.vhdl, *.ucf, *.qsf and *.ice.
+
+FILE_PATTERNS          = *.c \
+                         *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT 
tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so 
to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = */test_* \
+                         */.git/* \
+                         */perf_* \
+                         .* \
+                         .* \
+                         */gnu-taler-error-codes/* \
+                         */src/templating/mustach*
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# ANamespace::AClass, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so 
to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude 
commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are 
added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: 
pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to 
YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE =
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = YES
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ 
and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented 
function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they 
will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as 
prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS    = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with 
command
+# line options that you would normally use when invoking the compiler. Note 
that
+# the include paths will already be set by doxygen for the files and 
directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          =
+
+# If clang assisted parsing is enabled you can provide the clang parser with 
the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be 
ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          = TALER_
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front 
of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file 
for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used 
(e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a 
description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for 
each
+# generated HTML page. If the tag is left blank doxygen will generate a 
standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the 
last
+# style sheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that 
the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the 
value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 
represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated 
HTML
+# page will contain the date and time when the page was generated. Setting this
+# to YES can help to show when doxygen was last run and thus if the
+# documentation is up to date.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index 
will
+# consists of multiple levels of tabs that are statically embedded in every 
HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated 
development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it 
at
+# startup. See 
https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed 
provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "GNU Taler Source Documentation"
+
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+# DOCSET_FEEDURL         =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = net.taler.exchange
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = net.taler
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = "Taler Systems SA"
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help 
Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many 
years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# 
http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / 
Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / 
Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files 
needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at 
top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the 
navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to 
YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one 
can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the 
PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = NONE
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the 
treeview
+# area (value NO) or if it should extend to the full height of the window 
(value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for 
the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# FULL_SIDEBAR           = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values 
that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from 
appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# OBFUSCATE_EMAILS       = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled 
resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT    = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files 
in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand 
commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE      =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the 
path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+# USE_MATHJAX            = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be 
used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION        = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml 
(This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        =
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax 
site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to 
cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. 
There
+# are two flavors of web server based searching depending on the 
EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the 
indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain 
the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If 
a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front 
of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first 
character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD    = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = YES
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 
x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified 
just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. 
It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. 
See
+# LATEX_HEADER for more information on how to generate a default footer and 
what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the 
last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the 
LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
running
+# if errors occur, instead of asking the user for help.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_TIMESTAMP        = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY  =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other 
RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front 
of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front 
of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the 
number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but 
without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front 
of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook 
files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be 
put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl 
module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and 
DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be 
nicely
+# formatted so it can be parsed by a human reader. This is useful if you want 
to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED 
and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = GNUNET_UNUSED= \
+                         GNUNET_PACKED=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the 
PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor 
will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format 
of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the 
use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               = ../../contrib/gnunet.tag \
+                         ../../contrib/microhttpd.tag
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       = taler-exchange.tag
+
+# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
+# the class index. If set to NO, only the inherited external classes will be
+# listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary 
resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is 
allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
+# graph for each documented class showing the direct and indirect inheritance
+# relations. In case HAVE_DOT is set as well dot will be used to draw the 
graph,
+# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
+# to TEXT the direct and indirect inheritance relations will be shown as texts 
/
+# links.
+# Possible values are: NO, YES, TEXT and GRAPH.
+# The default value is: YES.
+
+CLASS_GRAPH            = NO
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect 
implementation
+# dependencies (inheritance, containment, and class references variables) of 
the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = NO
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. See also the chapter Grouping
+# in the manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = NO
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS        = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of 
characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are 
apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD     = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set 
to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file 
showing
+# the direct and indirect include dependencies of the file with other 
documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = YES
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can 
be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = YES
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = NO
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+# DIR_GRAPH_MAX_DEPTH    = 1
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# http://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
+# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
+# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = svg
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called 
during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH      =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE      =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH  =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of 
direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 100
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 
1
+# or 2 may greatly reduce the computation time needed for large code bases. 
Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 2
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = YES
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
+DOT_CLEANUP            = YES
diff --git a/m4/.gitignore b/m4/.gitignore
new file mode 100644
index 0000000..1f12f43
--- /dev/null
+++ b/m4/.gitignore
@@ -0,0 +1,6 @@
+# These are added by "autoreconf -if"
+libtool.m4
+ltoptions.m4
+ltsugar.m4
+ltversion.m4
+lt~obsolete.m4
diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4
new file mode 100644
index 0000000..ffb4997
--- /dev/null
+++ b/m4/ax_compare_version.m4
@@ -0,0 +1,177 @@
+# ===========================================================================
+#    https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], 
[ACTION-IF-FALSE])
+#
+# DESCRIPTION
+#
+#   This macro compares two version strings. Due to the various number of
+#   minor-version numbers that can exist, and the fact that string
+#   comparisons are not compatible with numeric comparisons, this is not
+#   necessarily trivial to do in a autoconf script. This macro makes doing
+#   these comparisons easy.
+#
+#   The six basic comparisons are available, as well as checking equality
+#   limited to a certain number of minor-version levels.
+#
+#   The operator OP determines what type of comparison to do, and can be one
+#   of:
+#
+#    eq  - equal (test A == B)
+#    ne  - not equal (test A != B)
+#    le  - less than or equal (test A <= B)
+#    ge  - greater than or equal (test A >= B)
+#    lt  - less than (test A < B)
+#    gt  - greater than (test A > B)
+#
+#   Additionally, the eq and ne operator can have a number after it to limit
+#   the test to that number of minor versions.
+#
+#    eq0 - equal up to the length of the shorter version
+#    ne0 - not equal up to the length of the shorter version
+#    eqN - equal up to N sub-version levels
+#    neN - not equal up to N sub-version levels
+#
+#   When the condition is true, shell commands ACTION-IF-TRUE are run,
+#   otherwise shell commands ACTION-IF-FALSE are run. The environment
+#   variable 'ax_compare_version' is always set to either 'true' or 'false'
+#   as well.
+#
+#   Examples:
+#
+#     AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
+#     AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
+#
+#   would both be true.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
+#     AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
+#
+#   would both be false.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
+#
+#   would be true because it is only comparing two minor versions.
+#
+#     AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
+#
+#   would be true because it is only comparing the lesser number of minor
+#   versions of the two values.
+#
+#   Note: The characters that separate the version numbers do not matter. An
+#   empty string is the same as version 0. OP is evaluated by autoconf, not
+#   configure, so must be a string, not a variable.
+#
+#   The author would like to acknowledge Guido Draheim whose advice about
+#   the m4_case and m4_ifvaln functions make this macro only include the
+#   portions necessary to perform the specific comparison specified by the
+#   OP argument in the final configure script.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Tim Toolan <toolan@ele.uri.edu>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 13
+
+dnl #########################################################################
+AC_DEFUN([AX_COMPARE_VERSION], [
+  AC_REQUIRE([AC_PROG_AWK])
+
+  # Used to indicate true or false condition
+  ax_compare_version=false
+
+  # Convert the two version strings to be compared into a format that
+  # allows a simple string comparison.  The end result is that a version
+  # string of the form 1.12.5-r617 will be converted to the form
+  # 0001001200050617.  In other words, each number is zero padded to four
+  # digits, and non digits are removed.
+  AS_VAR_PUSHDEF([A],[ax_compare_version_A])
+  A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+                     -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/[[^0-9]]//g'`
+
+  AS_VAR_PUSHDEF([B],[ax_compare_version_B])
+  B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
+                     -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
+                     -e 's/[[^0-9]]//g'`
+
+  dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
+  dnl # then the first line is used to determine if the condition is true.
+  dnl # The sed right after the echo is to remove any indented white space.
+  m4_case(m4_tolower($2),
+  [lt],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+  ],
+  [gt],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
+  ],
+  [le],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+  ],
+  [ge],[
+    ax_compare_version=`echo "x$A
+x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
+  ],[
+    dnl Split the operator from the subversion count if present.
+    m4_bmatch(m4_substr($2,2),
+    [0],[
+      # A count of zero means use the length of the shorter version.
+      # Determine the number of characters in A and B.
+      ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
+      ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
+
+      # Set A to no more than B's length and B to no more than A's length.
+      A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
+      B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
+    ],
+    [[0-9]+],[
+      # A count greater than zero means use only that many subversions
+      A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+      B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
+    ],
+    [.+],[
+      AC_WARNING(
+        [invalid OP numeric parameter: $2])
+    ],[])
+
+    # Pad zeros at end of numbers to make same length.
+    ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
+    B="$B`echo $A | sed 's/./0/g'`"
+    A="$ax_compare_version_tmp_A"
+
+    # Check for equality or inequality as necessary.
+    m4_case(m4_tolower(m4_substr($2,0,2)),
+    [eq],[
+      test "x$A" = "x$B" && ax_compare_version=true
+    ],
+    [ne],[
+      test "x$A" != "x$B" && ax_compare_version=true
+    ],[
+      AC_WARNING([invalid OP parameter: $2])
+    ])
+  ])
+
+  AS_VAR_POPDEF([A])dnl
+  AS_VAR_POPDEF([B])dnl
+
+  dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
+  if test "$ax_compare_version" = "true" ; then
+    m4_ifvaln([$4],[$4],[:])dnl
+    m4_ifvaln([$5],[else $5])dnl
+  fi
+]) dnl AX_COMPARE_VERSION
diff --git a/m4/ax_have_epoll.m4 b/m4/ax_have_epoll.m4
new file mode 100644
index 0000000..9d9bc87
--- /dev/null
+++ b/m4/ax_have_epoll.m4
@@ -0,0 +1,104 @@
+# ===========================================================================
+#      https://www.gnu.org/software/autoconf-archive/ax_have_epoll.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_HAVE_EPOLL([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#   AX_HAVE_EPOLL_PWAIT([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+#
+# DESCRIPTION
+#
+#   This macro determines whether the system supports the epoll I/O event
+#   interface. A neat usage example would be:
+#
+#     AX_HAVE_EPOLL(
+#       [AX_CONFIG_FEATURE_ENABLE(epoll)],
+#       [AX_CONFIG_FEATURE_DISABLE(epoll)])
+#     AX_CONFIG_FEATURE(
+#       [epoll], [This platform supports epoll(7)],
+#       [HAVE_EPOLL], [This platform supports epoll(7).])
+#
+#   The epoll interface was added to the Linux kernel in version 2.5.45, and
+#   the macro verifies that a kernel newer than this is installed. This
+#   check is somewhat unreliable if <linux/version.h> doesn't match the
+#   running kernel, but it is necessary regardless, because glibc comes with
+#   stubs for the epoll_create(), epoll_wait(), etc. that allow programs to
+#   compile and link even if the kernel is too old; the problem would then
+#   be detected only at runtime.
+#
+#   Linux kernel version 2.6.19 adds the epoll_pwait() call in addition to
+#   epoll_wait(). The availability of that function can be tested with the
+#   second macro. Generally speaking, it is safe to assume that
+#   AX_HAVE_EPOLL would succeed if AX_HAVE_EPOLL_PWAIT has, but not the
+#   other way round.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Peter Simons <simons@cryp.to>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 11
+
+AC_DEFUN([AX_HAVE_EPOLL], [dnl
+  ax_have_epoll_cppflags="${CPPFLAGS}"
+  AC_CHECK_HEADER([linux/version.h], [CPPFLAGS="${CPPFLAGS} 
-DHAVE_LINUX_VERSION_H"])
+  AC_MSG_CHECKING([for Linux epoll(7) interface])
+  AC_CACHE_VAL([ax_cv_have_epoll], [dnl
+    AC_LINK_IFELSE([dnl
+      AC_LANG_PROGRAM([dnl
+#include <sys/epoll.h>
+#ifdef HAVE_LINUX_VERSION_H
+#  include <linux/version.h>
+#  if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,45)
+#    error linux kernel version is too old to have epoll
+#  endif
+#endif
+], [dnl
+int fd, rc;
+struct epoll_event ev;
+fd = epoll_create(128);
+rc = epoll_wait(fd, &ev, 1, 0);])],
+      [ax_cv_have_epoll=yes],
+      [ax_cv_have_epoll=no])])
+  CPPFLAGS="${ax_have_epoll_cppflags}"
+  AS_IF([test "${ax_cv_have_epoll}" = "yes"],
+    [AC_MSG_RESULT([yes])
+$1],[AC_MSG_RESULT([no])
+$2])
+])dnl
+
+AC_DEFUN([AX_HAVE_EPOLL_PWAIT], [dnl
+  ax_have_epoll_cppflags="${CPPFLAGS}"
+  AC_CHECK_HEADER([linux/version.h],
+    [CPPFLAGS="${CPPFLAGS} -DHAVE_LINUX_VERSION_H"])
+  AC_MSG_CHECKING([for Linux epoll(7) interface with signals extension])
+  AC_CACHE_VAL([ax_cv_have_epoll_pwait], [dnl
+    AC_LINK_IFELSE([dnl
+      AC_LANG_PROGRAM([dnl
+#ifdef HAVE_LINUX_VERSION_H
+#  include <linux/version.h>
+#  if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
+#    error linux kernel version is too old to have epoll_pwait
+#  endif
+#endif
+#include <sys/epoll.h>
+#include <signal.h>
+], [dnl
+int fd, rc;
+struct epoll_event ev;
+fd = epoll_create(128);
+rc = epoll_wait(fd, &ev, 1, 0);
+rc = epoll_pwait(fd, &ev, 1, 0, (sigset_t const *)(0));])],
+      [ax_cv_have_epoll_pwait=yes],
+      [ax_cv_have_epoll_pwait=no])])
+  CPPFLAGS="${ax_have_epoll_cppflags}"
+  AS_IF([test "${ax_cv_have_epoll_pwait}" = "yes"],
+    [AC_MSG_RESULT([yes])
+$1],[AC_MSG_RESULT([no])
+$2])
+])dnl
diff --git a/m4/ax_lib_postgresql.m4 b/m4/ax_lib_postgresql.m4
new file mode 100644
index 0000000..cc8e750
--- /dev/null
+++ b/m4/ax_lib_postgresql.m4
@@ -0,0 +1,247 @@
+# ===========================================================================
+#    https://www.gnu.org/software/autoconf-archive/ax_lib_postgresql.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   
AX_LIB_POSTGRESQL([MINIMUM-VERSION],[ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
+#
+# DESCRIPTION
+#
+#   This macro provides tests of availability of PostgreSQL 'libpq' library
+#   of particular version or newer.
+#
+#   AX_LIB_POSTGRESQL macro takes only one argument which is optional. If
+#   there is no required version passed, then macro does not run version
+#   test.
+#
+#   The --with-postgresql option takes one of three possible values:
+#
+#   no - do not check for PostgreSQL client library
+#
+#   yes - do check for PostgreSQL library in standard locations (pg_config
+#   should be in the PATH)
+#
+#   path - complete path to pg_config utility, use this option if pg_config
+#   can't be found in the PATH (You could set also PG_CONFIG variable)
+#
+#   This macro calls:
+#
+#     AC_SUBST(POSTGRESQL_CPPFLAGS)
+#     AC_SUBST(POSTGRESQL_LDFLAGS)
+#     AC_SUBST(POSTGRESQL_LIBS)
+#     AC_SUBST(POSTGRESQL_VERSION)
+#
+#   And sets:
+#
+#     HAVE_POSTGRESQL
+#
+#   It execute if found ACTION-IF-FOUND (empty by default) and
+#   ACTION-IF-NOT-FOUND (AC_MSG_FAILURE by default) if not found.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Mateusz Loskot <mateusz@loskot.net>
+#   Copyright (c) 2014 Sree Harsha Totakura <sreeharsha@totakura.in>
+#   Copyright (c) 2018 Bastien Roucaries <rouca@debian.org>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 22
+
+AC_DEFUN([_AX_LIB_POSTGRESQL_OLD],[
+       found_postgresql="no"
+       _AX_LIB_POSTGRESQL_OLD_fail="no"
+       while true; do
+         AC_CACHE_CHECK([for the pg_config program], [ac_cv_path_PG_CONFIG],
+           [AC_PATH_PROGS_FEATURE_CHECK([PG_CONFIG], [pg_config],
+             [[ac_cv_path_PG_CONFIG="";$ac_path_PG_CONFIG --includedir > 
/dev/null \
+               && ac_cv_path_PG_CONFIG=$ac_path_PG_CONFIG 
ac_path_PG_CONFIG_found=:]],
+             [ac_cv_path_PG_CONFIG=""])])
+         PG_CONFIG=$ac_cv_path_PG_CONFIG
+         AS_IF([test "X$PG_CONFIG" = "X"],[break])
+
+         AC_CACHE_CHECK([for the PostgreSQL libraries 
CPPFLAGS],[ac_cv_POSTGRESQL_CPPFLAGS],
+                      [ac_cv_POSTGRESQL_CPPFLAGS="-I`$PG_CONFIG --includedir`" 
|| _AX_LIB_POSTGRESQL_OLD_fail=yes])
+         AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+         POSTGRESQL_CPPFLAGS="$ac_cv_POSTGRESQL_CPPFLAGS"
+
+         AC_CACHE_CHECK([for the PostgreSQL libraries 
LDFLAGS],[ac_cv_POSTGRESQL_LDFLAGS],
+                      [ac_cv_POSTGRESQL_LDFLAGS="-L`$PG_CONFIG --libdir`" || 
_AX_LIB_POSTGRESQL_OLD_fail=yes])
+         AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+         POSTGRESQL_LDFLAGS="$ac_cv_POSTGRESQL_LDFLAGS"
+
+         AC_CACHE_CHECK([for the PostgreSQL libraries 
LIBS],[ac_cv_POSTGRESQL_LIBS],
+                      [ac_cv_POSTGRESQL_LIBS="-lpq"])
+         POSTGRESQL_LIBS="$ac_cv_POSTGRESQL_LIBS"
+
+         AC_CACHE_CHECK([for the PostgreSQL 
version],[ac_cv_POSTGRESQL_VERSION],
+                      [
+                       ac_cv_POSTGRESQL_VERSION=`$PG_CONFIG --version | sed 
"s/^PostgreSQL[[[:space:]]][[[:space:]]]*\([[0-9.]][[0-9.]]*\).*/\1/"` \
+                             || _AX_LIB_POSTGRESQL_OLD_fail=yes
+                      ])
+         AS_IF([test "X$_AX_LIB_POSTGRESQL_OLD_fail" = "Xyes"],[break])
+         POSTGRESQL_VERSION="$ac_cv_POSTGRESQL_VERSION"
+
+
+         dnl
+         dnl Check if required version of PostgreSQL is available
+         dnl
+         AS_IF([test X"$postgresql_version_req" != "X"],[
+            AC_MSG_CHECKING([if PostgreSQL version $POSTGRESQL_VERSION is >= 
$postgresql_version_req])
+            
AX_COMPARE_VERSION([$POSTGRESQL_VERSION],[ge],[$postgresql_version_req],
+                               
[found_postgresql_req_version=yes],[found_postgresql_req_version=no])
+            AC_MSG_RESULT([$found_postgresql_req_version])
+         ])
+         AS_IF([test "Xfound_postgresql_req_version" = "Xno"],[break])
+
+         found_postgresql="yes"
+         break
+       done
+])
+
+AC_DEFUN([_AX_LIB_POSTGRESQL_PKG_CONFIG],
+[
+  AC_REQUIRE([PKG_PROG_PKG_CONFIG])
+  found_postgresql=no
+
+  while true; do
+    PKG_PROG_PKG_CONFIG
+    AS_IF([test X$PKG_CONFIG = X],[break])
+
+    _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=no;
+    AS_IF([test "X$postgresql_version_req" = "X"],
+         
[PKG_CHECK_EXISTS([libpq],[found_postgresql_pkg_config=yes],[found_postgresql=no])],
+         [PKG_CHECK_EXISTS([libpq >= "$postgresql_version_req"],
+                          [found_postgresql=yes],[found_postgresql=no])])
+    AS_IF([test "X$found_postgresql" = "no"],[break])
+
+    AC_CACHE_CHECK([for the PostgreSQL libraries 
CPPFLAGS],[ac_cv_POSTGRESQL_CPPFLAGS],
+                  [ac_cv_POSTGRESQL_CPPFLAGS="`$PKG_CONFIG libpq 
--cflags-only-I`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+    AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+    POSTGRESQL_CPPFLAGS="$ac_cv_POSTGRESQL_CPPFLAGS"
+
+
+    AC_CACHE_CHECK([for the PostgreSQL libraries 
LDFLAGS],[ac_cv_POSTGRESQL_LDFLAGS],
+                  [ac_cv_POSTGRESQL_LDFLAGS="`$PKG_CONFIG libpq --libs-only-L 
--libs-only-other`" || _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+    AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+    POSTGRESQL_LDFLAGS="$ac_cv_POSTGRESQL_LDFLAGS"
+
+
+    AC_CACHE_CHECK([for the PostgreSQL libraries LIBS],[ac_cv_POSTGRESQL_LIBS],
+                  [ac_cv_POSTGRESQL_LIBS="`$PKG_CONFIG libpq --libs-only-l`" 
|| _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=ye])
+    AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+    POSTGRESQL_LIBS="$ac_cv_POSTGRESQL_LIBS"
+
+    dnl already checked by exist but need to be recovered
+    AC_CACHE_CHECK([for the PostgreSQL version],[ac_cv_POSTGRESQL_VERSION],
+                  [ac_cv_POSTGRESQL_VERSION="`$PKG_CONFIG libpq --modversion`" 
|| _AX_LIB_POSTGRESQL_PKG_CONFIG_fail=yes])
+    AS_IF([test "X$_AX_LIB_POSTGRESQL_PKG_CONFIG_fail" = "Xyes"],[break])
+    POSTGRESQL_VERSION="$ac_cv_POSTGRESQL_VERSION"
+
+    found_postgresql=yes
+    break;
+  done
+
+])
+
+
+
+AC_DEFUN([AX_LIB_POSTGRESQL],
+[
+    AC_ARG_WITH([postgresql],
+       AS_HELP_STRING([--with-postgresql=@<:@ARG@:>@],
+           [use PostgreSQL library @<:@default=yes@:>@, optionally specify 
path to pg_config]
+       ),
+       [
+       AS_CASE([$withval],
+               [[[nN]][[oO]]],[want_postgresql="no"],
+               [[[yY]][[eE]][[sS]]],[want_postgresql="yes"],
+               [
+                       want_postgresql="yes"
+                       PG_CONFIG="$withval"
+               ])
+       ],
+       [want_postgresql="yes"]
+    )
+
+    AC_ARG_VAR([POSTGRESQL_CPPFLAGS],[cpp flags for PostgreSQL overriding 
detected flags])
+    AC_ARG_VAR([POSTGRESQL_LIBFLAGS],[libs for PostgreSQL overriding detected 
flags])
+    AC_ARG_VAR([POSTGRESQL_LDFLAGS],[linker flags for PostgreSQL overriding 
detected flags])
+
+    # populate cache
+    AS_IF([test "X$POSTGRESQL_CPPFLAGS" != 
X],[ac_cv_POSTGRESQL_CPPFLAGS="$POSTGRESQL_CPPFLAGS"])
+    AS_IF([test "X$POSTGRESQL_LDFLAGS" != 
X],[ac_cv_POSTGRESQL_LDFLAGS="$POSTGRESQL_LDFLAGS"])
+    AS_IF([test "X$POSTGRESQL_LIBS" != 
X],[ac_cv_POSTGRESQL_LIBS="$POSTGRESQL_LIBS"])
+
+    postgresql_version_req=ifelse([$1], [], [], [$1])
+    found_postgresql="no"
+
+    POSTGRESQL_VERSION=""
+
+    dnl
+    dnl Check PostgreSQL libraries (libpq)
+    dnl
+    AS_IF([test X"$want_postgresql" = "Xyes"],[
+      _AX_LIB_POSTGRESQL_PKG_CONFIG
+
+
+      AS_IF([test X"$found_postgresql" = "Xno"],
+           [_AX_LIB_POSTGRESQL_OLD])
+
+      AS_IF([test X"$found_postgresql" = Xyes],[
+         _AX_LIB_POSTGRESQL_OLD_CPPFLAGS="$CPPFLAGS"
+         CPPFLAGS="$CPPFLAGS $POSTGRESQL_CPPFLAGS"
+         _AX_LIB_POSTGRESQL_OLD_LDFLAGS="$LDFLAGS"
+         LDFLAGS="$LDFLAGS $POSTGRESQL_LDFLAGS"
+         _AX_LIB_POSTGRESQL_OLD_LIBS="$LIBS"
+         LIBS="$LIBS $POSTGRESQL_LIBS"
+         while true; do
+           dnl try to compile
+           AC_CHECK_HEADER([libpq-fe.h],[],[found_postgresql=no])
+           AS_IF([test "X$found_postgresql" = "Xno"],[break])
+           dnl try now to link
+           AC_CACHE_CHECK([for the PostgreSQL library linking is 
working],[ac_cv_postgresql_found],
+           [
+             AC_LINK_IFELSE([
+               AC_LANG_PROGRAM(
+                 [
+                  #include <libpq-fe.h>
+                 ],
+                 [[
+                   char conninfo[]="dbname = postgres";
+                   PGconn     *conn;
+                   conn = PQconnectdb(conninfo);
+                 ]]
+                )
+               ],[ac_cv_postgresql_found=yes],
+                 [ac_cv_postgresql_found=no])
+            ])
+           found_postgresql="$ac_cv_postgresql_found"
+           AS_IF([test "X$found_postgresql" = "Xno"],[break])
+           break
+       done
+       CPPFLAGS="$_AX_LIB_POSTGRESQL_OLD_CPPFLAGS"
+       LDFLAGS="$_AX_LIB_POSTGRESQL_OLD_LDFLAGS"
+       LIBS="$_AX_LIB_POSTGRESQL_OLD_LIBS"
+       ])
+
+
+      AS_IF([test "x$found_postgresql" = "xyes"],[
+               AC_DEFINE([HAVE_POSTGRESQL], [1],
+                         [Define to 1 if PostgreSQL libraries are available])])
+    ])
+
+    AC_SUBST([POSTGRESQL_VERSION])
+    AC_SUBST([POSTGRESQL_CPPFLAGS])
+    AC_SUBST([POSTGRESQL_LDFLAGS])
+    AC_SUBST([POSTGRESQL_LIBS])
+
+    AS_IF([test "x$found_postgresql" = "xyes"],
+     [ifelse([$2], , :, [$2])],
+     [ifelse([$3], , AS_IF([test X"$want_postgresql" = 
"Xyes"],[AC_MSG_ERROR([Library requirements (PostgreSQL) not met.])],[:]), 
[$3])])
+
+])
diff --git a/m4/ax_prog_doxygen.m4 b/m4/ax_prog_doxygen.m4
new file mode 100644
index 0000000..a371f7f
--- /dev/null
+++ b/m4/ax_prog_doxygen.m4
@@ -0,0 +1,586 @@
+# ===========================================================================
+#     https://www.gnu.org/software/autoconf-archive/ax_prog_doxygen.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   DX_INIT_DOXYGEN(PROJECT-NAME, [DOXYFILE-PATH], [OUTPUT-DIR], ...)
+#   DX_DOXYGEN_FEATURE(ON|OFF)
+#   DX_DOT_FEATURE(ON|OFF)
+#   DX_HTML_FEATURE(ON|OFF)
+#   DX_CHM_FEATURE(ON|OFF)
+#   DX_CHI_FEATURE(ON|OFF)
+#   DX_MAN_FEATURE(ON|OFF)
+#   DX_RTF_FEATURE(ON|OFF)
+#   DX_XML_FEATURE(ON|OFF)
+#   DX_PDF_FEATURE(ON|OFF)
+#   DX_PS_FEATURE(ON|OFF)
+#
+# DESCRIPTION
+#
+#   The DX_*_FEATURE macros control the default setting for the given
+#   Doxygen feature. Supported features are 'DOXYGEN' itself, 'DOT' for
+#   generating graphics, 'HTML' for plain HTML, 'CHM' for compressed HTML
+#   help (for MS users), 'CHI' for generating a separate .chi file by the
+#   .chm file, and 'MAN', 'RTF', 'XML', 'PDF' and 'PS' for the appropriate
+#   output formats. The environment variable DOXYGEN_PAPER_SIZE may be
+#   specified to override the default 'a4wide' paper size.
+#
+#   By default, HTML, PDF and PS documentation is generated as this seems to
+#   be the most popular and portable combination. MAN pages created by
+#   Doxygen are usually problematic, though by picking an appropriate subset
+#   and doing some massaging they might be better than nothing. CHM and RTF
+#   are specific for MS (note that you can't generate both HTML and CHM at
+#   the same time). The XML is rather useless unless you apply specialized
+#   post-processing to it.
+#
+#   The macros mainly control the default state of the feature. The use can
+#   override the default by specifying --enable or --disable. The macros
+#   ensure that contradictory flags are not given (e.g.,
+#   --enable-doxygen-html and --enable-doxygen-chm,
+#   --enable-doxygen-anything with --disable-doxygen, etc.) Finally, each
+#   feature will be automatically disabled (with a warning) if the required
+#   programs are missing.
+#
+#   Once all the feature defaults have been specified, call DX_INIT_DOXYGEN
+#   with the following parameters: a one-word name for the project for use
+#   as a filename base etc., an optional configuration file name (the
+#   default is '$(srcdir)/Doxyfile', the same as Doxygen's default), and an
+#   optional output directory name (the default is 'doxygen-doc'). To run
+#   doxygen multiple times for different configuration files and output
+#   directories provide more parameters: the second, forth, sixth, etc
+#   parameter are configuration file names and the third, fifth, seventh,
+#   etc parameter are output directories. No checking is done to catch
+#   duplicates.
+#
+#   Automake Support
+#
+#   The DX_RULES substitution can be used to add all needed rules to the
+#   Makefile. Note that this is a substitution without being a variable:
+#   only the @DX_RULES@ syntax will work.
+#
+#   The provided targets are:
+#
+#     doxygen-doc: Generate all doxygen documentation.
+#
+#     doxygen-run: Run doxygen, which will generate some of the
+#                  documentation (HTML, CHM, CHI, MAN, RTF, XML)
+#                  but will not do the post processing required
+#                  for the rest of it (PS, PDF).
+#
+#     doxygen-ps:  Generate doxygen PostScript documentation.
+#
+#     doxygen-pdf: Generate doxygen PDF documentation.
+#
+#   Note that by default these are not integrated into the automake targets.
+#   If doxygen is used to generate man pages, you can achieve this
+#   integration by setting man3_MANS to the list of man pages generated and
+#   then adding the dependency:
+#
+#     $(man3_MANS): doxygen-doc
+#
+#   This will cause make to run doxygen and generate all the documentation.
+#
+#   The following variable is intended for use in Makefile.am:
+#
+#     DX_CLEANFILES = everything to clean.
+#
+#   Then add this variable to MOSTLYCLEANFILES.
+#
+# LICENSE
+#
+#   Copyright (c) 2009 Oren Ben-Kiki <oren@ben-kiki.org>
+#   Copyright (c) 2015 Olaf Mandel <olaf@mandel.name>
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 23
+
+## ----------##
+## Defaults. ##
+## ----------##
+
+DX_ENV=""
+AC_DEFUN([DX_FEATURE_doc],  ON)
+AC_DEFUN([DX_FEATURE_dot],  OFF)
+AC_DEFUN([DX_FEATURE_man],  OFF)
+AC_DEFUN([DX_FEATURE_html], ON)
+AC_DEFUN([DX_FEATURE_chm],  OFF)
+AC_DEFUN([DX_FEATURE_chi],  OFF)
+AC_DEFUN([DX_FEATURE_rtf],  OFF)
+AC_DEFUN([DX_FEATURE_xml],  OFF)
+AC_DEFUN([DX_FEATURE_pdf],  ON)
+AC_DEFUN([DX_FEATURE_ps],   ON)
+
+## --------------- ##
+## Private macros. ##
+## --------------- ##
+
+# DX_ENV_APPEND(VARIABLE, VALUE)
+# ------------------------------
+# Append VARIABLE="VALUE" to DX_ENV for invoking doxygen and add it
+# as a substitution (but not a Makefile variable). The substitution
+# is skipped if the variable name is VERSION.
+AC_DEFUN([DX_ENV_APPEND],
+[AC_SUBST([DX_ENV], ["$DX_ENV $1='$2'"])dnl
+m4_if([$1], [VERSION], [], [AC_SUBST([$1], [$2])dnl
+AM_SUBST_NOTMAKE([$1])])dnl
+])
+
+# DX_DIRNAME_EXPR
+# ---------------
+# Expand into a shell expression prints the directory part of a path.
+AC_DEFUN([DX_DIRNAME_EXPR],
+         [[expr ".$1" : '\(\.\)[^/]*$' \| "x$1" : 'x\(.*\)/[^/]*$']])
+
+# DX_IF_FEATURE(FEATURE, IF-ON, IF-OFF)
+# -------------------------------------
+# Expands according to the M4 (static) status of the feature.
+AC_DEFUN([DX_IF_FEATURE], [ifelse(DX_FEATURE_$1, ON, [$2], [$3])])
+
+# DX_REQUIRE_PROG(VARIABLE, PROGRAM)
+# ----------------------------------
+# Require the specified program to be found for the DX_CURRENT_FEATURE to work.
+AC_DEFUN([DX_REQUIRE_PROG], [
+AC_PATH_TOOL([$1], [$2])
+if test "$DX_FLAG_[]DX_CURRENT_FEATURE$$1" = 1; then
+    AC_MSG_WARN([$2 not found - will not DX_CURRENT_DESCRIPTION])
+    AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0)
+fi
+])
+
+# DX_TEST_FEATURE(FEATURE)
+# ------------------------
+# Expand to a shell expression testing whether the feature is active.
+AC_DEFUN([DX_TEST_FEATURE], [test "$DX_FLAG_$1" = 1])
+
+# DX_CHECK_DEPEND(REQUIRED_FEATURE, REQUIRED_STATE)
+# -------------------------------------------------
+# Verify that a required features has the right state before trying to turn on
+# the DX_CURRENT_FEATURE.
+AC_DEFUN([DX_CHECK_DEPEND], [
+test "$DX_FLAG_$1" = "$2" \
+|| AC_MSG_ERROR([doxygen-DX_CURRENT_FEATURE ifelse([$2], 1,
+                            requires, contradicts) doxygen-DX_CURRENT_FEATURE])
+])
+
+# DX_CLEAR_DEPEND(FEATURE, REQUIRED_FEATURE, REQUIRED_STATE)
+# ----------------------------------------------------------
+# Turn off the DX_CURRENT_FEATURE if the required feature is off.
+AC_DEFUN([DX_CLEAR_DEPEND], [
+test "$DX_FLAG_$1" = "$2" || AC_SUBST(DX_FLAG_[]DX_CURRENT_FEATURE, 0)
+])
+
+# DX_FEATURE_ARG(FEATURE, DESCRIPTION,
+#                CHECK_DEPEND, CLEAR_DEPEND,
+#                REQUIRE, DO-IF-ON, DO-IF-OFF)
+# --------------------------------------------
+# Parse the command-line option controlling a feature. CHECK_DEPEND is called
+# if the user explicitly turns the feature on (and invokes DX_CHECK_DEPEND),
+# otherwise CLEAR_DEPEND is called to turn off the default state if a required
+# feature is disabled (using DX_CLEAR_DEPEND). REQUIRE performs additional
+# requirement tests (DX_REQUIRE_PROG). Finally, an automake flag is set and
+# DO-IF-ON or DO-IF-OFF are called according to the final state of the feature.
+AC_DEFUN([DX_ARG_ABLE], [
+    AC_DEFUN([DX_CURRENT_FEATURE], [$1])
+    AC_DEFUN([DX_CURRENT_DESCRIPTION], [$2])
+    AC_ARG_ENABLE(doxygen-$1,
+                  [AS_HELP_STRING(DX_IF_FEATURE([$1], [--disable-doxygen-$1],
+                                                      [--enable-doxygen-$1]),
+                                  DX_IF_FEATURE([$1], [don't $2], [$2]))],
+                  [
+case "$enableval" in
+#(
+y|Y|yes|Yes|YES)
+    AC_SUBST([DX_FLAG_$1], 1)
+    $3
+;; #(
+n|N|no|No|NO)
+    AC_SUBST([DX_FLAG_$1], 0)
+;; #(
+*)
+    AC_MSG_ERROR([invalid value '$enableval' given to doxygen-$1])
+;;
+esac
+], [
+AC_SUBST([DX_FLAG_$1], [DX_IF_FEATURE([$1], 1, 0)])
+$4
+])
+if DX_TEST_FEATURE([$1]); then
+    $5
+    :
+fi
+if DX_TEST_FEATURE([$1]); then
+    $6
+    :
+else
+    $7
+    :
+fi
+])
+
+## -------------- ##
+## Public macros. ##
+## -------------- ##
+
+# DX_XXX_FEATURE(DEFAULT_STATE)
+# -----------------------------
+AC_DEFUN([DX_DOXYGEN_FEATURE], [AC_DEFUN([DX_FEATURE_doc],  [$1])])
+AC_DEFUN([DX_DOT_FEATURE],     [AC_DEFUN([DX_FEATURE_dot], [$1])])
+AC_DEFUN([DX_MAN_FEATURE],     [AC_DEFUN([DX_FEATURE_man],  [$1])])
+AC_DEFUN([DX_HTML_FEATURE],    [AC_DEFUN([DX_FEATURE_html], [$1])])
+AC_DEFUN([DX_CHM_FEATURE],     [AC_DEFUN([DX_FEATURE_chm],  [$1])])
+AC_DEFUN([DX_CHI_FEATURE],     [AC_DEFUN([DX_FEATURE_chi],  [$1])])
+AC_DEFUN([DX_RTF_FEATURE],     [AC_DEFUN([DX_FEATURE_rtf],  [$1])])
+AC_DEFUN([DX_XML_FEATURE],     [AC_DEFUN([DX_FEATURE_xml],  [$1])])
+AC_DEFUN([DX_XML_FEATURE],     [AC_DEFUN([DX_FEATURE_xml],  [$1])])
+AC_DEFUN([DX_PDF_FEATURE],     [AC_DEFUN([DX_FEATURE_pdf],  [$1])])
+AC_DEFUN([DX_PS_FEATURE],      [AC_DEFUN([DX_FEATURE_ps],   [$1])])
+
+# DX_INIT_DOXYGEN(PROJECT, [CONFIG-FILE], [OUTPUT-DOC-DIR], ...)
+# --------------------------------------------------------------
+# PROJECT also serves as the base name for the documentation files.
+# The default CONFIG-FILE is "$(srcdir)/Doxyfile" and OUTPUT-DOC-DIR is
+# "doxygen-doc".
+# More arguments are interpreted as interleaved CONFIG-FILE and
+# OUTPUT-DOC-DIR values.
+AC_DEFUN([DX_INIT_DOXYGEN], [
+
+# Files:
+AC_SUBST([DX_PROJECT], [$1])
+AC_SUBST([DX_CONFIG], ['ifelse([$2], [], [$(srcdir)/Doxyfile], [$2])'])
+AC_SUBST([DX_DOCDIR], ['ifelse([$3], [], [doxygen-doc], [$3])'])
+m4_if(m4_eval(3 < m4_count($@)), 1, [m4_for([DX_i], 4, m4_count($@), 2,
+      [AC_SUBST([DX_CONFIG]m4_eval(DX_i[/2]),
+                'm4_default_nblank_quoted(m4_argn(DX_i, $@),
+                                          [$(srcdir)/Doxyfile])')])])dnl
+m4_if(m4_eval(3 < m4_count($@)), 1, [m4_for([DX_i], 5, m4_count($@,), 2,
+      [AC_SUBST([DX_DOCDIR]m4_eval([(]DX_i[-1)/2]),
+                'm4_default_nblank_quoted(m4_argn(DX_i, $@),
+                                          [doxygen-doc])')])])dnl
+m4_define([DX_loop], m4_dquote(m4_if(m4_eval(3 < m4_count($@)), 1,
+          [m4_for([DX_i], 4, m4_count($@), 2, [, m4_eval(DX_i[/2])])],
+          [])))dnl
+
+# Environment variables used inside doxygen.cfg:
+DX_ENV_APPEND(SRCDIR, $srcdir)
+DX_ENV_APPEND(PROJECT, $DX_PROJECT)
+DX_ENV_APPEND(VERSION, $PACKAGE_VERSION)
+
+# Doxygen itself:
+DX_ARG_ABLE(doc, [generate any doxygen documentation],
+            [],
+            [],
+            [DX_REQUIRE_PROG([DX_DOXYGEN], doxygen)
+             DX_REQUIRE_PROG([DX_PERL], perl)],
+            [DX_ENV_APPEND(PERL_PATH, $DX_PERL)])
+
+# Dot for graphics:
+DX_ARG_ABLE(dot, [generate graphics for doxygen documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [DX_REQUIRE_PROG([DX_DOT], dot)],
+            [DX_ENV_APPEND(HAVE_DOT, YES)
+             DX_ENV_APPEND(DOT_PATH, [`DX_DIRNAME_EXPR($DX_DOT)`])],
+            [DX_ENV_APPEND(HAVE_DOT, NO)])
+
+# Man pages generation:
+DX_ARG_ABLE(man, [generate doxygen manual pages],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [],
+            [DX_ENV_APPEND(GENERATE_MAN, YES)],
+            [DX_ENV_APPEND(GENERATE_MAN, NO)])
+
+# RTF file generation:
+DX_ARG_ABLE(rtf, [generate doxygen RTF documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [],
+            [DX_ENV_APPEND(GENERATE_RTF, YES)],
+            [DX_ENV_APPEND(GENERATE_RTF, NO)])
+
+# XML file generation:
+DX_ARG_ABLE(xml, [generate doxygen XML documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [],
+            [DX_ENV_APPEND(GENERATE_XML, YES)],
+            [DX_ENV_APPEND(GENERATE_XML, NO)])
+
+# (Compressed) HTML help generation:
+DX_ARG_ABLE(chm, [generate doxygen compressed HTML help documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [DX_REQUIRE_PROG([DX_HHC], hhc)],
+            [DX_ENV_APPEND(HHC_PATH, $DX_HHC)
+             DX_ENV_APPEND(GENERATE_HTML, YES)
+             DX_ENV_APPEND(GENERATE_HTMLHELP, YES)],
+            [DX_ENV_APPEND(GENERATE_HTMLHELP, NO)])
+
+# Separate CHI file generation.
+DX_ARG_ABLE(chi, [generate doxygen separate compressed HTML help index file],
+            [DX_CHECK_DEPEND(chm, 1)],
+            [DX_CLEAR_DEPEND(chm, 1)],
+            [],
+            [DX_ENV_APPEND(GENERATE_CHI, YES)],
+            [DX_ENV_APPEND(GENERATE_CHI, NO)])
+
+# Plain HTML pages generation:
+DX_ARG_ABLE(html, [generate doxygen plain HTML documentation],
+            [DX_CHECK_DEPEND(doc, 1) DX_CHECK_DEPEND(chm, 0)],
+            [DX_CLEAR_DEPEND(doc, 1) DX_CLEAR_DEPEND(chm, 0)],
+            [],
+            [DX_ENV_APPEND(GENERATE_HTML, YES)],
+            [DX_TEST_FEATURE(chm) || DX_ENV_APPEND(GENERATE_HTML, NO)])
+
+# PostScript file generation:
+DX_ARG_ABLE(ps, [generate doxygen PostScript documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [DX_REQUIRE_PROG([DX_LATEX], latex)
+             DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex)
+             DX_REQUIRE_PROG([DX_DVIPS], dvips)
+             DX_REQUIRE_PROG([DX_EGREP], egrep)])
+
+# PDF file generation:
+DX_ARG_ABLE(pdf, [generate doxygen PDF documentation],
+            [DX_CHECK_DEPEND(doc, 1)],
+            [DX_CLEAR_DEPEND(doc, 1)],
+            [DX_REQUIRE_PROG([DX_PDFLATEX], pdflatex)
+             DX_REQUIRE_PROG([DX_MAKEINDEX], makeindex)
+             DX_REQUIRE_PROG([DX_EGREP], egrep)])
+
+# LaTeX generation for PS and/or PDF:
+if DX_TEST_FEATURE(ps) || DX_TEST_FEATURE(pdf); then
+    DX_ENV_APPEND(GENERATE_LATEX, YES)
+else
+    DX_ENV_APPEND(GENERATE_LATEX, NO)
+fi
+
+# Paper size for PS and/or PDF:
+AC_ARG_VAR(DOXYGEN_PAPER_SIZE,
+           [a4wide (default), a4, letter, legal or executive])
+case "$DOXYGEN_PAPER_SIZE" in
+#(
+"")
+    AC_SUBST(DOXYGEN_PAPER_SIZE, "")
+;; #(
+a4wide|a4|letter|legal|executive)
+    DX_ENV_APPEND(PAPER_SIZE, $DOXYGEN_PAPER_SIZE)
+;; #(
+*)
+    AC_MSG_ERROR([unknown DOXYGEN_PAPER_SIZE='$DOXYGEN_PAPER_SIZE'])
+;;
+esac
+
+# Rules:
+AS_IF([[test $DX_FLAG_html -eq 1]],
+[[DX_SNIPPET_html="## ------------------------------- ##
+## Rules specific for HTML output. ##
+## ------------------------------- ##
+
+DX_CLEAN_HTML = \$(DX_DOCDIR)/html]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+                \$(DX_DOCDIR]DX_i[)/html]])[
+
+"]],
+[[DX_SNIPPET_html=""]])
+AS_IF([[test $DX_FLAG_chi -eq 1]],
+[[DX_SNIPPET_chi="
+DX_CLEAN_CHI = \$(DX_DOCDIR)/\$(PACKAGE).chi]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).chi]])["]],
+[[DX_SNIPPET_chi=""]])
+AS_IF([[test $DX_FLAG_chm -eq 1]],
+[[DX_SNIPPET_chm="## ------------------------------ ##
+## Rules specific for CHM output. ##
+## ------------------------------ ##
+
+DX_CLEAN_CHM = \$(DX_DOCDIR)/chm]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/chm]])[\
+${DX_SNIPPET_chi}
+
+"]],
+[[DX_SNIPPET_chm=""]])
+AS_IF([[test $DX_FLAG_man -eq 1]],
+[[DX_SNIPPET_man="## ------------------------------ ##
+## Rules specific for MAN output. ##
+## ------------------------------ ##
+
+DX_CLEAN_MAN = \$(DX_DOCDIR)/man]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/man]])[
+
+"]],
+[[DX_SNIPPET_man=""]])
+AS_IF([[test $DX_FLAG_rtf -eq 1]],
+[[DX_SNIPPET_rtf="## ------------------------------ ##
+## Rules specific for RTF output. ##
+## ------------------------------ ##
+
+DX_CLEAN_RTF = \$(DX_DOCDIR)/rtf]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/rtf]])[
+
+"]],
+[[DX_SNIPPET_rtf=""]])
+AS_IF([[test $DX_FLAG_xml -eq 1]],
+[[DX_SNIPPET_xml="## ------------------------------ ##
+## Rules specific for XML output. ##
+## ------------------------------ ##
+
+DX_CLEAN_XML = \$(DX_DOCDIR)/xml]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/xml]])[
+
+"]],
+[[DX_SNIPPET_xml=""]])
+AS_IF([[test $DX_FLAG_ps -eq 1]],
+[[DX_SNIPPET_ps="## ----------------------------- ##
+## Rules specific for PS output. ##
+## ----------------------------- ##
+
+DX_CLEAN_PS = \$(DX_DOCDIR)/\$(PACKAGE).ps]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+              \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).ps]])[
+
+DX_PS_GOAL = doxygen-ps
+
+doxygen-ps: \$(DX_CLEAN_PS)
+
+]m4_foreach([DX_i], [DX_loop],
+[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).ps: \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag
+       \$(DX_V_LATEX)cd \$(DX_DOCDIR]DX_i[)/latex; \\
+       rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \\
+       \$(DX_LATEX) refman.tex; \\
+       \$(DX_MAKEINDEX) refman.idx; \\
+       \$(DX_LATEX) refman.tex; \\
+       countdown=5; \\
+       while \$(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \\
+                         refman.log > /dev/null 2>&1 \\
+          && test \$\$countdown -gt 0; do \\
+           \$(DX_LATEX) refman.tex; \\
+            countdown=\`expr \$\$countdown - 1\`; \\
+       done; \\
+       \$(DX_DVIPS) -o ../\$(PACKAGE).ps refman.dvi
+
+]])["]],
+[[DX_SNIPPET_ps=""]])
+AS_IF([[test $DX_FLAG_pdf -eq 1]],
+[[DX_SNIPPET_pdf="## ------------------------------ ##
+## Rules specific for PDF output. ##
+## ------------------------------ ##
+
+DX_CLEAN_PDF = \$(DX_DOCDIR)/\$(PACKAGE).pdf]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+               \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).pdf]])[
+
+DX_PDF_GOAL = doxygen-pdf
+
+doxygen-pdf: \$(DX_CLEAN_PDF)
+
+]m4_foreach([DX_i], [DX_loop],
+[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).pdf: \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag
+       \$(DX_V_LATEX)cd \$(DX_DOCDIR]DX_i[)/latex; \\
+       rm -f *.aux *.toc *.idx *.ind *.ilg *.log *.out; \\
+       \$(DX_PDFLATEX) refman.tex; \\
+       \$(DX_MAKEINDEX) refman.idx; \\
+       \$(DX_PDFLATEX) refman.tex; \\
+       countdown=5; \\
+       while \$(DX_EGREP) 'Rerun (LaTeX|to get cross-references right)' \\
+                         refman.log > /dev/null 2>&1 \\
+          && test \$\$countdown -gt 0; do \\
+           \$(DX_PDFLATEX) refman.tex; \\
+           countdown=\`expr \$\$countdown - 1\`; \\
+       done; \\
+       mv refman.pdf ../\$(PACKAGE).pdf
+
+]])["]],
+[[DX_SNIPPET_pdf=""]])
+AS_IF([[test $DX_FLAG_ps -eq 1 -o $DX_FLAG_pdf -eq 1]],
+[[DX_SNIPPET_latex="## ------------------------------------------------- ##
+## Rules specific for LaTeX (shared for PS and PDF). ##
+## ------------------------------------------------- ##
+
+DX_V_LATEX = \$(_DX_v_LATEX_\$(V))
+_DX_v_LATEX_ = \$(_DX_v_LATEX_\$(AM_DEFAULT_VERBOSITY))
+_DX_v_LATEX_0 = @echo \"  LATEX \" \$][@;
+
+DX_CLEAN_LATEX = \$(DX_DOCDIR)/latex]dnl
+m4_foreach([DX_i], [m4_shift(DX_loop)], [[\\
+                 \$(DX_DOCDIR]DX_i[)/latex]])[
+
+"]],
+[[DX_SNIPPET_latex=""]])
+
+AS_IF([[test $DX_FLAG_doc -eq 1]],
+[[DX_SNIPPET_doc="## --------------------------------- ##
+## Format-independent Doxygen rules. ##
+## --------------------------------- ##
+
+${DX_SNIPPET_html}\
+${DX_SNIPPET_chm}\
+${DX_SNIPPET_man}\
+${DX_SNIPPET_rtf}\
+${DX_SNIPPET_xml}\
+${DX_SNIPPET_ps}\
+${DX_SNIPPET_pdf}\
+${DX_SNIPPET_latex}\
+DX_V_DXGEN = \$(_DX_v_DXGEN_\$(V))
+_DX_v_DXGEN_ = \$(_DX_v_DXGEN_\$(AM_DEFAULT_VERBOSITY))
+_DX_v_DXGEN_0 = @echo \"  DXGEN \" \$<;
+
+.PHONY: doxygen-run doxygen-doc \$(DX_PS_GOAL) \$(DX_PDF_GOAL)
+
+.INTERMEDIATE: doxygen-run \$(DX_PS_GOAL) \$(DX_PDF_GOAL)
+
+doxygen-run:]m4_foreach([DX_i], [DX_loop],
+                         [[ \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag]])[
+
+doxygen-doc: doxygen-run \$(DX_PS_GOAL) \$(DX_PDF_GOAL)
+
+]m4_foreach([DX_i], [DX_loop],
+[[\$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag: \$(DX_CONFIG]DX_i[) 
\$(pkginclude_HEADERS)
+       \$(A""M_V_at)rm -rf \$(DX_DOCDIR]DX_i[)
+       \$(DX_V_DXGEN)\$(DX_ENV) DOCDIR=\$(DX_DOCDIR]DX_i[) \$(DX_DOXYGEN) 
\$(DX_CONFIG]DX_i[)
+       \$(A""M_V_at)echo Timestamp >\$][@
+
+]])dnl
+[DX_CLEANFILES = \\]
+m4_foreach([DX_i], [DX_loop],
+[[     \$(DX_DOCDIR]DX_i[)/doxygen_sqlite3.db \\
+       \$(DX_DOCDIR]DX_i[)/\$(PACKAGE).tag \\
+]])dnl
+[      -r \\
+       \$(DX_CLEAN_HTML) \\
+       \$(DX_CLEAN_CHM) \\
+       \$(DX_CLEAN_CHI) \\
+       \$(DX_CLEAN_MAN) \\
+       \$(DX_CLEAN_RTF) \\
+       \$(DX_CLEAN_XML) \\
+       \$(DX_CLEAN_PS) \\
+       \$(DX_CLEAN_PDF) \\
+       \$(DX_CLEAN_LATEX)"]],
+[[DX_SNIPPET_doc=""]])
+AC_SUBST([DX_RULES],
+["${DX_SNIPPET_doc}"])dnl
+AM_SUBST_NOTMAKE([DX_RULES])
+
+#For debugging:
+#echo DX_FLAG_doc=$DX_FLAG_doc
+#echo DX_FLAG_dot=$DX_FLAG_dot
+#echo DX_FLAG_man=$DX_FLAG_man
+#echo DX_FLAG_html=$DX_FLAG_html
+#echo DX_FLAG_chm=$DX_FLAG_chm
+#echo DX_FLAG_chi=$DX_FLAG_chi
+#echo DX_FLAG_rtf=$DX_FLAG_rtf
+#echo DX_FLAG_xml=$DX_FLAG_xml
+#echo DX_FLAG_pdf=$DX_FLAG_pdf
+#echo DX_FLAG_ps=$DX_FLAG_ps
+#echo DX_ENV=$DX_ENV
+])
diff --git a/m4/codeset.m4 b/m4/codeset.m4
new file mode 100644
index 0000000..bc98201
--- /dev/null
+++ b/m4/codeset.m4
@@ -0,0 +1,24 @@
+# codeset.m4 serial 5 (gettext-0.18.2)
+dnl Copyright (C) 2000-2002, 2006, 2008-2014, 2016 Free Software Foundation,
+dnl Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([AM_LANGINFO_CODESET],
+[
+  AC_CACHE_CHECK([for nl_langinfo and CODESET], [am_cv_langinfo_codeset],
+    [AC_LINK_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <langinfo.h>]],
+          [[char* cs = nl_langinfo(CODESET); return !cs;]])],
+       [am_cv_langinfo_codeset=yes],
+       [am_cv_langinfo_codeset=no])
+    ])
+  if test $am_cv_langinfo_codeset = yes; then
+    AC_DEFINE([HAVE_LANGINFO_CODESET], [1],
+      [Define if you have <langinfo.h> and nl_langinfo(CODESET).])
+  fi
+])
diff --git a/m4/extern-inline.m4 b/m4/extern-inline.m4
new file mode 100644
index 0000000..1e578f3
--- /dev/null
+++ b/m4/extern-inline.m4
@@ -0,0 +1,102 @@
+dnl 'extern inline' a la ISO C99.
+
+dnl Copyright 2012-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_EXTERN_INLINE],
+[
+  AH_VERBATIM([extern_inline],
+[/* Please see the Gnulib manual for how to use these macros.
+
+   Suppress extern inline with HP-UX cc, as it appears to be broken; see
+   <http://lists.gnu.org/archive/html/bug-texinfo/2013-02/msg00030.html>.
+
+   Suppress extern inline with Sun C in standards-conformance mode, as it
+   mishandles inline functions that call each other.  E.g., for 'inline void f
+   (void) { } inline void g (void) { f (); }', c99 incorrectly complains
+   'reference to static identifier "f" in extern inline function'.
+   This bug was observed with Sun C 5.12 SunOS_i386 2011/11/16.
+
+   Suppress extern inline (with or without __attribute__ ((__gnu_inline__)))
+   on configurations that mistakenly use 'static inline' to implement
+   functions or macros in standard C headers like <ctype.h>.  For example,
+   if isdigit is mistakenly implemented via a static inline function,
+   a program containing an extern inline function that calls isdigit
+   may not work since the C standard prohibits extern inline functions
+   from calling static functions.  This bug is known to occur on:
+
+     OS X 10.8 and earlier; see:
+     http://lists.gnu.org/archive/html/bug-gnulib/2012-12/msg00023.html
+
+     DragonFly; see
+     
http://muscles.dragonflybsd.org/bulk/bleeding-edge-potential/latest-per-pkg/ah-tty-0.3.12.log
+
+     FreeBSD; see:
+     http://lists.gnu.org/archive/html/bug-gnulib/2014-07/msg00104.html
+
+   OS X 10.9 has a macro __header_inline indicating the bug is fixed for C and
+   for clang but remains for g++; see <http://trac.macports.org/ticket/41033>.
+   Assume DragonFly and FreeBSD will be similar.  */
+#if (((defined __APPLE__ && defined __MACH__) \
+      || defined __DragonFly__ || defined __FreeBSD__) \
+     && (defined __header_inline \
+         ? (defined __cplusplus && defined __GNUC_STDC_INLINE__ \
+            && ! defined __clang__) \
+         : ((! defined _DONT_USE_CTYPE_INLINE_ \
+             && (defined __GNUC__ || defined __cplusplus)) \
+            || (defined _FORTIFY_SOURCE && 0 < _FORTIFY_SOURCE \
+                && defined __GNUC__ && ! defined __cplusplus))))
+# define _GL_EXTERN_INLINE_STDHEADER_BUG
+#endif
+#if ((__GNUC__ \
+      ? defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__ \
+      : (199901L <= __STDC_VERSION__ \
+         && !defined __HP_cc \
+         && !defined __PGI \
+         && !(defined __SUNPRO_C && __STDC__))) \
+     && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# define _GL_INLINE inline
+# define _GL_EXTERN_INLINE extern inline
+# define _GL_EXTERN_INLINE_IN_USE
+#elif (2 < __GNUC__ + (7 <= __GNUC_MINOR__) && !defined __STRICT_ANSI__ \
+       && !defined _GL_EXTERN_INLINE_STDHEADER_BUG)
+# if defined __GNUC_GNU_INLINE__ && __GNUC_GNU_INLINE__
+   /* __gnu_inline__ suppresses a GCC 4.2 diagnostic.  */
+#  define _GL_INLINE extern inline __attribute__ ((__gnu_inline__))
+# else
+#  define _GL_INLINE extern inline
+# endif
+# define _GL_EXTERN_INLINE extern
+# define _GL_EXTERN_INLINE_IN_USE
+#else
+# define _GL_INLINE static _GL_UNUSED
+# define _GL_EXTERN_INLINE static _GL_UNUSED
+#endif
+
+/* In GCC 4.6 (inclusive) to 5.1 (exclusive),
+   suppress bogus "no previous prototype for 'FOO'"
+   and "no previous declaration for 'FOO'" diagnostics,
+   when FOO is an inline function in the header; see
+   <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54113> and
+   <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63877>.  */
+#if __GNUC__ == 4 && 6 <= __GNUC_MINOR__
+# if defined __GNUC_STDC_INLINE__ && __GNUC_STDC_INLINE__
+#  define _GL_INLINE_HEADER_CONST_PRAGMA
+# else
+#  define _GL_INLINE_HEADER_CONST_PRAGMA \
+     _Pragma ("GCC diagnostic ignored \"-Wsuggest-attribute=const\"")
+# endif
+# define _GL_INLINE_HEADER_BEGIN \
+    _Pragma ("GCC diagnostic push") \
+    _Pragma ("GCC diagnostic ignored \"-Wmissing-prototypes\"") \
+    _Pragma ("GCC diagnostic ignored \"-Wmissing-declarations\"") \
+    _GL_INLINE_HEADER_CONST_PRAGMA
+# define _GL_INLINE_HEADER_END \
+    _Pragma ("GCC diagnostic pop")
+#else
+# define _GL_INLINE_HEADER_BEGIN
+# define _GL_INLINE_HEADER_END
+#endif])
+])
diff --git a/m4/fcntl-o.m4 b/m4/fcntl-o.m4
new file mode 100644
index 0000000..24fcf88
--- /dev/null
+++ b/m4/fcntl-o.m4
@@ -0,0 +1,134 @@
+# fcntl-o.m4 serial 4
+dnl Copyright (C) 2006, 2009-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Written by Paul Eggert.
+
+# Test whether the flags O_NOATIME and O_NOFOLLOW actually work.
+# Define HAVE_WORKING_O_NOATIME to 1 if O_NOATIME works, or to 0 otherwise.
+# Define HAVE_WORKING_O_NOFOLLOW to 1 if O_NOFOLLOW works, or to 0 otherwise.
+AC_DEFUN([gl_FCNTL_O_FLAGS],
+[
+  dnl Persuade glibc <fcntl.h> to define O_NOATIME and O_NOFOLLOW.
+  dnl AC_USE_SYSTEM_EXTENSIONS was introduced in autoconf 2.60 and obsoletes
+  dnl AC_GNU_SOURCE.
+  m4_ifdef([AC_USE_SYSTEM_EXTENSIONS],
+    [AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])],
+    [AC_REQUIRE([AC_GNU_SOURCE])])
+
+  AC_CHECK_HEADERS_ONCE([unistd.h])
+  AC_CHECK_FUNCS_ONCE([symlink])
+  AC_CACHE_CHECK([for working fcntl.h], [gl_cv_header_working_fcntl_h],
+    [AC_RUN_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <sys/types.h>
+           #include <sys/stat.h>
+           #if HAVE_UNISTD_H
+           # include <unistd.h>
+           #else /* on Windows with MSVC */
+           # include <io.h>
+           # include <stdlib.h>
+           # defined sleep(n) _sleep ((n) * 1000)
+           #endif
+           #include <fcntl.h>
+           #ifndef O_NOATIME
+            #define O_NOATIME 0
+           #endif
+           #ifndef O_NOFOLLOW
+            #define O_NOFOLLOW 0
+           #endif
+           static int const constants[] =
+            {
+              O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND,
+              O_NONBLOCK, O_SYNC, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY
+            };
+          ]],
+          [[
+            int result = !constants;
+            #if HAVE_SYMLINK
+            {
+              static char const sym[] = "conftest.sym";
+              if (symlink ("/dev/null", sym) != 0)
+                result |= 2;
+              else
+                {
+                  int fd = open (sym, O_WRONLY | O_NOFOLLOW | O_CREAT, 0);
+                  if (fd >= 0)
+                    {
+                      close (fd);
+                      result |= 4;
+                    }
+                }
+              if (unlink (sym) != 0 || symlink (".", sym) != 0)
+                result |= 2;
+              else
+                {
+                  int fd = open (sym, O_RDONLY | O_NOFOLLOW);
+                  if (fd >= 0)
+                    {
+                      close (fd);
+                      result |= 4;
+                    }
+                }
+              unlink (sym);
+            }
+            #endif
+            {
+              static char const file[] = "confdefs.h";
+              int fd = open (file, O_RDONLY | O_NOATIME);
+              if (fd < 0)
+                result |= 8;
+              else
+                {
+                  struct stat st0;
+                  if (fstat (fd, &st0) != 0)
+                    result |= 16;
+                  else
+                    {
+                      char c;
+                      sleep (1);
+                      if (read (fd, &c, 1) != 1)
+                        result |= 24;
+                      else
+                        {
+                          if (close (fd) != 0)
+                            result |= 32;
+                          else
+                            {
+                              struct stat st1;
+                              if (stat (file, &st1) != 0)
+                                result |= 40;
+                              else
+                                if (st0.st_atime != st1.st_atime)
+                                  result |= 64;
+                            }
+                        }
+                    }
+                }
+            }
+            return result;]])],
+       [gl_cv_header_working_fcntl_h=yes],
+       [case $? in #(
+        4) gl_cv_header_working_fcntl_h='no (bad O_NOFOLLOW)';; #(
+        64) gl_cv_header_working_fcntl_h='no (bad O_NOATIME)';; #(
+        68) gl_cv_header_working_fcntl_h='no (bad O_NOATIME, O_NOFOLLOW)';; #(
+         *) gl_cv_header_working_fcntl_h='no';;
+        esac],
+       [gl_cv_header_working_fcntl_h=cross-compiling])])
+
+  case $gl_cv_header_working_fcntl_h in #(
+  *O_NOATIME* | no | cross-compiling) ac_val=0;; #(
+  *) ac_val=1;;
+  esac
+  AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOATIME], [$ac_val],
+    [Define to 1 if O_NOATIME works.])
+
+  case $gl_cv_header_working_fcntl_h in #(
+  *O_NOFOLLOW* | no | cross-compiling) ac_val=0;; #(
+  *) ac_val=1;;
+  esac
+  AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOFOLLOW], [$ac_val],
+    [Define to 1 if O_NOFOLLOW works.])
+])
diff --git a/m4/gettext.m4 b/m4/gettext.m4
new file mode 100644
index 0000000..eef5073
--- /dev/null
+++ b/m4/gettext.m4
@@ -0,0 +1,420 @@
+# gettext.m4 serial 68 (gettext-0.19.8)
+dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl   Bruno Haible <haible@clisp.cons.org>, 2000-2006, 2008-2010.
+
+dnl Macro to add for using GNU gettext.
+
+dnl Usage: AM_GNU_GETTEXT([INTLSYMBOL], [NEEDSYMBOL], [INTLDIR]).
+dnl INTLSYMBOL can be one of 'external', 'no-libtool', 'use-libtool'. The
+dnl    default (if it is not specified or empty) is 'no-libtool'.
+dnl    INTLSYMBOL should be 'external' for packages with no intl directory,
+dnl    and 'no-libtool' or 'use-libtool' for packages with an intl directory.
+dnl    If INTLSYMBOL is 'use-libtool', then a libtool library
+dnl    $(top_builddir)/intl/libintl.la will be created (shared and/or static,
+dnl    depending on --{enable,disable}-{shared,static} and on the presence of
+dnl    AM-DISABLE-SHARED). If INTLSYMBOL is 'no-libtool', a static library
+dnl    $(top_builddir)/intl/libintl.a will be created.
+dnl If NEEDSYMBOL is specified and is 'need-ngettext', then GNU gettext
+dnl    implementations (in libc or libintl) without the ngettext() function
+dnl    will be ignored.  If NEEDSYMBOL is specified and is
+dnl    'need-formatstring-macros', then GNU gettext implementations that don't
+dnl    support the ISO C 99 <inttypes.h> formatstring macros will be ignored.
+dnl INTLDIR is used to find the intl libraries.  If empty,
+dnl    the value '$(top_builddir)/intl/' is used.
+dnl
+dnl The result of the configuration is one of three cases:
+dnl 1) GNU gettext, as included in the intl subdirectory, will be compiled
+dnl    and used.
+dnl    Catalog format: GNU --> install in $(datadir)
+dnl    Catalog extension: .mo after installation, .gmo in source tree
+dnl 2) GNU gettext has been found in the system's C library.
+dnl    Catalog format: GNU --> install in $(datadir)
+dnl    Catalog extension: .mo after installation, .gmo in source tree
+dnl 3) No internationalization, always use English msgid.
+dnl    Catalog format: none
+dnl    Catalog extension: none
+dnl If INTLSYMBOL is 'external', only cases 2 and 3 can occur.
+dnl The use of .gmo is historical (it was needed to avoid overwriting the
+dnl GNU format catalogs when building on a platform with an X/Open gettext),
+dnl but we keep it in order not to force irrelevant filename changes on the
+dnl maintainers.
+dnl
+AC_DEFUN([AM_GNU_GETTEXT],
+[
+  dnl Argument checking.
+  ifelse([$1], [], , [ifelse([$1], [external], , [ifelse([$1], [no-libtool], , 
[ifelse([$1], [use-libtool], ,
+    [errprint([ERROR: invalid first argument to AM_GNU_GETTEXT
+])])])])])
+  ifelse(ifelse([$1], [], [old])[]ifelse([$1], [no-libtool], [old]), [old],
+    [AC_DIAGNOSE([obsolete], [Use of AM_GNU_GETTEXT without [external] 
argument is deprecated.])])
+  ifelse([$2], [], , [ifelse([$2], [need-ngettext], , [ifelse([$2], 
[need-formatstring-macros], ,
+    [errprint([ERROR: invalid second argument to AM_GNU_GETTEXT
+])])])])
+  define([gt_included_intl],
+    ifelse([$1], [external],
+      ifdef([AM_GNU_GETTEXT_][INTL_SUBDIR], [yes], [no]),
+      [yes]))
+  define([gt_libtool_suffix_prefix], ifelse([$1], [use-libtool], [l], []))
+  gt_NEEDS_INIT
+  AM_GNU_GETTEXT_NEED([$2])
+
+  AC_REQUIRE([AM_PO_SUBDIRS])dnl
+  ifelse(gt_included_intl, yes, [
+    AC_REQUIRE([AM_INTL_SUBDIR])dnl
+  ])
+
+  dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+
+  dnl Sometimes libintl requires libiconv, so first search for libiconv.
+  dnl Ideally we would do this search only after the
+  dnl      if test "$USE_NLS" = "yes"; then
+  dnl        if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != 
"yes"; }; then
+  dnl tests. But if configure.in invokes AM_ICONV after AM_GNU_GETTEXT
+  dnl the configure script would need to contain the same shell code
+  dnl again, outside any 'if'. There are two solutions:
+  dnl - Invoke AM_ICONV_LINKFLAGS_BODY here, outside any 'if'.
+  dnl - Control the expansions in more detail using AC_PROVIDE_IFELSE.
+  dnl Since AC_PROVIDE_IFELSE is only in autoconf >= 2.52 and not
+  dnl documented, we avoid it.
+  ifelse(gt_included_intl, yes, , [
+    AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
+  ])
+
+  dnl Sometimes, on Mac OS X, libintl requires linking with CoreFoundation.
+  gt_INTL_MACOSX
+
+  dnl Set USE_NLS.
+  AC_REQUIRE([AM_NLS])
+
+  ifelse(gt_included_intl, yes, [
+    BUILD_INCLUDED_LIBINTL=no
+    USE_INCLUDED_LIBINTL=no
+  ])
+  LIBINTL=
+  LTLIBINTL=
+  POSUB=
+
+  dnl Add a version number to the cache macros.
+  case " $gt_needs " in
+    *" need-formatstring-macros "*) gt_api_version=3 ;;
+    *" need-ngettext "*) gt_api_version=2 ;;
+    *) gt_api_version=1 ;;
+  esac
+  gt_func_gnugettext_libc="gt_cv_func_gnugettext${gt_api_version}_libc"
+  gt_func_gnugettext_libintl="gt_cv_func_gnugettext${gt_api_version}_libintl"
+
+  dnl If we use NLS figure out what method
+  if test "$USE_NLS" = "yes"; then
+    gt_use_preinstalled_gnugettext=no
+    ifelse(gt_included_intl, yes, [
+      AC_MSG_CHECKING([whether included gettext is requested])
+      AC_ARG_WITH([included-gettext],
+        [  --with-included-gettext use the GNU gettext library included here],
+        nls_cv_force_use_gnu_gettext=$withval,
+        nls_cv_force_use_gnu_gettext=no)
+      AC_MSG_RESULT([$nls_cv_force_use_gnu_gettext])
+
+      nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext"
+      if test "$nls_cv_force_use_gnu_gettext" != "yes"; then
+    ])
+        dnl User does not insist on using GNU NLS library.  Figure out what
+        dnl to use.  If GNU gettext is available we use this.  Else we have
+        dnl to fall back to GNU NLS library.
+
+        if test $gt_api_version -ge 3; then
+          gt_revision_test_code='
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+#define __GNU_GETTEXT_SUPPORTED_REVISION(major) ((major) == 0 ? 0 : -1)
+#endif
+changequote(,)dnl
+typedef int array [2 * (__GNU_GETTEXT_SUPPORTED_REVISION(0) >= 1) - 1];
+changequote([,])dnl
+'
+        else
+          gt_revision_test_code=
+        fi
+        if test $gt_api_version -ge 2; then
+          gt_expression_test_code=' + * ngettext ("", "", 0)'
+        else
+          gt_expression_test_code=
+        fi
+
+        AC_CACHE_CHECK([for GNU gettext in libc], [$gt_func_gnugettext_libc],
+         [AC_LINK_IFELSE(
+            [AC_LANG_PROGRAM(
+               [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern int *_nl_domain_bindings;
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + 
*_nl_domain_bindings)
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+               ]],
+               [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+               ]])],
+            [eval "$gt_func_gnugettext_libc=yes"],
+            [eval "$gt_func_gnugettext_libc=no"])])
+
+        if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != 
"yes"; }; then
+          dnl Sometimes libintl requires libiconv, so first search for 
libiconv.
+          ifelse(gt_included_intl, yes, , [
+            AM_ICONV_LINK
+          ])
+          dnl Search for libintl and define LIBINTL, LTLIBINTL and INCINTL
+          dnl accordingly. Don't use AC_LIB_LINKFLAGS_BODY([intl],[iconv])
+          dnl because that would add "-liconv" to LIBINTL and LTLIBINTL
+          dnl even if libiconv doesn't exist.
+          AC_LIB_LINKFLAGS_BODY([intl])
+          AC_CACHE_CHECK([for GNU gettext in libintl],
+            [$gt_func_gnugettext_libintl],
+           [gt_save_CPPFLAGS="$CPPFLAGS"
+            CPPFLAGS="$CPPFLAGS $INCINTL"
+            gt_save_LIBS="$LIBS"
+            LIBS="$LIBS $LIBINTL"
+            dnl Now see whether libintl exists and does not depend on libiconv.
+            AC_LINK_IFELSE(
+              [AC_LANG_PROGRAM(
+                 [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern
+#ifdef __cplusplus
+"C"
+#endif
+const char *_nl_expand_alias (const char *);
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias 
(""))
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+                 ]],
+                 [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+                 ]])],
+              [eval "$gt_func_gnugettext_libintl=yes"],
+              [eval "$gt_func_gnugettext_libintl=no"])
+            dnl Now see whether libintl exists and depends on libiconv.
+            if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" 
!= yes; } && test -n "$LIBICONV"; then
+              LIBS="$LIBS $LIBICONV"
+              AC_LINK_IFELSE(
+                [AC_LANG_PROGRAM(
+                   [[
+#include <libintl.h>
+#ifndef __GNU_GETTEXT_SUPPORTED_REVISION
+extern int _nl_msg_cat_cntr;
+extern
+#ifdef __cplusplus
+"C"
+#endif
+const char *_nl_expand_alias (const char *);
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION (_nl_msg_cat_cntr + *_nl_expand_alias 
(""))
+#else
+#define __GNU_GETTEXT_SYMBOL_EXPRESSION 0
+#endif
+$gt_revision_test_code
+                   ]],
+                   [[
+bindtextdomain ("", "");
+return * gettext ("")$gt_expression_test_code + __GNU_GETTEXT_SYMBOL_EXPRESSION
+                   ]])],
+                [LIBINTL="$LIBINTL $LIBICONV"
+                 LTLIBINTL="$LTLIBINTL $LTLIBICONV"
+                 eval "$gt_func_gnugettext_libintl=yes"
+                ])
+            fi
+            CPPFLAGS="$gt_save_CPPFLAGS"
+            LIBS="$gt_save_LIBS"])
+        fi
+
+        dnl If an already present or preinstalled GNU gettext() is found,
+        dnl use it.  But if this macro is used in GNU gettext, and GNU
+        dnl gettext is already preinstalled in libintl, we update this
+        dnl libintl.  (Cf. the install rule in intl/Makefile.in.)
+        if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" = "yes"; 
} \
+           || { { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" 
= "yes"; } \
+                && test "$PACKAGE" != gettext-runtime \
+                && test "$PACKAGE" != gettext-tools; }; then
+          gt_use_preinstalled_gnugettext=yes
+        else
+          dnl Reset the values set by searching for libintl.
+          LIBINTL=
+          LTLIBINTL=
+          INCINTL=
+        fi
+
+    ifelse(gt_included_intl, yes, [
+        if test "$gt_use_preinstalled_gnugettext" != "yes"; then
+          dnl GNU gettext is not found in the C library.
+          dnl Fall back on included GNU gettext library.
+          nls_cv_use_gnu_gettext=yes
+        fi
+      fi
+
+      if test "$nls_cv_use_gnu_gettext" = "yes"; then
+        dnl Mark actions used to generate GNU NLS library.
+        BUILD_INCLUDED_LIBINTL=yes
+        USE_INCLUDED_LIBINTL=yes
+        
LIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a
 $LIBICONV $LIBTHREAD"
+        
LTLIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a
 $LTLIBICONV $LTLIBTHREAD"
+        LIBS=`echo " $LIBS " | sed -e 's/ -lintl / /' -e 's/^ //' -e 's/ $//'`
+      fi
+
+      CATOBJEXT=
+      if test "$gt_use_preinstalled_gnugettext" = "yes" \
+         || test "$nls_cv_use_gnu_gettext" = "yes"; then
+        dnl Mark actions to use GNU gettext tools.
+        CATOBJEXT=.gmo
+      fi
+    ])
+
+    if test -n "$INTL_MACOSX_LIBS"; then
+      if test "$gt_use_preinstalled_gnugettext" = "yes" \
+         || test "$nls_cv_use_gnu_gettext" = "yes"; then
+        dnl Some extra flags are needed during linking.
+        LIBINTL="$LIBINTL $INTL_MACOSX_LIBS"
+        LTLIBINTL="$LTLIBINTL $INTL_MACOSX_LIBS"
+      fi
+    fi
+
+    if test "$gt_use_preinstalled_gnugettext" = "yes" \
+       || test "$nls_cv_use_gnu_gettext" = "yes"; then
+      AC_DEFINE([ENABLE_NLS], [1],
+        [Define to 1 if translation of program messages to the user's native 
language
+   is requested.])
+    else
+      USE_NLS=no
+    fi
+  fi
+
+  AC_MSG_CHECKING([whether to use NLS])
+  AC_MSG_RESULT([$USE_NLS])
+  if test "$USE_NLS" = "yes"; then
+    AC_MSG_CHECKING([where the gettext function comes from])
+    if test "$gt_use_preinstalled_gnugettext" = "yes"; then
+      if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = 
"yes"; }; then
+        gt_source="external libintl"
+      else
+        gt_source="libc"
+      fi
+    else
+      gt_source="included intl directory"
+    fi
+    AC_MSG_RESULT([$gt_source])
+  fi
+
+  if test "$USE_NLS" = "yes"; then
+
+    if test "$gt_use_preinstalled_gnugettext" = "yes"; then
+      if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = 
"yes"; }; then
+        AC_MSG_CHECKING([how to link with libintl])
+        AC_MSG_RESULT([$LIBINTL])
+        AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCINTL])
+      fi
+
+      dnl For backward compatibility. Some packages may be using this.
+      AC_DEFINE([HAVE_GETTEXT], [1],
+       [Define if the GNU gettext() function is already present or 
preinstalled.])
+      AC_DEFINE([HAVE_DCGETTEXT], [1],
+       [Define if the GNU dcgettext() function is already present or 
preinstalled.])
+    fi
+
+    dnl We need to process the po/ directory.
+    POSUB=po
+  fi
+
+  ifelse(gt_included_intl, yes, [
+    dnl If this is used in GNU gettext we have to set BUILD_INCLUDED_LIBINTL
+    dnl to 'yes' because some of the testsuite requires it.
+    if test "$PACKAGE" = gettext-runtime || test "$PACKAGE" = gettext-tools; 
then
+      BUILD_INCLUDED_LIBINTL=yes
+    fi
+
+    dnl Make all variables we use known to autoconf.
+    AC_SUBST([BUILD_INCLUDED_LIBINTL])
+    AC_SUBST([USE_INCLUDED_LIBINTL])
+    AC_SUBST([CATOBJEXT])
+
+    dnl For backward compatibility. Some configure.ins may be using this.
+    nls_cv_header_intl=
+    nls_cv_header_libgt=
+
+    dnl For backward compatibility. Some Makefiles may be using this.
+    DATADIRNAME=share
+    AC_SUBST([DATADIRNAME])
+
+    dnl For backward compatibility. Some Makefiles may be using this.
+    INSTOBJEXT=.mo
+    AC_SUBST([INSTOBJEXT])
+
+    dnl For backward compatibility. Some Makefiles may be using this.
+    GENCAT=gencat
+    AC_SUBST([GENCAT])
+
+    dnl For backward compatibility. Some Makefiles may be using this.
+    INTLOBJS=
+    if test "$USE_INCLUDED_LIBINTL" = yes; then
+      INTLOBJS="\$(GETTOBJS)"
+    fi
+    AC_SUBST([INTLOBJS])
+
+    dnl Enable libtool support if the surrounding package wishes it.
+    INTL_LIBTOOL_SUFFIX_PREFIX=gt_libtool_suffix_prefix
+    AC_SUBST([INTL_LIBTOOL_SUFFIX_PREFIX])
+  ])
+
+  dnl For backward compatibility. Some Makefiles may be using this.
+  INTLLIBS="$LIBINTL"
+  AC_SUBST([INTLLIBS])
+
+  dnl Make all documented variables known to autoconf.
+  AC_SUBST([LIBINTL])
+  AC_SUBST([LTLIBINTL])
+  AC_SUBST([POSUB])
+])
+
+
+dnl gt_NEEDS_INIT ensures that the gt_needs variable is initialized.
+m4_define([gt_NEEDS_INIT],
+[
+  m4_divert_text([DEFAULTS], [gt_needs=])
+  m4_define([gt_NEEDS_INIT], [])
+])
+
+
+dnl Usage: AM_GNU_GETTEXT_NEED([NEEDSYMBOL])
+AC_DEFUN([AM_GNU_GETTEXT_NEED],
+[
+  m4_divert_text([INIT_PREPARE], [gt_needs="$gt_needs $1"])
+])
+
+
+dnl Usage: AM_GNU_GETTEXT_VERSION([gettext-version])
+AC_DEFUN([AM_GNU_GETTEXT_VERSION], [])
+
+
+dnl Usage: AM_GNU_GETTEXT_REQUIRE_VERSION([gettext-version])
+AC_DEFUN([AM_GNU_GETTEXT_REQUIRE_VERSION], [])
diff --git a/m4/glibc2.m4 b/m4/glibc2.m4
new file mode 100644
index 0000000..785bba0
--- /dev/null
+++ b/m4/glibc2.m4
@@ -0,0 +1,31 @@
+# glibc2.m4 serial 3
+dnl Copyright (C) 2000-2002, 2004, 2008, 2010-2016 Free Software Foundation,
+dnl Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Test for the GNU C Library, version 2.0 or newer.
+# From Bruno Haible.
+
+AC_DEFUN([gt_GLIBC2],
+  [
+    AC_CACHE_CHECK([whether we are using the GNU C Library 2 or newer],
+      [ac_cv_gnu_library_2],
+      [AC_EGREP_CPP([Lucky GNU user],
+        [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if (__GLIBC__ >= 2) && !defined __UCLIBC__
+  Lucky GNU user
+ #endif
+#endif
+        ],
+        [ac_cv_gnu_library_2=yes],
+        [ac_cv_gnu_library_2=no])
+      ]
+    )
+    AC_SUBST([GLIBC2])
+    GLIBC2="$ac_cv_gnu_library_2"
+  ]
+)
diff --git a/m4/glibc21.m4 b/m4/glibc21.m4
new file mode 100644
index 0000000..dafebf5
--- /dev/null
+++ b/m4/glibc21.m4
@@ -0,0 +1,34 @@
+# glibc21.m4 serial 5
+dnl Copyright (C) 2000-2002, 2004, 2008, 2010-2016 Free Software Foundation,
+dnl Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+# Test for the GNU C Library, version 2.1 or newer, or uClibc.
+# From Bruno Haible.
+
+AC_DEFUN([gl_GLIBC21],
+  [
+    AC_CACHE_CHECK([whether we are using the GNU C Library >= 2.1 or uClibc],
+      [ac_cv_gnu_library_2_1],
+      [AC_EGREP_CPP([Lucky],
+        [
+#include <features.h>
+#ifdef __GNU_LIBRARY__
+ #if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 1) || (__GLIBC__ > 2)
+  Lucky GNU user
+ #endif
+#endif
+#ifdef __UCLIBC__
+ Lucky user
+#endif
+        ],
+        [ac_cv_gnu_library_2_1=yes],
+        [ac_cv_gnu_library_2_1=no])
+      ]
+    )
+    AC_SUBST([GLIBC21])
+    GLIBC21="$ac_cv_gnu_library_2_1"
+  ]
+)
diff --git a/m4/iconv.m4 b/m4/iconv.m4
new file mode 100644
index 0000000..aa159c5
--- /dev/null
+++ b/m4/iconv.m4
@@ -0,0 +1,271 @@
+# iconv.m4 serial 19 (gettext-0.18.2)
+dnl Copyright (C) 2000-2002, 2007-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([AM_ICONV_LINKFLAGS_BODY],
+[
+  dnl Prerequisites of AC_LIB_LINKFLAGS_BODY.
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+
+  dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
+  dnl accordingly.
+  AC_LIB_LINKFLAGS_BODY([iconv])
+])
+
+AC_DEFUN([AM_ICONV_LINK],
+[
+  dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and
+  dnl those with the standalone portable GNU libiconv installed).
+  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+
+  dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV
+  dnl accordingly.
+  AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY])
+
+  dnl Add $INCICONV to CPPFLAGS before performing the following checks,
+  dnl because if the user has installed libiconv and not disabled its use
+  dnl via --without-libiconv-prefix, he wants to use it. The first
+  dnl AC_LINK_IFELSE will then fail, the second AC_LINK_IFELSE will succeed.
+  am_save_CPPFLAGS="$CPPFLAGS"
+  AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV])
+
+  AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [
+    am_cv_func_iconv="no, consider installing GNU libiconv"
+    am_cv_lib_iconv=no
+    AC_LINK_IFELSE(
+      [AC_LANG_PROGRAM(
+         [[
+#include <stdlib.h>
+#include <iconv.h>
+         ]],
+         [[iconv_t cd = iconv_open("","");
+           iconv(cd,NULL,NULL,NULL,NULL);
+           iconv_close(cd);]])],
+      [am_cv_func_iconv=yes])
+    if test "$am_cv_func_iconv" != yes; then
+      am_save_LIBS="$LIBS"
+      LIBS="$LIBS $LIBICONV"
+      AC_LINK_IFELSE(
+        [AC_LANG_PROGRAM(
+           [[
+#include <stdlib.h>
+#include <iconv.h>
+           ]],
+           [[iconv_t cd = iconv_open("","");
+             iconv(cd,NULL,NULL,NULL,NULL);
+             iconv_close(cd);]])],
+        [am_cv_lib_iconv=yes]
+        [am_cv_func_iconv=yes])
+      LIBS="$am_save_LIBS"
+    fi
+  ])
+  if test "$am_cv_func_iconv" = yes; then
+    AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [
+      dnl This tests against bugs in AIX 5.1, AIX 6.1..7.1, HP-UX 11.11,
+      dnl Solaris 10.
+      am_save_LIBS="$LIBS"
+      if test $am_cv_lib_iconv = yes; then
+        LIBS="$LIBS $LIBICONV"
+      fi
+      am_cv_func_iconv_works=no
+      for ac_iconv_const in '' 'const'; do
+        AC_RUN_IFELSE(
+          [AC_LANG_PROGRAM(
+             [[
+#include <iconv.h>
+#include <string.h>
+
+#ifndef ICONV_CONST
+# define ICONV_CONST $ac_iconv_const
+#endif
+             ]],
+             [[int result = 0;
+  /* Test against AIX 5.1 bug: Failures are not distinguishable from successful
+     returns.  */
+  {
+    iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8");
+    if (cd_utf8_to_88591 != (iconv_t)(-1))
+      {
+        static ICONV_CONST char input[] = "\342\202\254"; /* EURO SIGN */
+        char buf[10];
+        ICONV_CONST char *inptr = input;
+        size_t inbytesleft = strlen (input);
+        char *outptr = buf;
+        size_t outbytesleft = sizeof (buf);
+        size_t res = iconv (cd_utf8_to_88591,
+                            &inptr, &inbytesleft,
+                            &outptr, &outbytesleft);
+        if (res == 0)
+          result |= 1;
+        iconv_close (cd_utf8_to_88591);
+      }
+  }
+  /* Test against Solaris 10 bug: Failures are not distinguishable from
+     successful returns.  */
+  {
+    iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646");
+    if (cd_ascii_to_88591 != (iconv_t)(-1))
+      {
+        static ICONV_CONST char input[] = "\263";
+        char buf[10];
+        ICONV_CONST char *inptr = input;
+        size_t inbytesleft = strlen (input);
+        char *outptr = buf;
+        size_t outbytesleft = sizeof (buf);
+        size_t res = iconv (cd_ascii_to_88591,
+                            &inptr, &inbytesleft,
+                            &outptr, &outbytesleft);
+        if (res == 0)
+          result |= 2;
+        iconv_close (cd_ascii_to_88591);
+      }
+  }
+  /* Test against AIX 6.1..7.1 bug: Buffer overrun.  */
+  {
+    iconv_t cd_88591_to_utf8 = iconv_open ("UTF-8", "ISO-8859-1");
+    if (cd_88591_to_utf8 != (iconv_t)(-1))
+      {
+        static ICONV_CONST char input[] = "\304";
+        static char buf[2] = { (char)0xDE, (char)0xAD };
+        ICONV_CONST char *inptr = input;
+        size_t inbytesleft = 1;
+        char *outptr = buf;
+        size_t outbytesleft = 1;
+        size_t res = iconv (cd_88591_to_utf8,
+                            &inptr, &inbytesleft,
+                            &outptr, &outbytesleft);
+        if (res != (size_t)(-1) || outptr - buf > 1 || buf[1] != (char)0xAD)
+          result |= 4;
+        iconv_close (cd_88591_to_utf8);
+      }
+  }
+#if 0 /* This bug could be worked around by the caller.  */
+  /* Test against HP-UX 11.11 bug: Positive return value instead of 0.  */
+  {
+    iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591");
+    if (cd_88591_to_utf8 != (iconv_t)(-1))
+      {
+        static ICONV_CONST char input[] = "\304rger mit b\366sen B\374bchen 
ohne Augenma\337";
+        char buf[50];
+        ICONV_CONST char *inptr = input;
+        size_t inbytesleft = strlen (input);
+        char *outptr = buf;
+        size_t outbytesleft = sizeof (buf);
+        size_t res = iconv (cd_88591_to_utf8,
+                            &inptr, &inbytesleft,
+                            &outptr, &outbytesleft);
+        if ((int)res > 0)
+          result |= 8;
+        iconv_close (cd_88591_to_utf8);
+      }
+  }
+#endif
+  /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is
+     provided.  */
+  if (/* Try standardized names.  */
+      iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1)
+      /* Try IRIX, OSF/1 names.  */
+      && iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1)
+      /* Try AIX names.  */
+      && iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1)
+      /* Try HP-UX names.  */
+      && iconv_open ("utf8", "eucJP") == (iconv_t)(-1))
+    result |= 16;
+  return result;
+]])],
+          [am_cv_func_iconv_works=yes], ,
+          [case "$host_os" in
+             aix* | hpux*) am_cv_func_iconv_works="guessing no" ;;
+             *)            am_cv_func_iconv_works="guessing yes" ;;
+           esac])
+        test "$am_cv_func_iconv_works" = no || break
+      done
+      LIBS="$am_save_LIBS"
+    ])
+    case "$am_cv_func_iconv_works" in
+      *no) am_func_iconv=no am_cv_lib_iconv=no ;;
+      *)   am_func_iconv=yes ;;
+    esac
+  else
+    am_func_iconv=no am_cv_lib_iconv=no
+  fi
+  if test "$am_func_iconv" = yes; then
+    AC_DEFINE([HAVE_ICONV], [1],
+      [Define if you have the iconv() function and it works.])
+  fi
+  if test "$am_cv_lib_iconv" = yes; then
+    AC_MSG_CHECKING([how to link with libiconv])
+    AC_MSG_RESULT([$LIBICONV])
+  else
+    dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV
+    dnl either.
+    CPPFLAGS="$am_save_CPPFLAGS"
+    LIBICONV=
+    LTLIBICONV=
+  fi
+  AC_SUBST([LIBICONV])
+  AC_SUBST([LTLIBICONV])
+])
+
+dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to
+dnl avoid warnings like
+dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required".
+dnl This is tricky because of the way 'aclocal' is implemented:
+dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN.
+dnl   Otherwise aclocal's initial scan pass would miss the macro definition.
+dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN 
expansions.
+dnl   Otherwise aclocal would emit many "Use of uninitialized value $1"
+dnl   warnings.
+m4_define([gl_iconv_AC_DEFUN],
+  m4_version_prereq([2.64],
+    [[AC_DEFUN_ONCE(
+        [$1], [$2])]],
+    [m4_ifdef([gl_00GNULIB],
+       [[AC_DEFUN_ONCE(
+           [$1], [$2])]],
+       [[AC_DEFUN(
+           [$1], [$2])]])]))
+gl_iconv_AC_DEFUN([AM_ICONV],
+[
+  AM_ICONV_LINK
+  if test "$am_cv_func_iconv" = yes; then
+    AC_MSG_CHECKING([for iconv declaration])
+    AC_CACHE_VAL([am_cv_proto_iconv], [
+      AC_COMPILE_IFELSE(
+        [AC_LANG_PROGRAM(
+           [[
+#include <stdlib.h>
+#include <iconv.h>
+extern
+#ifdef __cplusplus
+"C"
+#endif
+#if defined(__STDC__) || defined(_MSC_VER) || defined(__cplusplus)
+size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, 
size_t *outbytesleft);
+#else
+size_t iconv();
+#endif
+           ]],
+           [[]])],
+        [am_cv_proto_iconv_arg1=""],
+        [am_cv_proto_iconv_arg1="const"])
+      am_cv_proto_iconv="extern size_t iconv (iconv_t cd, 
$am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, 
size_t *outbytesleft);"])
+    am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( 
/(/'`
+    AC_MSG_RESULT([
+         $am_cv_proto_iconv])
+    AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1],
+      [Define as const if the declaration of iconv() needs const.])
+    dnl Also substitute ICONV_CONST in the gnulib generated <iconv.h>.
+    m4_ifdef([gl_ICONV_H_DEFAULTS],
+      [AC_REQUIRE([gl_ICONV_H_DEFAULTS])
+       if test -n "$am_cv_proto_iconv_arg1"; then
+         ICONV_CONST="const"
+       fi
+      ])
+  fi
+])
diff --git a/m4/intdiv0.m4 b/m4/intdiv0.m4
new file mode 100644
index 0000000..744b99e
--- /dev/null
+++ b/m4/intdiv0.m4
@@ -0,0 +1,87 @@
+# intdiv0.m4 serial 6 (gettext-0.18.2)
+dnl Copyright (C) 2002, 2007-2008, 2010-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([gt_INTDIV0],
+[
+  AC_REQUIRE([AC_PROG_CC])dnl
+  AC_REQUIRE([AC_CANONICAL_HOST])dnl
+
+  AC_CACHE_CHECK([whether integer division by zero raises SIGFPE],
+    gt_cv_int_divbyzero_sigfpe,
+    [
+      gt_cv_int_divbyzero_sigfpe=
+changequote(,)dnl
+      case "$host_os" in
+        macos* | darwin[6-9]* | darwin[1-9][0-9]*)
+          # On Mac OS X 10.2 or newer, just assume the same as when cross-
+          # compiling. If we were to perform the real test, 1 Crash Report
+          # dialog window would pop up.
+          case "$host_cpu" in
+            i[34567]86 | x86_64)
+              gt_cv_int_divbyzero_sigfpe="guessing yes" ;;
+          esac
+          ;;
+      esac
+changequote([,])dnl
+      if test -z "$gt_cv_int_divbyzero_sigfpe"; then
+        AC_RUN_IFELSE(
+          [AC_LANG_SOURCE([[
+#include <stdlib.h>
+#include <signal.h>
+
+static void
+sigfpe_handler (int sig)
+{
+  /* Exit with code 0 if SIGFPE, with code 1 if any other signal.  */
+  _exit (sig != SIGFPE);
+}
+
+int x = 1;
+int y = 0;
+int z;
+int nan;
+
+int main ()
+{
+  signal (SIGFPE, sigfpe_handler);
+/* IRIX and AIX (when "xlc -qcheck" is used) yield signal SIGTRAP.  */
+#if (defined (__sgi) || defined (_AIX)) && defined (SIGTRAP)
+  signal (SIGTRAP, sigfpe_handler);
+#endif
+/* Linux/SPARC yields signal SIGILL.  */
+#if defined (__sparc__) && defined (__linux__)
+  signal (SIGILL, sigfpe_handler);
+#endif
+
+  z = x / y;
+  nan = y / y;
+  exit (2);
+}
+]])],
+          [gt_cv_int_divbyzero_sigfpe=yes],
+          [gt_cv_int_divbyzero_sigfpe=no],
+          [
+            # Guess based on the CPU.
+changequote(,)dnl
+            case "$host_cpu" in
+              alpha* | i[34567]86 | x86_64 | m68k | s390*)
+                gt_cv_int_divbyzero_sigfpe="guessing yes";;
+              *)
+                gt_cv_int_divbyzero_sigfpe="guessing no";;
+            esac
+changequote([,])dnl
+          ])
+      fi
+    ])
+  case "$gt_cv_int_divbyzero_sigfpe" in
+    *yes) value=1;;
+    *) value=0;;
+  esac
+  AC_DEFINE_UNQUOTED([INTDIV0_RAISES_SIGFPE], [$value],
+    [Define if integer division by zero raises signal SIGFPE.])
+])
diff --git a/m4/intl.m4 b/m4/intl.m4
new file mode 100644
index 0000000..42fac95
--- /dev/null
+++ b/m4/intl.m4
@@ -0,0 +1,304 @@
+# intl.m4 serial 29 (gettext-0.19)
+dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl   Bruno Haible <haible@clisp.cons.org>, 2000-2009.
+
+AC_PREREQ([2.60])
+
+dnl Checks for all prerequisites of the intl subdirectory,
+dnl except for INTL_LIBTOOL_SUFFIX_PREFIX (and possibly LIBTOOL), INTLOBJS,
+dnl            USE_INCLUDED_LIBINTL, BUILD_INCLUDED_LIBINTL.
+AC_DEFUN([AM_INTL_SUBDIR],
+[
+  AC_REQUIRE([AC_PROG_INSTALL])dnl
+  AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+  AC_REQUIRE([AC_PROG_CC])dnl
+  AC_REQUIRE([AC_CANONICAL_HOST])dnl
+  AC_REQUIRE([gt_GLIBC2])dnl
+  AC_REQUIRE([AC_PROG_RANLIB])dnl
+  AC_REQUIRE([gl_VISIBILITY])dnl
+  AC_REQUIRE([gt_INTL_SUBDIR_CORE])dnl
+  AC_REQUIRE([AC_TYPE_LONG_LONG_INT])dnl
+  AC_REQUIRE([gt_TYPE_WCHAR_T])dnl
+  AC_REQUIRE([gt_TYPE_WINT_T])dnl
+  AC_REQUIRE([gl_AC_HEADER_INTTYPES_H])
+  AC_REQUIRE([gt_TYPE_INTMAX_T])
+  AC_REQUIRE([gt_PRINTF_POSIX])
+  AC_REQUIRE([gl_GLIBC21])dnl
+  AC_REQUIRE([gl_XSIZE])dnl
+  AC_REQUIRE([gl_FCNTL_O_FLAGS])dnl
+  AC_REQUIRE([gt_INTL_MACOSX])dnl
+  AC_REQUIRE([gl_EXTERN_INLINE])dnl
+  AC_REQUIRE([gt_GL_ATTRIBUTE])dnl
+
+  dnl Support for automake's --enable-silent-rules.
+  case "$enable_silent_rules" in
+    yes) INTL_DEFAULT_VERBOSITY=0;;
+    no)  INTL_DEFAULT_VERBOSITY=1;;
+    *)   INTL_DEFAULT_VERBOSITY=1;;
+  esac
+  AC_SUBST([INTL_DEFAULT_VERBOSITY])
+
+  AC_CHECK_TYPE([ptrdiff_t], ,
+    [AC_DEFINE([ptrdiff_t], [long],
+       [Define as the type of the result of subtracting two pointers, if the 
system doesn't define it.])
+    ])
+  AC_CHECK_HEADERS([features.h stddef.h stdlib.h string.h])
+  AC_CHECK_FUNCS([asprintf fwprintf newlocale putenv setenv setlocale \
+    snprintf strnlen wcslen wcsnlen mbrtowc wcrtomb])
+
+  dnl Use the _snprintf function only if it is declared (because on NetBSD it
+  dnl is defined as a weak alias of snprintf; we prefer to use the latter).
+  AC_CHECK_DECLS([_snprintf, _snwprintf], , , [#include <stdio.h>])
+
+  dnl Use the *_unlocked functions only if they are declared.
+  dnl (because some of them were defined without being declared in Solaris
+  dnl 2.5.1 but were removed in Solaris 2.6, whereas we want binaries built
+  dnl on Solaris 2.5.1 to run on Solaris 2.6).
+  AC_CHECK_DECLS([getc_unlocked], , , [#include <stdio.h>])
+
+  case $gt_cv_func_printf_posix in
+    *yes) HAVE_POSIX_PRINTF=1 ;;
+    *) HAVE_POSIX_PRINTF=0 ;;
+  esac
+  AC_SUBST([HAVE_POSIX_PRINTF])
+  if test "$ac_cv_func_asprintf" = yes; then
+    HAVE_ASPRINTF=1
+  else
+    HAVE_ASPRINTF=0
+  fi
+  AC_SUBST([HAVE_ASPRINTF])
+  if test "$ac_cv_func_snprintf" = yes; then
+    HAVE_SNPRINTF=1
+  else
+    HAVE_SNPRINTF=0
+  fi
+  AC_SUBST([HAVE_SNPRINTF])
+  if test "$ac_cv_func_newlocale" = yes; then
+    HAVE_NEWLOCALE=1
+  else
+    HAVE_NEWLOCALE=0
+  fi
+  AC_SUBST([HAVE_NEWLOCALE])
+  if test "$ac_cv_func_wprintf" = yes; then
+    HAVE_WPRINTF=1
+  else
+    HAVE_WPRINTF=0
+  fi
+  AC_SUBST([HAVE_WPRINTF])
+
+  AM_LANGINFO_CODESET
+  gt_LC_MESSAGES
+
+  dnl Compilation on mingw and Cygwin needs special Makefile rules, because
+  dnl 1. when we install a shared library, we must arrange to export
+  dnl    auxiliary pointer variables for every exported variable,
+  dnl 2. when we install a shared library and a static library simultaneously,
+  dnl    the include file specifies __declspec(dllimport) and therefore we
+  dnl    must arrange to define the auxiliary pointer variables for the
+  dnl    exported variables _also_ in the static library.
+  if test "$enable_shared" = yes; then
+    case "$host_os" in
+      mingw* | cygwin*) is_woe32dll=yes ;;
+      *) is_woe32dll=no ;;
+    esac
+  else
+    is_woe32dll=no
+  fi
+  WOE32DLL=$is_woe32dll
+  AC_SUBST([WOE32DLL])
+
+  dnl On mingw and Cygwin, we can activate special Makefile rules which add
+  dnl version information to the shared libraries and executables.
+  case "$host_os" in
+    mingw* | cygwin*) is_woe32=yes ;;
+    *) is_woe32=no ;;
+  esac
+  WOE32=$is_woe32
+  AC_SUBST([WOE32])
+  if test $WOE32 = yes; then
+    dnl Check for a program that compiles Windows resource files.
+    AC_CHECK_TOOL([WINDRES], [windres])
+  fi
+
+  dnl Determine whether when creating a library, "-lc" should be passed to
+  dnl libtool or not. On many platforms, it is required for the libtool option
+  dnl -no-undefined to work. On HP-UX, however, the -lc - stored by libtool
+  dnl in the *.la files - makes it impossible to create multithreaded programs,
+  dnl because libtool also reorders the -lc to come before the -pthread, and
+  dnl this disables pthread_create() 
<http://docs.hp.com/en/1896/pthreads.html>.
+  case "$host_os" in
+    hpux*) LTLIBC="" ;;
+    *)     LTLIBC="-lc" ;;
+  esac
+  AC_SUBST([LTLIBC])
+
+  dnl Rename some macros and functions used for locking.
+  AH_BOTTOM([
+#define __libc_lock_t                   gl_lock_t
+#define __libc_lock_define              gl_lock_define
+#define __libc_lock_define_initialized  gl_lock_define_initialized
+#define __libc_lock_init                gl_lock_init
+#define __libc_lock_lock                gl_lock_lock
+#define __libc_lock_unlock              gl_lock_unlock
+#define __libc_lock_recursive_t                   gl_recursive_lock_t
+#define __libc_lock_define_recursive              gl_recursive_lock_define
+#define __libc_lock_define_initialized_recursive  
gl_recursive_lock_define_initialized
+#define __libc_lock_init_recursive                gl_recursive_lock_init
+#define __libc_lock_lock_recursive                gl_recursive_lock_lock
+#define __libc_lock_unlock_recursive              gl_recursive_lock_unlock
+#define glthread_in_use  libintl_thread_in_use
+#define glthread_lock_init_func     libintl_lock_init_func
+#define glthread_lock_lock_func     libintl_lock_lock_func
+#define glthread_lock_unlock_func   libintl_lock_unlock_func
+#define glthread_lock_destroy_func  libintl_lock_destroy_func
+#define glthread_rwlock_init_multithreaded     
libintl_rwlock_init_multithreaded
+#define glthread_rwlock_init_func              libintl_rwlock_init_func
+#define glthread_rwlock_rdlock_multithreaded   
libintl_rwlock_rdlock_multithreaded
+#define glthread_rwlock_rdlock_func            libintl_rwlock_rdlock_func
+#define glthread_rwlock_wrlock_multithreaded   
libintl_rwlock_wrlock_multithreaded
+#define glthread_rwlock_wrlock_func            libintl_rwlock_wrlock_func
+#define glthread_rwlock_unlock_multithreaded   
libintl_rwlock_unlock_multithreaded
+#define glthread_rwlock_unlock_func            libintl_rwlock_unlock_func
+#define glthread_rwlock_destroy_multithreaded  
libintl_rwlock_destroy_multithreaded
+#define glthread_rwlock_destroy_func           libintl_rwlock_destroy_func
+#define glthread_recursive_lock_init_multithreaded     
libintl_recursive_lock_init_multithreaded
+#define glthread_recursive_lock_init_func              
libintl_recursive_lock_init_func
+#define glthread_recursive_lock_lock_multithreaded     
libintl_recursive_lock_lock_multithreaded
+#define glthread_recursive_lock_lock_func              
libintl_recursive_lock_lock_func
+#define glthread_recursive_lock_unlock_multithreaded   
libintl_recursive_lock_unlock_multithreaded
+#define glthread_recursive_lock_unlock_func            
libintl_recursive_lock_unlock_func
+#define glthread_recursive_lock_destroy_multithreaded  
libintl_recursive_lock_destroy_multithreaded
+#define glthread_recursive_lock_destroy_func           
libintl_recursive_lock_destroy_func
+#define glthread_once_func            libintl_once_func
+#define glthread_once_singlethreaded  libintl_once_singlethreaded
+#define glthread_once_multithreaded   libintl_once_multithreaded
+])
+])
+
+
+dnl Checks for the core files of the intl subdirectory:
+dnl   dcigettext.c
+dnl   eval-plural.h
+dnl   explodename.c
+dnl   finddomain.c
+dnl   gettextP.h
+dnl   gmo.h
+dnl   hash-string.h hash-string.c
+dnl   l10nflist.c
+dnl   libgnuintl.h.in (except the *printf stuff)
+dnl   loadinfo.h
+dnl   loadmsgcat.c
+dnl   localealias.c
+dnl   log.c
+dnl   plural-exp.h plural-exp.c
+dnl   plural.y
+dnl Used by libglocale.
+AC_DEFUN([gt_INTL_SUBDIR_CORE],
+[
+  AC_REQUIRE([AC_C_INLINE])dnl
+  AC_REQUIRE([AC_TYPE_SIZE_T])dnl
+  AC_REQUIRE([gl_AC_HEADER_STDINT_H])
+  AC_REQUIRE([AC_FUNC_ALLOCA])dnl
+  AC_REQUIRE([AC_FUNC_MMAP])dnl
+  AC_REQUIRE([gt_INTDIV0])dnl
+  AC_REQUIRE([gl_AC_TYPE_UINTMAX_T])dnl
+  AC_REQUIRE([gt_INTTYPES_PRI])dnl
+  AC_REQUIRE([gl_LOCK])dnl
+
+  AC_LINK_IFELSE(
+    [AC_LANG_PROGRAM(
+       [[int foo (int a) { a = __builtin_expect (a, 10); return a == 10 ? 0 : 
1; }]],
+       [[]])],
+    [AC_DEFINE([HAVE_BUILTIN_EXPECT], [1],
+       [Define to 1 if the compiler understands __builtin_expect.])])
+
+  AC_CHECK_HEADERS([argz.h inttypes.h limits.h unistd.h sys/param.h])
+  AC_CHECK_FUNCS([getcwd getegid geteuid getgid getuid mempcpy munmap \
+    stpcpy strcasecmp strdup strtoul tsearch uselocale argz_count \
+    argz_stringify argz_next __fsetlocking])
+
+  dnl Solaris 12 provides getlocalename_l, while Illumos doesn't have
+  dnl it nor the equivalent.
+  if test $ac_cv_func_uselocale = yes; then
+    AC_CHECK_FUNCS([getlocalename_l])
+  fi
+
+  dnl Use the *_unlocked functions only if they are declared.
+  dnl (because some of them were defined without being declared in Solaris
+  dnl 2.5.1 but were removed in Solaris 2.6, whereas we want binaries built
+  dnl on Solaris 2.5.1 to run on Solaris 2.6).
+  AC_CHECK_DECLS([feof_unlocked, fgets_unlocked], , , [#include <stdio.h>])
+
+  AM_ICONV
+
+  dnl intl/plural.c is generated from intl/plural.y. It requires bison,
+  dnl because plural.y uses bison specific features. It requires at least
+  dnl bison-2.7 for %define api.pure.
+  dnl bison is only needed for the maintainer (who touches plural.y). But in
+  dnl order to avoid separate Makefiles or --enable-maintainer-mode, we put
+  dnl the rule in general Makefile. Now, some people carelessly touch the
+  dnl files or have a broken "make" program, hence the plural.c rule will
+  dnl sometimes fire. To avoid an error, defines BISON to ":" if it is not
+  dnl present or too old.
+  AC_CHECK_PROGS([INTLBISON], [bison])
+  if test -z "$INTLBISON"; then
+    ac_verc_fail=yes
+  else
+    dnl Found it, now check the version.
+    AC_MSG_CHECKING([version of bison])
+changequote(<<,>>)dnl
+    ac_prog_version=`$INTLBISON --version 2>&1 | sed -n 's/^.*GNU Bison.* 
\([0-9]*\.[0-9.]*\).*$/\1/p'`
+    case $ac_prog_version in
+      '') ac_prog_version="v. ?.??, bad"; ac_verc_fail=yes;;
+      2.[7-9]* | [3-9].*)
+changequote([,])dnl
+         ac_prog_version="$ac_prog_version, ok"; ac_verc_fail=no;;
+      *) ac_prog_version="$ac_prog_version, bad"; ac_verc_fail=yes;;
+    esac
+    AC_MSG_RESULT([$ac_prog_version])
+  fi
+  if test $ac_verc_fail = yes; then
+    INTLBISON=:
+  fi
+])
+
+dnl Copies _GL_UNUSED and _GL_ATTRIBUTE_PURE definitions from
+dnl gnulib-common.m4 as a fallback, if the project isn't using Gnulib.
+AC_DEFUN([gt_GL_ATTRIBUTE], [
+  m4_ifndef([gl_[]COMMON],
+    AH_VERBATIM([gt_gl_attribute],
+[/* Define as a marker that can be attached to declarations that might not
+    be used.  This helps to reduce warnings, such as from
+    GCC -Wunused-parameter.  */
+#ifndef _GL_UNUSED
+# if __GNUC__ >= 3 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 7)
+#  define _GL_UNUSED __attribute__ ((__unused__))
+# else
+#  define _GL_UNUSED
+# endif
+#endif
+
+/* The __pure__ attribute was added in gcc 2.96.  */
+#ifndef _GL_ATTRIBUTE_PURE
+# if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 96)
+#  define _GL_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+#  define _GL_ATTRIBUTE_PURE /* empty */
+# endif
+#endif
+]))])
diff --git a/m4/intldir.m4 b/m4/intldir.m4
new file mode 100644
index 0000000..c688f46
--- /dev/null
+++ b/m4/intldir.m4
@@ -0,0 +1,19 @@
+# intldir.m4 serial 2 (gettext-0.18)
+dnl Copyright (C) 2006, 2009-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+AC_PREREQ([2.52])
+
+dnl Tells the AM_GNU_GETTEXT macro to consider an intl/ directory.
+AC_DEFUN([AM_GNU_GETTEXT_INTL_SUBDIR], [])
diff --git a/m4/intlmacosx.m4 b/m4/intlmacosx.m4
new file mode 100644
index 0000000..aca924c
--- /dev/null
+++ b/m4/intlmacosx.m4
@@ -0,0 +1,56 @@
+# intlmacosx.m4 serial 5 (gettext-0.18.2)
+dnl Copyright (C) 2004-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Checks for special options needed on Mac OS X.
+dnl Defines INTL_MACOSX_LIBS.
+AC_DEFUN([gt_INTL_MACOSX],
+[
+  dnl Check for API introduced in Mac OS X 10.2.
+  AC_CACHE_CHECK([for CFPreferencesCopyAppValue],
+    [gt_cv_func_CFPreferencesCopyAppValue],
+    [gt_save_LIBS="$LIBS"
+     LIBS="$LIBS -Wl,-framework -Wl,CoreFoundation"
+     AC_LINK_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <CoreFoundation/CFPreferences.h>]],
+          [[CFPreferencesCopyAppValue(NULL, NULL)]])],
+       [gt_cv_func_CFPreferencesCopyAppValue=yes],
+       [gt_cv_func_CFPreferencesCopyAppValue=no])
+     LIBS="$gt_save_LIBS"])
+  if test $gt_cv_func_CFPreferencesCopyAppValue = yes; then
+    AC_DEFINE([HAVE_CFPREFERENCESCOPYAPPVALUE], [1],
+      [Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue 
in the CoreFoundation framework.])
+  fi
+  dnl Check for API introduced in Mac OS X 10.3.
+  AC_CACHE_CHECK([for CFLocaleCopyCurrent], [gt_cv_func_CFLocaleCopyCurrent],
+    [gt_save_LIBS="$LIBS"
+     LIBS="$LIBS -Wl,-framework -Wl,CoreFoundation"
+     AC_LINK_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <CoreFoundation/CFLocale.h>]],
+          [[CFLocaleCopyCurrent();]])],
+       [gt_cv_func_CFLocaleCopyCurrent=yes],
+       [gt_cv_func_CFLocaleCopyCurrent=no])
+     LIBS="$gt_save_LIBS"])
+  if test $gt_cv_func_CFLocaleCopyCurrent = yes; then
+    AC_DEFINE([HAVE_CFLOCALECOPYCURRENT], [1],
+      [Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in 
the CoreFoundation framework.])
+  fi
+  INTL_MACOSX_LIBS=
+  if test $gt_cv_func_CFPreferencesCopyAppValue = yes || test 
$gt_cv_func_CFLocaleCopyCurrent = yes; then
+    INTL_MACOSX_LIBS="-Wl,-framework -Wl,CoreFoundation"
+  fi
+  AC_SUBST([INTL_MACOSX_LIBS])
+])
diff --git a/m4/intmax.m4 b/m4/intmax.m4
new file mode 100644
index 0000000..1a47107
--- /dev/null
+++ b/m4/intmax.m4
@@ -0,0 +1,36 @@
+# intmax.m4 serial 6 (gettext-0.18.2)
+dnl Copyright (C) 2002-2005, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+dnl Test whether the system has the 'intmax_t' type, but don't attempt to
+dnl find a replacement if it is lacking.
+
+AC_DEFUN([gt_TYPE_INTMAX_T],
+[
+  AC_REQUIRE([gl_AC_HEADER_INTTYPES_H])
+  AC_REQUIRE([gl_AC_HEADER_STDINT_H])
+  AC_CACHE_CHECK([for intmax_t], [gt_cv_c_intmax_t],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[
+#include <stddef.h>
+#include <stdlib.h>
+#if HAVE_STDINT_H_WITH_UINTMAX
+#include <stdint.h>
+#endif
+#if HAVE_INTTYPES_H_WITH_UINTMAX
+#include <inttypes.h>
+#endif
+          ]],
+          [[intmax_t x = -1;
+            return !x;]])],
+       [gt_cv_c_intmax_t=yes],
+       [gt_cv_c_intmax_t=no])])
+  if test $gt_cv_c_intmax_t = yes; then
+    AC_DEFINE([HAVE_INTMAX_T], [1],
+      [Define if you have the 'intmax_t' type in <stdint.h> or <inttypes.h>.])
+  fi
+])
diff --git a/m4/inttypes-pri.m4 b/m4/inttypes-pri.m4
new file mode 100644
index 0000000..ae20183
--- /dev/null
+++ b/m4/inttypes-pri.m4
@@ -0,0 +1,42 @@
+# inttypes-pri.m4 serial 7 (gettext-0.18.2)
+dnl Copyright (C) 1997-2002, 2006, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_PREREQ([2.53])
+
+# Define PRI_MACROS_BROKEN if <inttypes.h> exists and defines the PRI*
+# macros to non-string values.  This is the case on AIX 4.3.3.
+
+AC_DEFUN([gt_INTTYPES_PRI],
+[
+  AC_CHECK_HEADERS([inttypes.h])
+  if test $ac_cv_header_inttypes_h = yes; then
+    AC_CACHE_CHECK([whether the inttypes.h PRIxNN macros are broken],
+      [gt_cv_inttypes_pri_broken],
+      [
+        AC_COMPILE_IFELSE(
+          [AC_LANG_PROGRAM(
+             [[
+#include <inttypes.h>
+#ifdef PRId32
+char *p = PRId32;
+#endif
+             ]],
+             [[]])],
+          [gt_cv_inttypes_pri_broken=no],
+          [gt_cv_inttypes_pri_broken=yes])
+      ])
+  fi
+  if test "$gt_cv_inttypes_pri_broken" = yes; then
+    AC_DEFINE_UNQUOTED([PRI_MACROS_BROKEN], [1],
+      [Define if <inttypes.h> exists and defines unusable PRI* macros.])
+    PRI_MACROS_BROKEN=1
+  else
+    PRI_MACROS_BROKEN=0
+  fi
+  AC_SUBST([PRI_MACROS_BROKEN])
+])
diff --git a/m4/inttypes_h.m4 b/m4/inttypes_h.m4
new file mode 100644
index 0000000..7657119
--- /dev/null
+++ b/m4/inttypes_h.m4
@@ -0,0 +1,29 @@
+# inttypes_h.m4 serial 10
+dnl Copyright (C) 1997-2004, 2006, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Paul Eggert.
+
+# Define HAVE_INTTYPES_H_WITH_UINTMAX if <inttypes.h> exists,
+# doesn't clash with <sys/types.h>, and declares uintmax_t.
+
+AC_DEFUN([gl_AC_HEADER_INTTYPES_H],
+[
+  AC_CACHE_CHECK([for inttypes.h], [gl_cv_header_inttypes_h],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[
+#include <sys/types.h>
+#include <inttypes.h>
+          ]],
+          [[uintmax_t i = (uintmax_t) -1; return !i;]])],
+       [gl_cv_header_inttypes_h=yes],
+       [gl_cv_header_inttypes_h=no])])
+  if test $gl_cv_header_inttypes_h = yes; then
+    AC_DEFINE_UNQUOTED([HAVE_INTTYPES_H_WITH_UINTMAX], [1],
+      [Define if <inttypes.h> exists, doesn't clash with <sys/types.h>,
+       and declares uintmax_t. ])
+  fi
+])
diff --git a/m4/lcmessage.m4 b/m4/lcmessage.m4
new file mode 100644
index 0000000..1c24d6d
--- /dev/null
+++ b/m4/lcmessage.m4
@@ -0,0 +1,35 @@
+# lcmessage.m4 serial 7 (gettext-0.18.2)
+dnl Copyright (C) 1995-2002, 2004-2005, 2008-2014, 2016 Free Software
+dnl Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1995.
+
+# Check whether LC_MESSAGES is available in <locale.h>.
+
+AC_DEFUN([gt_LC_MESSAGES],
+[
+  AC_CACHE_CHECK([for LC_MESSAGES], [gt_cv_val_LC_MESSAGES],
+    [AC_LINK_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <locale.h>]],
+          [[return LC_MESSAGES]])],
+       [gt_cv_val_LC_MESSAGES=yes],
+       [gt_cv_val_LC_MESSAGES=no])])
+  if test $gt_cv_val_LC_MESSAGES = yes; then
+    AC_DEFINE([HAVE_LC_MESSAGES], [1],
+      [Define if your <locale.h> file defines LC_MESSAGES.])
+  fi
+])
diff --git a/m4/lib-ld.m4 b/m4/lib-ld.m4
new file mode 100644
index 0000000..6209de6
--- /dev/null
+++ b/m4/lib-ld.m4
@@ -0,0 +1,119 @@
+# lib-ld.m4 serial 6
+dnl Copyright (C) 1996-2003, 2009-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl Subroutines of libtool.m4,
+dnl with replacements s/_*LT_PATH/AC_LIB_PROG/ and s/lt_/acl_/ to avoid
+dnl collision with libtool.m4.
+
+dnl From libtool-2.4. Sets the variable with_gnu_ld to yes or no.
+AC_DEFUN([AC_LIB_PROG_LD_GNU],
+[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld],
+[# I'd rather use --version here, but apparently some GNU lds only accept -v.
+case `$LD -v 2>&1 </dev/null` in
+*GNU* | *'with BFD'*)
+  acl_cv_prog_gnu_ld=yes
+  ;;
+*)
+  acl_cv_prog_gnu_ld=no
+  ;;
+esac])
+with_gnu_ld=$acl_cv_prog_gnu_ld
+])
+
+dnl From libtool-2.4. Sets the variable LD.
+AC_DEFUN([AC_LIB_PROG_LD],
+[AC_REQUIRE([AC_PROG_CC])dnl
+AC_REQUIRE([AC_CANONICAL_HOST])dnl
+
+AC_ARG_WITH([gnu-ld],
+    [AS_HELP_STRING([--with-gnu-ld],
+        [assume the C compiler uses GNU ld [default=no]])],
+    [test "$withval" = no || with_gnu_ld=yes],
+    [with_gnu_ld=no])dnl
+
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  # Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
+  # contains only /bin. Note that ksh looks also at the FPATH variable,
+  # so we have to set that as well for the test.
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+    && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+           || PATH_SEPARATOR=';'
+       }
+fi
+
+ac_prog=ld
+if test "$GCC" = yes; then
+  # Check if gcc -print-prog-name=ld gives a path.
+  AC_MSG_CHECKING([for ld used by $CC])
+  case $host in
+  *-*-mingw*)
+    # gcc leaves a trailing carriage return which upsets mingw
+    ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;;
+  *)
+    ac_prog=`($CC -print-prog-name=ld) 2>&5` ;;
+  esac
+  case $ac_prog in
+    # Accept absolute paths.
+    [[\\/]]* | ?:[[\\/]]*)
+      re_direlt='/[[^/]][[^/]]*/\.\./'
+      # Canonicalize the pathname of ld
+      ac_prog=`echo "$ac_prog"| sed 's%\\\\%/%g'`
+      while echo "$ac_prog" | grep "$re_direlt" > /dev/null 2>&1; do
+        ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"`
+      done
+      test -z "$LD" && LD="$ac_prog"
+      ;;
+  "")
+    # If it fails, then pretend we aren't using GCC.
+    ac_prog=ld
+    ;;
+  *)
+    # If it is relative, then search for the first ld in PATH.
+    with_gnu_ld=unknown
+    ;;
+  esac
+elif test "$with_gnu_ld" = yes; then
+  AC_MSG_CHECKING([for GNU ld])
+else
+  AC_MSG_CHECKING([for non-GNU ld])
+fi
+AC_CACHE_VAL([acl_cv_path_LD],
+[if test -z "$LD"; then
+  acl_save_ifs="$IFS"; IFS=$PATH_SEPARATOR
+  for ac_dir in $PATH; do
+    IFS="$acl_save_ifs"
+    test -z "$ac_dir" && ac_dir=.
+    if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then
+      acl_cv_path_LD="$ac_dir/$ac_prog"
+      # Check to see if the program is GNU ld.  I'd rather use --version,
+      # but apparently some variants of GNU ld only accept -v.
+      # Break only if it was the GNU/non-GNU ld that we prefer.
+      case `"$acl_cv_path_LD" -v 2>&1 </dev/null` in
+      *GNU* | *'with BFD'*)
+        test "$with_gnu_ld" != no && break
+        ;;
+      *)
+        test "$with_gnu_ld" != yes && break
+        ;;
+      esac
+    fi
+  done
+  IFS="$acl_save_ifs"
+else
+  acl_cv_path_LD="$LD" # Let the user override the test with a path.
+fi])
+LD="$acl_cv_path_LD"
+if test -n "$LD"; then
+  AC_MSG_RESULT([$LD])
+else
+  AC_MSG_RESULT([no])
+fi
+test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH])
+AC_LIB_PROG_LD_GNU
+])
diff --git a/m4/lib-link.m4 b/m4/lib-link.m4
new file mode 100644
index 0000000..2f51855
--- /dev/null
+++ b/m4/lib-link.m4
@@ -0,0 +1,777 @@
+# lib-link.m4 serial 26 (gettext-0.18.2)
+dnl Copyright (C) 2001-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_PREREQ([2.54])
+
+dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and
+dnl augments the CPPFLAGS variable.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+  pushdef([Name],[m4_translit([$1],[./+-], [____])])
+  pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+                                   [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+  AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [
+    AC_LIB_LINKFLAGS_BODY([$1], [$2])
+    ac_cv_lib[]Name[]_libs="$LIB[]NAME"
+    ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME"
+    ac_cv_lib[]Name[]_cppflags="$INC[]NAME"
+    ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX"
+  ])
+  LIB[]NAME="$ac_cv_lib[]Name[]_libs"
+  LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs"
+  INC[]NAME="$ac_cv_lib[]Name[]_cppflags"
+  LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix"
+  AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+  AC_SUBST([LIB]NAME)
+  AC_SUBST([LTLIB]NAME)
+  AC_SUBST([LIB]NAME[_PREFIX])
+  dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the
+  dnl results of this search when this library appears as a dependency.
+  HAVE_LIB[]NAME=yes
+  popdef([NAME])
+  popdef([Name])
+])
+
+dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode, 
[missing-message])
+dnl searches for libname and the libraries corresponding to explicit and
+dnl implicit dependencies, together with the specified include files and
+dnl the ability to compile and link the specified testcode. The missing-message
+dnl defaults to 'no' and may contain additional hints for the user.
+dnl If found, it sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME}
+dnl and LTLIB${NAME} variables and augments the CPPFLAGS variable, and
+dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs
+dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty.
+dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname
+dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_HAVE_LINKFLAGS],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  AC_REQUIRE([AC_LIB_RPATH])
+  pushdef([Name],[m4_translit([$1],[./+-], [____])])
+  pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+                                   [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+
+  dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME
+  dnl accordingly.
+  AC_LIB_LINKFLAGS_BODY([$1], [$2])
+
+  dnl Add $INC[]NAME to CPPFLAGS before performing the following checks,
+  dnl because if the user has installed lib[]Name and not disabled its use
+  dnl via --without-lib[]Name-prefix, he wants to use it.
+  ac_save_CPPFLAGS="$CPPFLAGS"
+  AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME)
+
+  AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [
+    ac_save_LIBS="$LIBS"
+    dnl If $LIB[]NAME contains some -l options, add it to the end of LIBS,
+    dnl because these -l options might require -L options that are present in
+    dnl LIBS. -l options benefit only from the -L options listed before it.
+    dnl Otherwise, add it to the front of LIBS, because it may be a static
+    dnl library that depends on another static library that is present in LIBS.
+    dnl Static libraries benefit only from the static libraries listed after
+    dnl it.
+    case " $LIB[]NAME" in
+      *" -l"*) LIBS="$LIBS $LIB[]NAME" ;;
+      *)       LIBS="$LIB[]NAME $LIBS" ;;
+    esac
+    AC_LINK_IFELSE(
+      [AC_LANG_PROGRAM([[$3]], [[$4]])],
+      [ac_cv_lib[]Name=yes],
+      [ac_cv_lib[]Name='m4_if([$5], [], [no], [[$5]])'])
+    LIBS="$ac_save_LIBS"
+  ])
+  if test "$ac_cv_lib[]Name" = yes; then
+    HAVE_LIB[]NAME=yes
+    AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the lib][$1 library.])
+    AC_MSG_CHECKING([how to link with lib[]$1])
+    AC_MSG_RESULT([$LIB[]NAME])
+  else
+    HAVE_LIB[]NAME=no
+    dnl If $LIB[]NAME didn't lead to a usable library, we don't need
+    dnl $INC[]NAME either.
+    CPPFLAGS="$ac_save_CPPFLAGS"
+    LIB[]NAME=
+    LTLIB[]NAME=
+    LIB[]NAME[]_PREFIX=
+  fi
+  AC_SUBST([HAVE_LIB]NAME)
+  AC_SUBST([LIB]NAME)
+  AC_SUBST([LTLIB]NAME)
+  AC_SUBST([LIB]NAME[_PREFIX])
+  popdef([NAME])
+  popdef([Name])
+])
+
+dnl Determine the platform dependent parameters needed to use rpath:
+dnl   acl_libext,
+dnl   acl_shlibext,
+dnl   acl_libname_spec,
+dnl   acl_library_names_spec,
+dnl   acl_hardcode_libdir_flag_spec,
+dnl   acl_hardcode_libdir_separator,
+dnl   acl_hardcode_direct,
+dnl   acl_hardcode_minus_L.
+AC_DEFUN([AC_LIB_RPATH],
+[
+  dnl Tell automake >= 1.10 to complain if config.rpath is missing.
+  m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])])
+  AC_REQUIRE([AC_PROG_CC])                dnl we use $CC, $GCC, $LDFLAGS
+  AC_REQUIRE([AC_LIB_PROG_LD])            dnl we use $LD, $with_gnu_ld
+  AC_REQUIRE([AC_CANONICAL_HOST])         dnl we use $host
+  AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir
+  AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [
+    CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" 
\
+    ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh
+    . ./conftest.sh
+    rm -f ./conftest.sh
+    acl_cv_rpath=done
+  ])
+  wl="$acl_cv_wl"
+  acl_libext="$acl_cv_libext"
+  acl_shlibext="$acl_cv_shlibext"
+  acl_libname_spec="$acl_cv_libname_spec"
+  acl_library_names_spec="$acl_cv_library_names_spec"
+  acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec"
+  acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator"
+  acl_hardcode_direct="$acl_cv_hardcode_direct"
+  acl_hardcode_minus_L="$acl_cv_hardcode_minus_L"
+  dnl Determine whether the user wants rpath handling at all.
+  AC_ARG_ENABLE([rpath],
+    [  --disable-rpath         do not hardcode runtime library paths],
+    :, enable_rpath=yes)
+])
+
+dnl AC_LIB_FROMPACKAGE(name, package)
+dnl declares that libname comes from the given package. The configure file
+dnl will then not have a --with-libname-prefix option but a
+dnl --with-package-prefix option. Several libraries can come from the same
+dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar
+dnl macro call that searches for libname.
+AC_DEFUN([AC_LIB_FROMPACKAGE],
+[
+  pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+                                   [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+  define([acl_frompackage_]NAME, [$2])
+  popdef([NAME])
+  pushdef([PACK],[$2])
+  pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
+                                     [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+  define([acl_libsinpackage_]PACKUP,
+    m4_ifdef([acl_libsinpackage_]PACKUP, 
[m4_defn([acl_libsinpackage_]PACKUP)[, ]],)[lib$1])
+  popdef([PACKUP])
+  popdef([PACK])
+])
+
+dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and
+dnl the libraries corresponding to explicit and implicit dependencies.
+dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables.
+dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found
+dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem.
+AC_DEFUN([AC_LIB_LINKFLAGS_BODY],
+[
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  pushdef([NAME],[m4_translit([$1],[abcdefghijklmnopqrstuvwxyz./+-],
+                                   [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+  pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, 
lib[$1])])
+  pushdef([PACKUP],[m4_translit(PACK,[abcdefghijklmnopqrstuvwxyz./+-],
+                                     [ABCDEFGHIJKLMNOPQRSTUVWXYZ____])])
+  pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, 
[acl_libsinpackage_]PACKUP, lib[$1])])
+  dnl Autoconf >= 2.61 supports dots in --with options.
+  
pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[m4_translit(PACK,[.],[_])],PACK)])
+  dnl By default, look in $includedir and $libdir.
+  use_additional=yes
+  AC_LIB_WITH_FINAL_PREFIX([
+    eval additional_includedir=\"$includedir\"
+    eval additional_libdir=\"$libdir\"
+  ])
+  AC_ARG_WITH(P_A_C_K[-prefix],
+[[  --with-]]P_A_C_K[[-prefix[=DIR]  search for ]PACKLIBS[ in DIR/include and 
DIR/lib
+  --without-]]P_A_C_K[[-prefix     don't search for ]PACKLIBS[ in includedir 
and libdir]],
+[
+    if test "X$withval" = "Xno"; then
+      use_additional=no
+    else
+      if test "X$withval" = "X"; then
+        AC_LIB_WITH_FINAL_PREFIX([
+          eval additional_includedir=\"$includedir\"
+          eval additional_libdir=\"$libdir\"
+        ])
+      else
+        additional_includedir="$withval/include"
+        additional_libdir="$withval/$acl_libdirstem"
+        if test "$acl_libdirstem2" != "$acl_libdirstem" \
+           && ! test -d "$withval/$acl_libdirstem"; then
+          additional_libdir="$withval/$acl_libdirstem2"
+        fi
+      fi
+    fi
+])
+  dnl Search the library and its dependencies in $additional_libdir and
+  dnl $LDFLAGS. Using breadth-first-seach.
+  LIB[]NAME=
+  LTLIB[]NAME=
+  INC[]NAME=
+  LIB[]NAME[]_PREFIX=
+  dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been
+  dnl computed. So it has to be reset here.
+  HAVE_LIB[]NAME=
+  rpathdirs=
+  ltrpathdirs=
+  names_already_handled=
+  names_next_round='$1 $2'
+  while test -n "$names_next_round"; do
+    names_this_round="$names_next_round"
+    names_next_round=
+    for name in $names_this_round; do
+      already_handled=
+      for n in $names_already_handled; do
+        if test "$n" = "$name"; then
+          already_handled=yes
+          break
+        fi
+      done
+      if test -z "$already_handled"; then
+        names_already_handled="$names_already_handled $name"
+        dnl See if it was already located by an earlier AC_LIB_LINKFLAGS
+        dnl or AC_LIB_HAVE_LINKFLAGS call.
+        uppername=`echo "$name" | sed -e 
'y|abcdefghijklmnopqrstuvwxyz./+-|ABCDEFGHIJKLMNOPQRSTUVWXYZ____|'`
+        eval value=\"\$HAVE_LIB$uppername\"
+        if test -n "$value"; then
+          if test "$value" = yes; then
+            eval value=\"\$LIB$uppername\"
+            test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value"
+            eval value=\"\$LTLIB$uppername\"
+            test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ 
}$value"
+          else
+            dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined
+            dnl that this library doesn't exist. So just drop it.
+            :
+          fi
+        else
+          dnl Search the library lib$name in $additional_libdir and $LDFLAGS
+          dnl and the already constructed $LIBNAME/$LTLIBNAME.
+          found_dir=
+          found_la=
+          found_so=
+          found_a=
+          eval libname=\"$acl_libname_spec\"    # typically: libname=lib$name
+          if test -n "$acl_shlibext"; then
+            shrext=".$acl_shlibext"             # typically: shrext=.so
+          else
+            shrext=
+          fi
+          if test $use_additional = yes; then
+            dir="$additional_libdir"
+            dnl The same code as in the loop below:
+            dnl First look for a shared library.
+            if test -n "$acl_shlibext"; then
+              if test -f "$dir/$libname$shrext"; then
+                found_dir="$dir"
+                found_so="$dir/$libname$shrext"
+              else
+                if test "$acl_library_names_spec" = 
'$libname$shrext$versuffix'; then
+                  ver=`(cd "$dir" && \
+                        for f in "$libname$shrext".*; do echo "$f"; done \
+                        | sed -e "s,^$libname$shrext\\\\.,," \
+                        | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \
+                        | sed 1q ) 2>/dev/null`
+                  if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; 
then
+                    found_dir="$dir"
+                    found_so="$dir/$libname$shrext.$ver"
+                  fi
+                else
+                  eval library_names=\"$acl_library_names_spec\"
+                  for f in $library_names; do
+                    if test -f "$dir/$f"; then
+                      found_dir="$dir"
+                      found_so="$dir/$f"
+                      break
+                    fi
+                  done
+                fi
+              fi
+            fi
+            dnl Then look for a static library.
+            if test "X$found_dir" = "X"; then
+              if test -f "$dir/$libname.$acl_libext"; then
+                found_dir="$dir"
+                found_a="$dir/$libname.$acl_libext"
+              fi
+            fi
+            if test "X$found_dir" != "X"; then
+              if test -f "$dir/$libname.la"; then
+                found_la="$dir/$libname.la"
+              fi
+            fi
+          fi
+          if test "X$found_dir" = "X"; then
+            for x in $LDFLAGS $LTLIB[]NAME; do
+              AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+              case "$x" in
+                -L*)
+                  dir=`echo "X$x" | sed -e 's/^X-L//'`
+                  dnl First look for a shared library.
+                  if test -n "$acl_shlibext"; then
+                    if test -f "$dir/$libname$shrext"; then
+                      found_dir="$dir"
+                      found_so="$dir/$libname$shrext"
+                    else
+                      if test "$acl_library_names_spec" = 
'$libname$shrext$versuffix'; then
+                        ver=`(cd "$dir" && \
+                              for f in "$libname$shrext".*; do echo "$f"; done 
\
+                              | sed -e "s,^$libname$shrext\\\\.,," \
+                              | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 
-k5,5 \
+                              | sed 1q ) 2>/dev/null`
+                        if test -n "$ver" && test -f 
"$dir/$libname$shrext.$ver"; then
+                          found_dir="$dir"
+                          found_so="$dir/$libname$shrext.$ver"
+                        fi
+                      else
+                        eval library_names=\"$acl_library_names_spec\"
+                        for f in $library_names; do
+                          if test -f "$dir/$f"; then
+                            found_dir="$dir"
+                            found_so="$dir/$f"
+                            break
+                          fi
+                        done
+                      fi
+                    fi
+                  fi
+                  dnl Then look for a static library.
+                  if test "X$found_dir" = "X"; then
+                    if test -f "$dir/$libname.$acl_libext"; then
+                      found_dir="$dir"
+                      found_a="$dir/$libname.$acl_libext"
+                    fi
+                  fi
+                  if test "X$found_dir" != "X"; then
+                    if test -f "$dir/$libname.la"; then
+                      found_la="$dir/$libname.la"
+                    fi
+                  fi
+                  ;;
+              esac
+              if test "X$found_dir" != "X"; then
+                break
+              fi
+            done
+          fi
+          if test "X$found_dir" != "X"; then
+            dnl Found the library.
+            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name"
+            if test "X$found_so" != "X"; then
+              dnl Linking with a shared library. We attempt to hardcode its
+              dnl directory into the executable's runpath, unless it's the
+              dnl standard /usr/lib.
+              if test "$enable_rpath" = no \
+                 || test "X$found_dir" = "X/usr/$acl_libdirstem" \
+                 || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then
+                dnl No hardcoding is needed.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+              else
+                dnl Use an explicit option to hardcode DIR into the resulting
+                dnl binary.
+                dnl Potentially add DIR to ltrpathdirs.
+                dnl The ltrpathdirs will be appended to $LTLIBNAME at the end.
+                haveit=
+                for x in $ltrpathdirs; do
+                  if test "X$x" = "X$found_dir"; then
+                    haveit=yes
+                    break
+                  fi
+                done
+                if test -z "$haveit"; then
+                  ltrpathdirs="$ltrpathdirs $found_dir"
+                fi
+                dnl The hardcoding into $LIBNAME is system dependent.
+                if test "$acl_hardcode_direct" = yes; then
+                  dnl Using DIR/libNAME.so during linking hardcodes DIR into 
the
+                  dnl resulting binary.
+                  LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                else
+                  if test -n "$acl_hardcode_libdir_flag_spec" && test 
"$acl_hardcode_minus_L" = no; then
+                    dnl Use an explicit option to hardcode DIR into the 
resulting
+                    dnl binary.
+                    LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                    dnl Potentially add DIR to rpathdirs.
+                    dnl The rpathdirs will be appended to $LIBNAME at the end.
+                    haveit=
+                    for x in $rpathdirs; do
+                      if test "X$x" = "X$found_dir"; then
+                        haveit=yes
+                        break
+                      fi
+                    done
+                    if test -z "$haveit"; then
+                      rpathdirs="$rpathdirs $found_dir"
+                    fi
+                  else
+                    dnl Rely on "-L$found_dir".
+                    dnl But don't add it if it's already contained in the 
LDFLAGS
+                    dnl or the already constructed $LIBNAME
+                    haveit=
+                    for x in $LDFLAGS $LIB[]NAME; do
+                      AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                      if test "X$x" = "X-L$found_dir"; then
+                        haveit=yes
+                        break
+                      fi
+                    done
+                    if test -z "$haveit"; then
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir"
+                    fi
+                    if test "$acl_hardcode_minus_L" != no; then
+                      dnl FIXME: Not sure whether we should use
+                      dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+                      dnl here.
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so"
+                    else
+                      dnl We cannot use $acl_hardcode_runpath_var and 
LD_RUN_PATH
+                      dnl here, because this doesn't fit in flags passed to the
+                      dnl compiler. So give up. No hardcoding. This affects 
only
+                      dnl very old systems.
+                      dnl FIXME: Not sure whether we should use
+                      dnl "-L$found_dir -l$name" or "-L$found_dir $found_so"
+                      dnl here.
+                      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+                    fi
+                  fi
+                fi
+              fi
+            else
+              if test "X$found_a" != "X"; then
+                dnl Linking with a static library.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a"
+              else
+                dnl We shouldn't come here, but anyway it's good to have a
+                dnl fallback.
+                LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name"
+              fi
+            fi
+            dnl Assume the include files are nearby.
+            additional_includedir=
+            case "$found_dir" in
+              */$acl_libdirstem | */$acl_libdirstem/)
+                basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e 
"s,/$acl_libdirstem/"'*$,,'`
+                if test "$name" = '$1'; then
+                  LIB[]NAME[]_PREFIX="$basedir"
+                fi
+                additional_includedir="$basedir/include"
+                ;;
+              */$acl_libdirstem2 | */$acl_libdirstem2/)
+                basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e 
"s,/$acl_libdirstem2/"'*$,,'`
+                if test "$name" = '$1'; then
+                  LIB[]NAME[]_PREFIX="$basedir"
+                fi
+                additional_includedir="$basedir/include"
+                ;;
+            esac
+            if test "X$additional_includedir" != "X"; then
+              dnl Potentially add $additional_includedir to $INCNAME.
+              dnl But don't add it
+              dnl   1. if it's the standard /usr/include,
+              dnl   2. if it's /usr/local/include and we are using GCC on 
Linux,
+              dnl   3. if it's already present in $CPPFLAGS or the already
+              dnl      constructed $INCNAME,
+              dnl   4. if it doesn't exist as a directory.
+              if test "X$additional_includedir" != "X/usr/include"; then
+                haveit=
+                if test "X$additional_includedir" = "X/usr/local/include"; then
+                  if test -n "$GCC"; then
+                    case $host_os in
+                      linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+                    esac
+                  fi
+                fi
+                if test -z "$haveit"; then
+                  for x in $CPPFLAGS $INC[]NAME; do
+                    AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                    if test "X$x" = "X-I$additional_includedir"; then
+                      haveit=yes
+                      break
+                    fi
+                  done
+                  if test -z "$haveit"; then
+                    if test -d "$additional_includedir"; then
+                      dnl Really add $additional_includedir to $INCNAME.
+                      INC[]NAME="${INC[]NAME}${INC[]NAME:+ 
}-I$additional_includedir"
+                    fi
+                  fi
+                fi
+              fi
+            fi
+            dnl Look for dependencies.
+            if test -n "$found_la"; then
+              dnl Read the .la file. It defines the variables
+              dnl dlname, library_names, old_library, dependency_libs, current,
+              dnl age, revision, installed, dlopen, dlpreopen, libdir.
+              save_libdir="$libdir"
+              case "$found_la" in
+                */* | *\\*) . "$found_la" ;;
+                *) . "./$found_la" ;;
+              esac
+              libdir="$save_libdir"
+              dnl We use only dependency_libs.
+              for dep in $dependency_libs; do
+                case "$dep" in
+                  -L*)
+                    additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'`
+                    dnl Potentially add $additional_libdir to $LIBNAME and 
$LTLIBNAME.
+                    dnl But don't add it
+                    dnl   1. if it's the standard /usr/lib,
+                    dnl   2. if it's /usr/local/lib and we are using GCC on 
Linux,
+                    dnl   3. if it's already present in $LDFLAGS or the already
+                    dnl      constructed $LIBNAME,
+                    dnl   4. if it doesn't exist as a directory.
+                    if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \
+                       && test "X$additional_libdir" != 
"X/usr/$acl_libdirstem2"; then
+                      haveit=
+                      if test "X$additional_libdir" = 
"X/usr/local/$acl_libdirstem" \
+                         || test "X$additional_libdir" = 
"X/usr/local/$acl_libdirstem2"; then
+                        if test -n "$GCC"; then
+                          case $host_os in
+                            linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+                          esac
+                        fi
+                      fi
+                      if test -z "$haveit"; then
+                        haveit=
+                        for x in $LDFLAGS $LIB[]NAME; do
+                          AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                          if test "X$x" = "X-L$additional_libdir"; then
+                            haveit=yes
+                            break
+                          fi
+                        done
+                        if test -z "$haveit"; then
+                          if test -d "$additional_libdir"; then
+                            dnl Really add $additional_libdir to $LIBNAME.
+                            LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ 
}-L$additional_libdir"
+                          fi
+                        fi
+                        haveit=
+                        for x in $LDFLAGS $LTLIB[]NAME; do
+                          AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+                          if test "X$x" = "X-L$additional_libdir"; then
+                            haveit=yes
+                            break
+                          fi
+                        done
+                        if test -z "$haveit"; then
+                          if test -d "$additional_libdir"; then
+                            dnl Really add $additional_libdir to $LTLIBNAME.
+                            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ 
}-L$additional_libdir"
+                          fi
+                        fi
+                      fi
+                    fi
+                    ;;
+                  -R*)
+                    dir=`echo "X$dep" | sed -e 's/^X-R//'`
+                    if test "$enable_rpath" != no; then
+                      dnl Potentially add DIR to rpathdirs.
+                      dnl The rpathdirs will be appended to $LIBNAME at the 
end.
+                      haveit=
+                      for x in $rpathdirs; do
+                        if test "X$x" = "X$dir"; then
+                          haveit=yes
+                          break
+                        fi
+                      done
+                      if test -z "$haveit"; then
+                        rpathdirs="$rpathdirs $dir"
+                      fi
+                      dnl Potentially add DIR to ltrpathdirs.
+                      dnl The ltrpathdirs will be appended to $LTLIBNAME at 
the end.
+                      haveit=
+                      for x in $ltrpathdirs; do
+                        if test "X$x" = "X$dir"; then
+                          haveit=yes
+                          break
+                        fi
+                      done
+                      if test -z "$haveit"; then
+                        ltrpathdirs="$ltrpathdirs $dir"
+                      fi
+                    fi
+                    ;;
+                  -l*)
+                    dnl Handle this in the next round.
+                    names_next_round="$names_next_round "`echo "X$dep" | sed 
-e 's/^X-l//'`
+                    ;;
+                  *.la)
+                    dnl Handle this in the next round. Throw away the .la's
+                    dnl directory; it is already contained in a preceding -L
+                    dnl option.
+                    names_next_round="$names_next_round "`echo "X$dep" | sed 
-e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'`
+                    ;;
+                  *)
+                    dnl Most likely an immediate library name.
+                    LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep"
+                    LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep"
+                    ;;
+                esac
+              done
+            fi
+          else
+            dnl Didn't find the library; assume it is in the system directories
+            dnl known to the linker and runtime loader. (All the system
+            dnl directories known to the linker should also be known to the
+            dnl runtime loader, otherwise the system is severely 
misconfigured.)
+            LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name"
+            LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name"
+          fi
+        fi
+      fi
+    done
+  done
+  if test "X$rpathdirs" != "X"; then
+    if test -n "$acl_hardcode_libdir_separator"; then
+      dnl Weird platform: only the last -rpath option counts, the user must
+      dnl pass all path elements in one option. We can arrange that for a
+      dnl single library, but not when more than one $LIBNAMEs are used.
+      alldirs=
+      for found_dir in $rpathdirs; do
+        
alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir"
+      done
+      dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl.
+      acl_save_libdir="$libdir"
+      libdir="$alldirs"
+      eval flag=\"$acl_hardcode_libdir_flag_spec\"
+      libdir="$acl_save_libdir"
+      LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+    else
+      dnl The -rpath options are cumulative.
+      for found_dir in $rpathdirs; do
+        acl_save_libdir="$libdir"
+        libdir="$found_dir"
+        eval flag=\"$acl_hardcode_libdir_flag_spec\"
+        libdir="$acl_save_libdir"
+        LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag"
+      done
+    fi
+  fi
+  if test "X$ltrpathdirs" != "X"; then
+    dnl When using libtool, the option that works for both libraries and
+    dnl executables is -R. The -R options are cumulative.
+    for found_dir in $ltrpathdirs; do
+      LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir"
+    done
+  fi
+  popdef([P_A_C_K])
+  popdef([PACKLIBS])
+  popdef([PACKUP])
+  popdef([PACK])
+  popdef([NAME])
+])
+
+dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR,
+dnl unless already present in VAR.
+dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes
+dnl contains two or three consecutive elements that belong together.
+AC_DEFUN([AC_LIB_APPENDTOVAR],
+[
+  for element in [$2]; do
+    haveit=
+    for x in $[$1]; do
+      AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+      if test "X$x" = "X$element"; then
+        haveit=yes
+        break
+      fi
+    done
+    if test -z "$haveit"; then
+      [$1]="${[$1]}${[$1]:+ }$element"
+    fi
+  done
+])
+
+dnl For those cases where a variable contains several -L and -l options
+dnl referring to unknown libraries and directories, this macro determines the
+dnl necessary additional linker options for the runtime path.
+dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL])
+dnl sets LDADDVAR to linker options needed together with LIBSVALUE.
+dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed,
+dnl otherwise linking without libtool is assumed.
+AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS],
+[
+  AC_REQUIRE([AC_LIB_RPATH])
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  $1=
+  if test "$enable_rpath" != no; then
+    if test -n "$acl_hardcode_libdir_flag_spec" && test 
"$acl_hardcode_minus_L" = no; then
+      dnl Use an explicit option to hardcode directories into the resulting
+      dnl binary.
+      rpathdirs=
+      next=
+      for opt in $2; do
+        if test -n "$next"; then
+          dir="$next"
+          dnl No need to hardcode the standard /usr/lib.
+          if test "X$dir" != "X/usr/$acl_libdirstem" \
+             && test "X$dir" != "X/usr/$acl_libdirstem2"; then
+            rpathdirs="$rpathdirs $dir"
+          fi
+          next=
+        else
+          case $opt in
+            -L) next=yes ;;
+            -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'`
+                 dnl No need to hardcode the standard /usr/lib.
+                 if test "X$dir" != "X/usr/$acl_libdirstem" \
+                    && test "X$dir" != "X/usr/$acl_libdirstem2"; then
+                   rpathdirs="$rpathdirs $dir"
+                 fi
+                 next= ;;
+            *) next= ;;
+          esac
+        fi
+      done
+      if test "X$rpathdirs" != "X"; then
+        if test -n ""$3""; then
+          dnl libtool is used for linking. Use -R options.
+          for dir in $rpathdirs; do
+            $1="${$1}${$1:+ }-R$dir"
+          done
+        else
+          dnl The linker is used for linking directly.
+          if test -n "$acl_hardcode_libdir_separator"; then
+            dnl Weird platform: only the last -rpath option counts, the user
+            dnl must pass all path elements in one option.
+            alldirs=
+            for dir in $rpathdirs; do
+              
alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir"
+            done
+            acl_save_libdir="$libdir"
+            libdir="$alldirs"
+            eval flag=\"$acl_hardcode_libdir_flag_spec\"
+            libdir="$acl_save_libdir"
+            $1="$flag"
+          else
+            dnl The -rpath options are cumulative.
+            for dir in $rpathdirs; do
+              acl_save_libdir="$libdir"
+              libdir="$dir"
+              eval flag=\"$acl_hardcode_libdir_flag_spec\"
+              libdir="$acl_save_libdir"
+              $1="${$1}${$1:+ }$flag"
+            done
+          fi
+        fi
+      fi
+    fi
+  fi
+  AC_SUBST([$1])
+])
diff --git a/m4/lib-prefix.m4 b/m4/lib-prefix.m4
new file mode 100644
index 0000000..6851031
--- /dev/null
+++ b/m4/lib-prefix.m4
@@ -0,0 +1,224 @@
+# lib-prefix.m4 serial 7 (gettext-0.18)
+dnl Copyright (C) 2001-2005, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and
+dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't
+dnl require excessive bracketing.
+ifdef([AC_HELP_STRING],
+[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])],
+[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])])
+
+dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed
+dnl to access previously installed libraries. The basic assumption is that
+dnl a user will want packages to use other packages he previously installed
+dnl with the same --prefix option.
+dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate
+dnl libraries, but is otherwise very convenient.
+AC_DEFUN([AC_LIB_PREFIX],
+[
+  AC_BEFORE([$0], [AC_LIB_LINKFLAGS])
+  AC_REQUIRE([AC_PROG_CC])
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  AC_REQUIRE([AC_LIB_PREPARE_MULTILIB])
+  AC_REQUIRE([AC_LIB_PREPARE_PREFIX])
+  dnl By default, look in $includedir and $libdir.
+  use_additional=yes
+  AC_LIB_WITH_FINAL_PREFIX([
+    eval additional_includedir=\"$includedir\"
+    eval additional_libdir=\"$libdir\"
+  ])
+  AC_LIB_ARG_WITH([lib-prefix],
+[  --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib
+  --without-lib-prefix    don't search for libraries in includedir and libdir],
+[
+    if test "X$withval" = "Xno"; then
+      use_additional=no
+    else
+      if test "X$withval" = "X"; then
+        AC_LIB_WITH_FINAL_PREFIX([
+          eval additional_includedir=\"$includedir\"
+          eval additional_libdir=\"$libdir\"
+        ])
+      else
+        additional_includedir="$withval/include"
+        additional_libdir="$withval/$acl_libdirstem"
+      fi
+    fi
+])
+  if test $use_additional = yes; then
+    dnl Potentially add $additional_includedir to $CPPFLAGS.
+    dnl But don't add it
+    dnl   1. if it's the standard /usr/include,
+    dnl   2. if it's already present in $CPPFLAGS,
+    dnl   3. if it's /usr/local/include and we are using GCC on Linux,
+    dnl   4. if it doesn't exist as a directory.
+    if test "X$additional_includedir" != "X/usr/include"; then
+      haveit=
+      for x in $CPPFLAGS; do
+        AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+        if test "X$x" = "X-I$additional_includedir"; then
+          haveit=yes
+          break
+        fi
+      done
+      if test -z "$haveit"; then
+        if test "X$additional_includedir" = "X/usr/local/include"; then
+          if test -n "$GCC"; then
+            case $host_os in
+              linux* | gnu* | k*bsd*-gnu) haveit=yes;;
+            esac
+          fi
+        fi
+        if test -z "$haveit"; then
+          if test -d "$additional_includedir"; then
+            dnl Really add $additional_includedir to $CPPFLAGS.
+            CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir"
+          fi
+        fi
+      fi
+    fi
+    dnl Potentially add $additional_libdir to $LDFLAGS.
+    dnl But don't add it
+    dnl   1. if it's the standard /usr/lib,
+    dnl   2. if it's already present in $LDFLAGS,
+    dnl   3. if it's /usr/local/lib and we are using GCC on Linux,
+    dnl   4. if it doesn't exist as a directory.
+    if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then
+      haveit=
+      for x in $LDFLAGS; do
+        AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"])
+        if test "X$x" = "X-L$additional_libdir"; then
+          haveit=yes
+          break
+        fi
+      done
+      if test -z "$haveit"; then
+        if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then
+          if test -n "$GCC"; then
+            case $host_os in
+              linux*) haveit=yes;;
+            esac
+          fi
+        fi
+        if test -z "$haveit"; then
+          if test -d "$additional_libdir"; then
+            dnl Really add $additional_libdir to $LDFLAGS.
+            LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir"
+          fi
+        fi
+      fi
+    fi
+  fi
+])
+
+dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix,
+dnl acl_final_exec_prefix, containing the values to which $prefix and
+dnl $exec_prefix will expand at the end of the configure script.
+AC_DEFUN([AC_LIB_PREPARE_PREFIX],
+[
+  dnl Unfortunately, prefix and exec_prefix get only finally determined
+  dnl at the end of configure.
+  if test "X$prefix" = "XNONE"; then
+    acl_final_prefix="$ac_default_prefix"
+  else
+    acl_final_prefix="$prefix"
+  fi
+  if test "X$exec_prefix" = "XNONE"; then
+    acl_final_exec_prefix='${prefix}'
+  else
+    acl_final_exec_prefix="$exec_prefix"
+  fi
+  acl_save_prefix="$prefix"
+  prefix="$acl_final_prefix"
+  eval acl_final_exec_prefix=\"$acl_final_exec_prefix\"
+  prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the
+dnl variables prefix and exec_prefix bound to the values they will have
+dnl at the end of the configure script.
+AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX],
+[
+  acl_save_prefix="$prefix"
+  prefix="$acl_final_prefix"
+  acl_save_exec_prefix="$exec_prefix"
+  exec_prefix="$acl_final_exec_prefix"
+  $1
+  exec_prefix="$acl_save_exec_prefix"
+  prefix="$acl_save_prefix"
+])
+
+dnl AC_LIB_PREPARE_MULTILIB creates
+dnl - a variable acl_libdirstem, containing the basename of the libdir, either
+dnl   "lib" or "lib64" or "lib/64",
+dnl - a variable acl_libdirstem2, as a secondary possible value for
+dnl   acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or
+dnl   "lib/amd64".
+AC_DEFUN([AC_LIB_PREPARE_MULTILIB],
+[
+  dnl There is no formal standard regarding lib and lib64.
+  dnl On glibc systems, the current practice is that on a system supporting
+  dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
+  dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine
+  dnl the compiler's default mode by looking at the compiler's library search
+  dnl path. If at least one of its elements ends in /lib64 or points to a
+  dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI.
+  dnl Otherwise we use the default, namely "lib".
+  dnl On Solaris systems, the current practice is that on a system supporting
+  dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under
+  dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or
+  dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib.
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  acl_libdirstem=lib
+  acl_libdirstem2=
+  case "$host_os" in
+    solaris*)
+      dnl See Solaris 10 Software Developer Collection > Solaris 64-bit 
Developer's Guide > The Development Environment
+      dnl <http://docs.sun.com/app/docs/doc/816-5138/dev-env?l=en&a=view>.
+      dnl "Portable Makefiles should refer to any library directories using 
the 64 symbolic link."
+      dnl But we want to recognize the sparcv9 or amd64 subdirectory also if 
the
+      dnl symlink is missing, so we set acl_libdirstem2 too.
+      AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit],
+        [AC_EGREP_CPP([sixtyfour bits], [
+#ifdef _LP64
+sixtyfour bits
+#endif
+           ], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no])
+        ])
+      if test $gl_cv_solaris_64bit = yes; then
+        acl_libdirstem=lib/64
+        case "$host_cpu" in
+          sparc*)        acl_libdirstem2=lib/sparcv9 ;;
+          i*86 | x86_64) acl_libdirstem2=lib/amd64 ;;
+        esac
+      fi
+      ;;
+    *)
+      searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 
's,^libraries: ,,p' | sed -e 's,^=,,'`
+      if test -n "$searchpath"; then
+        acl_save_IFS="${IFS=   }"; IFS=":"
+        for searchdir in $searchpath; do
+          if test -d "$searchdir"; then
+            case "$searchdir" in
+              */lib64/ | */lib64 ) acl_libdirstem=lib64 ;;
+              */../ | */.. )
+                # Better ignore directories of this form. They are misleading.
+                ;;
+              *) searchdir=`cd "$searchdir" && pwd`
+                 case "$searchdir" in
+                   */lib64 ) acl_libdirstem=lib64 ;;
+                 esac ;;
+            esac
+          fi
+        done
+        IFS="$acl_save_IFS"
+      fi
+      ;;
+  esac
+  test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem"
+])
diff --git a/m4/libcurl.m4 b/m4/libcurl.m4
new file mode 100644
index 0000000..047260b
--- /dev/null
+++ b/m4/libcurl.m4
@@ -0,0 +1,251 @@
+# LIBCURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION],
+#                       [ACTION-IF-YES], [ACTION-IF-NO])
+# ----------------------------------------------------------
+#      David Shaw <dshaw@jabberwocky.com>   May-09-2006
+#
+# Checks for libcurl.  DEFAULT-ACTION is the string yes or no to
+# specify whether to default to --with-libcurl or --without-libcurl.
+# If not supplied, DEFAULT-ACTION is yes.  MINIMUM-VERSION is the
+# minimum version of libcurl to accept.  Pass the version as a regular
+# version number like 7.10.1. If not supplied, any version is
+# accepted.  ACTION-IF-YES is a list of shell commands to run if
+# libcurl was successfully found and passed the various tests.
+# ACTION-IF-NO is a list of shell commands that are run otherwise.
+# Note that using --without-libcurl does run ACTION-IF-NO.
+#
+# This macro #defines HAVE_LIBCURL if a working libcurl setup is
+# found, and sets @LIBCURL@ and @LIBCURL_CPPFLAGS@ to the necessary
+# values.  Other useful defines are LIBCURL_FEATURE_xxx where xxx are
+# the various features supported by libcurl, and LIBCURL_PROTOCOL_yyy
+# where yyy are the various protocols supported by libcurl.  Both xxx
+# and yyy are capitalized.  See the list of AH_TEMPLATEs at the top of
+# the macro for the complete list of possible defines.  Shell
+# variables $libcurl_feature_xxx and $libcurl_protocol_yyy are also
+# defined to 'yes' for those features and protocols that were found.
+# Note that xxx and yyy keep the same capitalization as in the
+# curl-config list (e.g. it's "HTTP" and not "http").
+#
+# Users may override the detected values by doing something like:
+# LIBCURL="-lcurl" LIBCURL_CPPFLAGS="-I/usr/myinclude" ./configure
+#
+# For the sake of sanity, this macro assumes that any libcurl that is
+# found is after version 7.7.2, the first version that included the
+# curl-config script.  Note that it is very important for people
+# packaging binary versions of libcurl to include this script!
+# Without curl-config, we can only guess what protocols are available,
+# or use curl_version_info to figure it out at runtime.
+
+AC_DEFUN([LIBCURL_CHECK_CONFIG],
+[
+  AH_TEMPLATE([LIBCURL_FEATURE_SSL],[Defined if libcurl supports SSL])
+  AH_TEMPLATE([LIBCURL_FEATURE_KRB4],[Defined if libcurl supports KRB4])
+  AH_TEMPLATE([LIBCURL_FEATURE_IPV6],[Defined if libcurl supports IPv6])
+  AH_TEMPLATE([LIBCURL_FEATURE_LIBZ],[Defined if libcurl supports libz])
+  AH_TEMPLATE([LIBCURL_FEATURE_ASYNCHDNS],[Defined if libcurl supports 
AsynchDNS])
+  AH_TEMPLATE([LIBCURL_FEATURE_IDN],[Defined if libcurl supports IDN])
+  AH_TEMPLATE([LIBCURL_FEATURE_SSPI],[Defined if libcurl supports SSPI])
+  AH_TEMPLATE([LIBCURL_FEATURE_NTLM],[Defined if libcurl supports NTLM])
+
+  AH_TEMPLATE([LIBCURL_PROTOCOL_HTTP],[Defined if libcurl supports HTTP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_HTTPS],[Defined if libcurl supports HTTPS])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_FTP],[Defined if libcurl supports FTP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_FTPS],[Defined if libcurl supports FTPS])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_FILE],[Defined if libcurl supports FILE])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_TELNET],[Defined if libcurl supports TELNET])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_LDAP],[Defined if libcurl supports LDAP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_DICT],[Defined if libcurl supports DICT])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_TFTP],[Defined if libcurl supports TFTP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_RTSP],[Defined if libcurl supports RTSP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_POP3],[Defined if libcurl supports POP3])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_IMAP],[Defined if libcurl supports IMAP])
+  AH_TEMPLATE([LIBCURL_PROTOCOL_SMTP],[Defined if libcurl supports SMTP])
+
+  AC_ARG_WITH(libcurl,
+     AS_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in 
PREFIX/lib and headers in PREFIX/include]),
+     [_libcurl_with=$withval],[_libcurl_with=ifelse([$1],,[yes],[$1])])
+
+  if test "$_libcurl_with" != "no" ; then
+
+     AC_PROG_AWK
+
+     _libcurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); 
X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'"
+
+     _libcurl_try_link=yes
+
+     if test -d "$_libcurl_with" ; then
+        LIBCURL_CPPFLAGS="-I$withval/include"
+        _libcurl_ldflags="-L$withval/lib"
+        AC_PATH_PROG([_libcurl_config],[curl-config],[],
+                     ["$withval/bin"])
+     else
+        AC_PATH_PROG([_libcurl_config],[curl-config],[],[$PATH])
+     fi
+
+     if test x$_libcurl_config != "x" ; then
+        AC_CACHE_CHECK([for the version of libcurl],
+           [libcurl_cv_lib_curl_version],
+           [libcurl_cv_lib_curl_version=`$_libcurl_config --version | $AWK 
'{print $[]2}'`])
+
+        _libcurl_version=`echo $libcurl_cv_lib_curl_version | 
$_libcurl_version_parse`
+        _libcurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libcurl_version_parse`
+
+        if test $_libcurl_wanted -gt 0 ; then
+           AC_CACHE_CHECK([for libcurl >= version $2],
+              [libcurl_cv_lib_version_ok],
+              [
+              if test $_libcurl_version -ge $_libcurl_wanted ; then
+                 libcurl_cv_lib_version_ok=yes
+              else
+                 libcurl_cv_lib_version_ok=no
+              fi
+              ])
+        fi
+
+        if test $_libcurl_wanted -eq 0 || test x$libcurl_cv_lib_version_ok = 
xyes ; then
+           if test x"$LIBCURL_CPPFLAGS" = "x" ; then
+              LIBCURL_CPPFLAGS=`$_libcurl_config --cflags`
+           fi
+           if test x"$LIBCURL" = "x" ; then
+              LIBCURL=`$_libcurl_config --libs`
+
+              # This is so silly, but Apple actually has a bug in their
+              # curl-config script.  Fixed in Tiger, but there are still
+              # lots of Panther installs around.
+              case "${host}" in
+                 powerpc-apple-darwin7*)
+                    LIBCURL=`echo $LIBCURL | sed -e 's|-arch i386||g'`
+                 ;;
+              esac
+           fi
+
+           # All curl-config scripts support --feature
+           _libcurl_features=`$_libcurl_config --feature`
+
+           # Is it modern enough to have --protocols? (7.12.4)
+           if test $_libcurl_version -ge 461828 ; then
+              _libcurl_protocols=`$_libcurl_config --protocols`
+           fi
+        else
+           _libcurl_try_link=no
+        fi
+
+        unset _libcurl_wanted
+     fi
+
+     if test $_libcurl_try_link = yes ; then
+
+        # we didn't find curl-config, so let's see if the user-supplied
+        # link line (or failing that, "-lcurl") is enough.
+        LIBCURL=${LIBCURL-"$_libcurl_ldflags -lcurl"}
+
+        AC_CACHE_CHECK([whether libcurl is usable],
+           [libcurl_cv_lib_curl_usable],
+           [
+           _libcurl_save_cppflags=$CPPFLAGS
+           CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS"
+           _libcurl_save_libs=$LIBS
+           LIBS="$LIBCURL $LIBS"
+
+           AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <curl/curl.h>]],[[
+/* Try and use a few common options to force a failure if we are
+   missing symbols or can't link. */
+int x;
+curl_easy_setopt(NULL,CURLOPT_URL,NULL);
+x=CURL_ERROR_SIZE;
+x=CURLOPT_WRITEFUNCTION;
+x=CURLOPT_WRITEDATA;
+x=CURLOPT_ERRORBUFFER;
+x=CURLOPT_STDERR;
+x=CURLOPT_VERBOSE;
+if (x) ;
+]])],libcurl_cv_lib_curl_usable=yes,libcurl_cv_lib_curl_usable=no)
+
+           CPPFLAGS=$_libcurl_save_cppflags
+           LIBS=$_libcurl_save_libs
+           unset _libcurl_save_cppflags
+           unset _libcurl_save_libs
+           ])
+
+        if test $libcurl_cv_lib_curl_usable = yes ; then
+
+           # Does curl_free() exist in this version of libcurl?
+           # If not, fake it with free()
+
+           _libcurl_save_cppflags=$CPPFLAGS
+           CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS"
+           _libcurl_save_libs=$LIBS
+           LIBS="$LIBS $LIBCURL"
+
+           AC_CHECK_FUNC(curl_free,,
+              AC_DEFINE(curl_free,free,
+                [Define curl_free() as free() if our version of curl lacks 
curl_free.]))
+
+           CPPFLAGS=$_libcurl_save_cppflags
+           LIBS=$_libcurl_save_libs
+           unset _libcurl_save_cppflags
+           unset _libcurl_save_libs
+
+           AC_DEFINE(HAVE_LIBCURL,1,
+             [Define to 1 if you have a functional curl library.])
+           AC_SUBST(LIBCURL_CPPFLAGS)
+           AC_SUBST(LIBCURL)
+
+           for _libcurl_feature in $_libcurl_features ; do
+              
AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_feature_$_libcurl_feature),[1])
+              eval AS_TR_SH(libcurl_feature_$_libcurl_feature)=yes
+           done
+
+           if test "x$_libcurl_protocols" = "x" ; then
+
+              # We don't have --protocols, so just assume that all
+              # protocols are available
+              _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP"
+
+              if test x$libcurl_feature_SSL = xyes ; then
+                 _libcurl_protocols="$_libcurl_protocols HTTPS"
+
+                 # FTPS wasn't standards-compliant until version
+                 # 7.11.0 (0x070b00 == 461568)
+                 if test $_libcurl_version -ge 461568; then
+                    _libcurl_protocols="$_libcurl_protocols FTPS"
+                 fi
+              fi
+
+              # RTSP, IMAP, POP3 and SMTP were added in
+              # 7.20.0 (0x071400 == 463872)
+              if test $_libcurl_version -ge 463872; then
+                 _libcurl_protocols="$_libcurl_protocols RTSP IMAP POP3 SMTP"
+              fi
+           fi
+
+           for _libcurl_protocol in $_libcurl_protocols ; do
+              
AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_protocol_$_libcurl_protocol),[1])
+              eval AS_TR_SH(libcurl_protocol_$_libcurl_protocol)=yes
+           done
+        else
+           unset LIBCURL
+           unset LIBCURL_CPPFLAGS
+        fi
+     fi
+
+     unset _libcurl_try_link
+     unset _libcurl_version_parse
+     unset _libcurl_config
+     unset _libcurl_feature
+     unset _libcurl_features
+     unset _libcurl_protocol
+     unset _libcurl_protocols
+     unset _libcurl_version
+     unset _libcurl_ldflags
+  fi
+
+  if test x$_libcurl_with = xno || test x$libcurl_cv_lib_curl_usable != xyes ; 
then
+     # This is the IF-NO path
+     ifelse([$4],,:,[$4])
+  else
+     # This is the IF-YES path
+     ifelse([$3],,:,[$3])
+  fi
+
+  unset _libcurl_with
+])dnl
diff --git a/m4/libgcrypt.m4 b/m4/libgcrypt.m4
new file mode 100644
index 0000000..9a29eb5
--- /dev/null
+++ b/m4/libgcrypt.m4
@@ -0,0 +1,122 @@
+dnl Autoconf macros for libgcrypt
+dnl       Copyright (C) 2002, 2004, 2011 Free Software Foundation, Inc.
+dnl
+dnl This file is free software; as a special exception the author gives
+dnl unlimited permission to copy and/or distribute it, with or without
+dnl modifications, as long as this notice is preserved.
+dnl
+dnl This file is distributed in the hope that it will be useful, but
+dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+
+dnl AM_PATH_LIBGCRYPT([MINIMUM-VERSION,
+dnl                   [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]])
+dnl Test for libgcrypt and define LIBGCRYPT_CFLAGS and LIBGCRYPT_LIBS.
+dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed
+dnl with the API version to also check the API compatibility. Example:
+dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed
+dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1.  Using
+dnl this features allows to prevent build against newer versions of libgcrypt
+dnl with a changed API.
+dnl
+AC_DEFUN([AM_PATH_LIBGCRYPT],
+[ AC_REQUIRE([AC_CANONICAL_HOST])
+  AC_ARG_WITH(libgcrypt-prefix,
+            AS_HELP_STRING([--with-libgcrypt-prefix=PFX],
+                           [prefix where LIBGCRYPT is installed (optional)]),
+     libgcrypt_config_prefix="$withval", libgcrypt_config_prefix="")
+  if test x$libgcrypt_config_prefix != x ; then
+     if test x${LIBGCRYPT_CONFIG+set} != xset ; then
+        LIBGCRYPT_CONFIG=$libgcrypt_config_prefix/bin/libgcrypt-config
+     fi
+  fi
+
+  AC_PATH_TOOL(LIBGCRYPT_CONFIG, libgcrypt-config, no)
+  tmp=ifelse([$1], ,1:1.2.0,$1)
+  if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then
+     req_libgcrypt_api=`echo "$tmp"     | sed 's/\(.*\):\(.*\)/\1/'`
+     min_libgcrypt_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'`
+  else
+     req_libgcrypt_api=0
+     min_libgcrypt_version="$tmp"
+  fi
+
+  AC_MSG_CHECKING(for LIBGCRYPT - version >= $min_libgcrypt_version)
+  ok=no
+  if test "$LIBGCRYPT_CONFIG" != "no" ; then
+    req_major=`echo $min_libgcrypt_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
+    req_minor=`echo $min_libgcrypt_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
+    req_micro=`echo $min_libgcrypt_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'`
+    libgcrypt_config_version=`$LIBGCRYPT_CONFIG --version`
+    major=`echo $libgcrypt_config_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
+    minor=`echo $libgcrypt_config_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
+    micro=`echo $libgcrypt_config_version | \
+               sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'`
+    if test "$major" -gt "$req_major"; then
+        ok=yes
+    else
+        if test "$major" -eq "$req_major"; then
+            if test "$minor" -gt "$req_minor"; then
+               ok=yes
+            else
+               if test "$minor" -eq "$req_minor"; then
+                   if test "$micro" -ge "$req_micro"; then
+                     ok=yes
+                   fi
+               fi
+            fi
+        fi
+    fi
+  fi
+  if test $ok = yes; then
+    AC_MSG_RESULT([yes ($libgcrypt_config_version)])
+  else
+    AC_MSG_RESULT(no)
+  fi
+  if test $ok = yes; then
+     # If we have a recent libgcrypt, we should also check that the
+     # API is compatible
+     if test "$req_libgcrypt_api" -gt 0 ; then
+        tmp=`$LIBGCRYPT_CONFIG --api-version 2>/dev/null || echo 0`
+        if test "$tmp" -gt 0 ; then
+           AC_MSG_CHECKING([LIBGCRYPT API version])
+           if test "$req_libgcrypt_api" -eq "$tmp" ; then
+             AC_MSG_RESULT([okay])
+           else
+             ok=no
+             AC_MSG_RESULT([does not match. want=$req_libgcrypt_api got=$tmp])
+           fi
+        fi
+     fi
+  fi
+  if test $ok = yes; then
+    LIBGCRYPT_CFLAGS=`$LIBGCRYPT_CONFIG --cflags`
+    LIBGCRYPT_LIBS=`$LIBGCRYPT_CONFIG --libs`
+    ifelse([$2], , :, [$2])
+    libgcrypt_config_host=`$LIBGCRYPT_CONFIG --host 2>/dev/null || echo none`
+    if test x"$libgcrypt_config_host" != xnone ; then
+      if test x"$libgcrypt_config_host" != x"$host" ; then
+  AC_MSG_WARN([[
+***
+*** The config script $LIBGCRYPT_CONFIG was
+*** built for $libgcrypt_config_host and thus may not match the
+*** used host $host.
+*** You may want to use the configure option --with-libgcrypt-prefix
+*** to specify a matching config script.
+***]])
+      fi
+    fi
+  else
+    LIBGCRYPT_CFLAGS=""
+    LIBGCRYPT_LIBS=""
+    ifelse([$3], , :, [$3])
+  fi
+  AC_SUBST(LIBGCRYPT_CFLAGS)
+  AC_SUBST(LIBGCRYPT_LIBS)
+])
diff --git a/m4/libgnurl.m4 b/m4/libgnurl.m4
new file mode 100644
index 0000000..4127093
--- /dev/null
+++ b/m4/libgnurl.m4
@@ -0,0 +1,266 @@
+# LIBGNURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION],
+#                       [ACTION-IF-YES], [ACTION-IF-NO])
+# ----------------------------------------------------------
+#      David Shaw <dshaw@jabberwocky.com>   May-09-2006
+#
+# Checks for libgnurl.  DEFAULT-ACTION is the string yes or no to
+# specify whether to default to --with-libgnurl or --without-libgnurl.
+# If not supplied, DEFAULT-ACTION is yes.  MINIMUM-VERSION is the
+# minimum version of libgnurl to accept.  Pass the version as a regular
+# version number like 7.10.1. If not supplied, any version is
+# accepted.  ACTION-IF-YES is a list of shell commands to run if
+# libgnurl was successfully found and passed the various tests.
+# ACTION-IF-NO is a list of shell commands that are run otherwise.
+# Note that using --without-libgnurl does run ACTION-IF-NO.
+#
+# This macro #defines HAVE_LIBGNURL if a working libgnurl setup is
+# found, and sets @LIBGNURL@ and @LIBGNURL_CPPFLAGS@ to the necessary
+# values.  Other useful defines are LIBGNURL_FEATURE_xxx where xxx are
+# the various features supported by libgnurl, and LIBGNURL_PROTOCOL_yyy
+# where yyy are the various protocols supported by libgnurl.  Both xxx
+# and yyy are capitalized.  See the list of AH_TEMPLATEs at the top of
+# the macro for the complete list of possible defines.  Shell
+# variables $libgnurl_feature_xxx and $libgnurl_protocol_yyy are also
+# defined to 'yes' for those features and protocols that were found.
+# Note that xxx and yyy keep the same capitalization as in the
+# gnurl-config list (e.g. it's "HTTP" and not "http").
+#
+# Users may override the detected values by doing something like:
+# LIBGNURL="-lgnurl" LIBGNURL_CPPFLAGS="-I/usr/myinclude" ./configure
+#
+# For the sake of sanity, this macro assumes that any libgnurl that is
+# found is after version 7.7.2, the first version that included the
+# gnurl-config script.  Note that it is very important for people
+# packaging binary versions of libgnurl to include this script!
+# Without gnurl-config, we can only guess what protocols are available,
+# or use gnurl_version_info to figure it out at runtime.
+
+AC_DEFUN([LIBGNURL_CHECK_CONFIG],
+[
+  AH_TEMPLATE([LIBGNURL_FEATURE_SSL],[Defined if libgnurl supports SSL])
+  AH_TEMPLATE([LIBGNURL_FEATURE_KRB4],[Defined if libgnurl supports KRB4])
+  AH_TEMPLATE([LIBGNURL_FEATURE_IPV6],[Defined if libgnurl supports IPv6])
+  AH_TEMPLATE([LIBGNURL_FEATURE_LIBZ],[Defined if libgnurl supports libz])
+  AH_TEMPLATE([LIBGNURL_FEATURE_ASYNCHDNS],[Defined if libgnurl supports 
AsynchDNS])
+  AH_TEMPLATE([LIBGNURL_FEATURE_IDN],[Defined if libgnurl supports IDN])
+  AH_TEMPLATE([LIBGNURL_FEATURE_SSPI],[Defined if libgnurl supports SSPI])
+  AH_TEMPLATE([LIBGNURL_FEATURE_NTLM],[Defined if libgnurl supports NTLM])
+
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTP],[Defined if libgnurl supports HTTP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_HTTPS],[Defined if libgnurl supports HTTPS])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_FTP],[Defined if libgnurl supports FTP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_FTPS],[Defined if libgnurl supports FTPS])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_FILE],[Defined if libgnurl supports FILE])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_TELNET],[Defined if libgnurl supports TELNET])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_LDAP],[Defined if libgnurl supports LDAP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_DICT],[Defined if libgnurl supports DICT])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_TFTP],[Defined if libgnurl supports TFTP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_RTSP],[Defined if libgnurl supports RTSP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_POP3],[Defined if libgnurl supports POP3])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_IMAP],[Defined if libgnurl supports IMAP])
+  AH_TEMPLATE([LIBGNURL_PROTOCOL_SMTP],[Defined if libgnurl supports SMTP])
+
+  AC_ARG_WITH(libgnurl,
+     AS_HELP_STRING([--with-libgnurl=PREFIX],[look for the gnurl library in 
PREFIX/lib and headers in PREFIX/include]),
+     [_libgnurl_with=$withval],[_libgnurl_with=ifelse([$1],,[yes],[$1])])
+
+  if test "$_libgnurl_with" != "no" ; then
+
+     AC_PROG_AWK
+
+     _libgnurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); 
X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'"
+
+     _libgnurl_try_link=yes
+
+     if test -d "$_libgnurl_with" ; then
+        LIBGNURL_CPPFLAGS="-I$withval/include"
+        _libgnurl_ldflags="-L$withval/lib"
+        AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],
+                     ["$withval/bin"])
+     else
+        AC_PATH_PROG([_libgnurl_config],[gnurl-config],[],[$PATH])
+     fi
+
+     if test x$_libgnurl_config != "x" ; then
+        AC_CACHE_CHECK([for the version of libgnurl],
+           [libgnurl_cv_lib_gnurl_version],
+           [libgnurl_cv_lib_gnurl_version=`$_libgnurl_config --version | $AWK 
'{print $[]2}'`])
+
+        _libgnurl_version=`echo $libgnurl_cv_lib_gnurl_version | 
$_libgnurl_version_parse`
+        _libgnurl_wanted=`echo ifelse([$2],,[0],[$2]) | 
$_libgnurl_version_parse`
+
+        if test $_libgnurl_wanted -gt 0 ; then
+           AC_CACHE_CHECK([for libgnurl >= version $2],
+              [libgnurl_cv_lib_version_ok],
+              [
+              if test $_libgnurl_version -ge $_libgnurl_wanted ; then
+                 libgnurl_cv_lib_version_ok=yes
+              else
+                 libgnurl_cv_lib_version_ok=no
+              fi
+              ])
+        fi
+
+        if test $_libgnurl_wanted -eq 0 || test x$libgnurl_cv_lib_version_ok = 
xyes ; then
+           if test x"$LIBGNURL_CPPFLAGS" = "x" ; then
+              LIBGNURL_CPPFLAGS=`$_libgnurl_config --cflags`
+           fi
+           if test x"$LIBGNURL" = "x" ; then
+              LIBGNURL=`$_libgnurl_config --libs`
+
+              # This is so silly, but Apple actually has a bug in their
+              # gnurl-config script.  Fixed in Tiger, but there are still
+              # lots of Panther installs around.
+              case "${host}" in
+                 powerpc-apple-darwin7*)
+                    LIBGNURL=`echo $LIBGNURL | sed -e 's|-arch i386||g'`
+                 ;;
+              esac
+           fi
+
+           # All gnurl-config scripts support --feature
+           _libgnurl_features=`$_libgnurl_config --feature`
+
+           # Is it modern enough to have --protocols? (7.12.4)
+           if test $_libgnurl_version -ge 461828 ; then
+              _libgnurl_protocols=`$_libgnurl_config --protocols`
+           fi
+        else
+           _libgnurl_try_link=no
+        fi
+
+        unset _libgnurl_wanted
+     fi
+
+     if test $_libgnurl_try_link = yes ; then
+
+        # we didn't find gnurl-config, so let's see if the user-supplied
+        # link line (or failing that, "-lgnurl") is enough.
+        LIBGNURL=${LIBGNURL-"$_libgnurl_ldflags -lgnurl"}
+
+        AC_CACHE_CHECK([whether libgnurl is usable],
+           [libgnurl_cv_lib_gnurl_usable],
+           [
+           _libgnurl_save_cppflags=$CPPFLAGS
+           CPPFLAGS="$LIBGNURL_CPPFLAGS $CPPFLAGS"
+           _libgnurl_save_libs=$LIBS
+           LIBS="$LIBGNURL $LIBS"
+
+           AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <curl/curl.h>],[
+/* Try and use a few common options to force a failure if we are
+   missing symbols or can't link. */
+int x;
+curl_easy_setopt(NULL,CURLOPT_URL,NULL);
+x=CURL_ERROR_SIZE;
+x=CURLOPT_WRITEFUNCTION;
+x=CURLOPT_FILE;
+x=CURLOPT_ERRORBUFFER;
+x=CURLOPT_STDERR;
+x=CURLOPT_VERBOSE;
+])],libgnurl_cv_lib_gnurl_usable=yes,libgnurl_cv_lib_gnurl_usable=no)
+
+# BEGIN Changes from original libcurl.m4:
+# Give it a 2nd shot using 'gnurl/curl.h'
+           AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <gnurl/curl.h>],[
+/* Try and use a few common options to force a failure if we are
+   missing symbols or can't link. */
+int x;
+curl_easy_setopt(NULL,CURLOPT_URL,NULL);
+x=CURL_ERROR_SIZE;
+x=CURLOPT_WRITEFUNCTION;
+x=CURLOPT_FILE;
+x=CURLOPT_ERRORBUFFER;
+x=CURLOPT_STDERR;
+x=CURLOPT_VERBOSE;
+])],libgnurl_cv_lib_gnurl_usable=yes)
+# END Changes from original libcurl.m4:
+
+           CPPFLAGS=$_libgnurl_save_cppflags
+           LIBS=$_libgnurl_save_libs
+           unset _libgnurl_save_cppflags
+           unset _libgnurl_save_libs
+           ])
+
+        if test $libgnurl_cv_lib_gnurl_usable = yes ; then
+
+           # Does gnurl_free() exist in this version of libgnurl?
+           # If not, fake it with free()
+
+           _libgnurl_save_cppflags=$CPPFLAGS
+           CPPFLAGS="$CPPFLAGS $LIBGNURL_CPPFLAGS"
+           _libgnurl_save_libs=$LIBS
+           LIBS="$LIBS $LIBGNURL"
+
+           AC_CHECK_FUNC(curl_free,,
+              AC_DEFINE(curl_free,free,
+                [Define curl_free() as free() if our version of gnurl lacks 
curl_free.]))
+
+           CPPFLAGS=$_libgnurl_save_cppflags
+           LIBS=$_libgnurl_save_libs
+           unset _libgnurl_save_cppflags
+           unset _libgnurl_save_libs
+
+           AC_DEFINE(HAVE_LIBGNURL,1,
+             [Define to 1 if you have a functional gnurl library.])
+           AC_SUBST(LIBGNURL_CPPFLAGS)
+           AC_SUBST(LIBGNURL)
+
+           for _libgnurl_feature in $_libgnurl_features ; do
+              
AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_feature_$_libgnurl_feature),[1])
+              eval AS_TR_SH(libgnurl_feature_$_libgnurl_feature)=yes
+           done
+
+           if test "x$_libgnurl_protocols" = "x" ; then
+
+              # We don't have --protocols, so just assume that all
+              # protocols are available
+              _libgnurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP"
+
+              if test x$libgnurl_feature_SSL = xyes ; then
+                 _libgnurl_protocols="$_libgnurl_protocols HTTPS"
+
+                 # FTPS wasn't standards-compliant until version
+                 # 7.11.0 (0x070b00 == 461568)
+                 if test $_libgnurl_version -ge 461568; then
+                    _libgnurl_protocols="$_libgnurl_protocols FTPS"
+                 fi
+              fi
+
+              # RTSP, IMAP, POP3 and SMTP were added in
+              # 7.20.0 (0x071400 == 463872)
+              if test $_libgnurl_version -ge 463872; then
+                 _libgnurl_protocols="$_libgnurl_protocols RTSP IMAP POP3 SMTP"
+              fi
+           fi
+
+           for _libgnurl_protocol in $_libgnurl_protocols ; do
+              
AC_DEFINE_UNQUOTED(AS_TR_CPP(libgnurl_protocol_$_libgnurl_protocol),[1])
+              eval AS_TR_SH(libgnurl_protocol_$_libgnurl_protocol)=yes
+           done
+        else
+           unset LIBGNURL
+           unset LIBGNURL_CPPFLAGS
+        fi
+     fi
+
+     unset _libgnurl_try_link
+     unset _libgnurl_version_parse
+     unset _libgnurl_config
+     unset _libgnurl_feature
+     unset _libgnurl_features
+     unset _libgnurl_protocol
+     unset _libgnurl_protocols
+     unset _libgnurl_version
+     unset _libgnurl_ldflags
+  fi
+
+  if test x$_libgnurl_with = xno || test x$libgnurl_cv_lib_gnurl_usable != 
xyes ; then
+     # This is the IF-NO path
+     ifelse([$4],,:,[$4])
+  else
+     # This is the IF-YES path
+     ifelse([$3],,:,[$3])
+  fi
+
+  unset _libgnurl_with
+])dnl
diff --git a/m4/lock.m4 b/m4/lock.m4
new file mode 100644
index 0000000..1e83e23
--- /dev/null
+++ b/m4/lock.m4
@@ -0,0 +1,42 @@
+# lock.m4 serial 13 (gettext-0.18.2)
+dnl Copyright (C) 2005-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([gl_LOCK],
+[
+  AC_REQUIRE([gl_THREADLIB])
+  if test "$gl_threads_api" = posix; then
+    # OSF/1 4.0 and Mac OS X 10.1 lack the pthread_rwlock_t type and the
+    # pthread_rwlock_* functions.
+    AC_CHECK_TYPE([pthread_rwlock_t],
+      [AC_DEFINE([HAVE_PTHREAD_RWLOCK], [1],
+         [Define if the POSIX multithreading library has read/write locks.])],
+      [],
+      [#include <pthread.h>])
+    # glibc defines PTHREAD_MUTEX_RECURSIVE as enum, not as a macro.
+    AC_COMPILE_IFELSE([
+      AC_LANG_PROGRAM(
+        [[#include <pthread.h>]],
+        [[
+#if __FreeBSD__ == 4
+error "No, in FreeBSD 4.0 recursive mutexes actually don't work."
+#elif (defined __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ \
+       && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1070)
+error "No, in Mac OS X < 10.7 recursive mutexes actually don't work."
+#else
+int x = (int)PTHREAD_MUTEX_RECURSIVE;
+return !x;
+#endif
+        ]])],
+      [AC_DEFINE([HAVE_PTHREAD_MUTEX_RECURSIVE], [1],
+         [Define if the <pthread.h> defines PTHREAD_MUTEX_RECURSIVE.])])
+  fi
+  gl_PREREQ_LOCK
+])
+
+# Prerequisites of lib/glthread/lock.c.
+AC_DEFUN([gl_PREREQ_LOCK], [:])
diff --git a/m4/longlong.m4 b/m4/longlong.m4
new file mode 100644
index 0000000..36d8b12
--- /dev/null
+++ b/m4/longlong.m4
@@ -0,0 +1,113 @@
+# longlong.m4 serial 17
+dnl Copyright (C) 1999-2007, 2009-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Paul Eggert.
+
+# Define HAVE_LONG_LONG_INT if 'long long int' works.
+# This fixes a bug in Autoconf 2.61, and can be faster
+# than what's in Autoconf 2.62 through 2.68.
+
+# Note: If the type 'long long int' exists but is only 32 bits large
+# (as on some very old compilers), HAVE_LONG_LONG_INT will not be
+# defined. In this case you can treat 'long long int' like 'long int'.
+
+AC_DEFUN([AC_TYPE_LONG_LONG_INT],
+[
+  AC_REQUIRE([AC_TYPE_UNSIGNED_LONG_LONG_INT])
+  AC_CACHE_CHECK([for long long int], [ac_cv_type_long_long_int],
+     [ac_cv_type_long_long_int=yes
+      if test "x${ac_cv_prog_cc_c99-no}" = xno; then
+        ac_cv_type_long_long_int=$ac_cv_type_unsigned_long_long_int
+        if test $ac_cv_type_long_long_int = yes; then
+          dnl Catch a bug in Tandem NonStop Kernel (OSS) cc -O circa 2004.
+          dnl If cross compiling, assume the bug is not important, since
+          dnl nobody cross compiles for this platform as far as we know.
+          AC_RUN_IFELSE(
+            [AC_LANG_PROGRAM(
+               [[@%:@include <limits.h>
+                 @%:@ifndef LLONG_MAX
+                 @%:@ define HALF \
+                          (1LL << (sizeof (long long int) * CHAR_BIT - 2))
+                 @%:@ define LLONG_MAX (HALF - 1 + HALF)
+                 @%:@endif]],
+               [[long long int n = 1;
+                 int i;
+                 for (i = 0; ; i++)
+                   {
+                     long long int m = n << i;
+                     if (m >> i != n)
+                       return 1;
+                     if (LLONG_MAX / 2 < m)
+                       break;
+                   }
+                 return 0;]])],
+            [],
+            [ac_cv_type_long_long_int=no],
+            [:])
+        fi
+      fi])
+  if test $ac_cv_type_long_long_int = yes; then
+    AC_DEFINE([HAVE_LONG_LONG_INT], [1],
+      [Define to 1 if the system has the type 'long long int'.])
+  fi
+])
+
+# Define HAVE_UNSIGNED_LONG_LONG_INT if 'unsigned long long int' works.
+# This fixes a bug in Autoconf 2.61, and can be faster
+# than what's in Autoconf 2.62 through 2.68.
+
+# Note: If the type 'unsigned long long int' exists but is only 32 bits
+# large (as on some very old compilers), AC_TYPE_UNSIGNED_LONG_LONG_INT
+# will not be defined. In this case you can treat 'unsigned long long int'
+# like 'unsigned long int'.
+
+AC_DEFUN([AC_TYPE_UNSIGNED_LONG_LONG_INT],
+[
+  AC_CACHE_CHECK([for unsigned long long int],
+    [ac_cv_type_unsigned_long_long_int],
+    [ac_cv_type_unsigned_long_long_int=yes
+     if test "x${ac_cv_prog_cc_c99-no}" = xno; then
+       AC_LINK_IFELSE(
+         [_AC_TYPE_LONG_LONG_SNIPPET],
+         [],
+         [ac_cv_type_unsigned_long_long_int=no])
+     fi])
+  if test $ac_cv_type_unsigned_long_long_int = yes; then
+    AC_DEFINE([HAVE_UNSIGNED_LONG_LONG_INT], [1],
+      [Define to 1 if the system has the type 'unsigned long long int'.])
+  fi
+])
+
+# Expands to a C program that can be used to test for simultaneous support
+# of 'long long' and 'unsigned long long'. We don't want to say that
+# 'long long' is available if 'unsigned long long' is not, or vice versa,
+# because too many programs rely on the symmetry between signed and unsigned
+# integer types (excluding 'bool').
+AC_DEFUN([_AC_TYPE_LONG_LONG_SNIPPET],
+[
+  AC_LANG_PROGRAM(
+    [[/* For now, do not test the preprocessor; as of 2007 there are too many
+         implementations with broken preprocessors.  Perhaps this can
+         be revisited in 2012.  In the meantime, code should not expect
+         #if to work with literals wider than 32 bits.  */
+      /* Test literals.  */
+      long long int ll = 9223372036854775807ll;
+      long long int nll = -9223372036854775807LL;
+      unsigned long long int ull = 18446744073709551615ULL;
+      /* Test constant expressions.   */
+      typedef int a[((-9223372036854775807LL < 0 && 0 < 9223372036854775807ll)
+                     ? 1 : -1)];
+      typedef int b[(18446744073709551615ULL <= (unsigned long long int) -1
+                     ? 1 : -1)];
+      int i = 63;]],
+    [[/* Test availability of runtime routines for shift and division.  */
+      long long int llmax = 9223372036854775807ll;
+      unsigned long long int ullmax = 18446744073709551615ull;
+      return ((ll << 63) | (ll >> 63) | (ll < i) | (ll > i)
+              | (llmax / ll) | (llmax % ll)
+              | (ull << 63) | (ull >> 63) | (ull << i) | (ull >> i)
+              | (ullmax / ull) | (ullmax % ull));]])
+])
diff --git a/m4/m4_ax_python_module.m4 b/m4/m4_ax_python_module.m4
new file mode 100644
index 0000000..f0f873d
--- /dev/null
+++ b/m4/m4_ax_python_module.m4
@@ -0,0 +1,56 @@
+# ===========================================================================
+#     https://www.gnu.org/software/autoconf-archive/ax_python_module.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_PYTHON_MODULE(modname[, fatal, python])
+#
+# DESCRIPTION
+#
+#   Checks for Python module.
+#
+#   If fatal is non-empty then absence of a module will trigger an error.
+#   The third parameter can either be "python" for Python 2 or "python3" for
+#   Python 3; defaults to Python 3.
+#
+# LICENSE
+#
+#   Copyright (c) 2008 Andrew Collier
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved. This file is offered as-is, without any
+#   warranty.
+
+#serial 9
+
+AU_ALIAS([AC_PYTHON_MODULE], [AX_PYTHON_MODULE])
+AC_DEFUN([AX_PYTHON_MODULE],[
+    if test -z $PYTHON;
+    then
+        if test -z "$3";
+        then
+            PYTHON="python3"
+        else
+            PYTHON="$3"
+        fi
+    fi
+    PYTHON_NAME=`basename $PYTHON`
+    AC_MSG_CHECKING($PYTHON_NAME module: $1)
+    $PYTHON -c "import $1" 2>/dev/null
+    if test $? -eq 0;
+    then
+        AC_MSG_RESULT(yes)
+        eval AS_TR_CPP(HAVE_PYMOD_$1)=yes
+    else
+        AC_MSG_RESULT(no)
+        eval AS_TR_CPP(HAVE_PYMOD_$1)=no
+        #
+        if test -n "$2"
+        then
+            AC_MSG_ERROR(failed to find required module $1)
+            exit 1
+        fi
+    fi
+])
diff --git a/m4/mhd.m4 b/m4/mhd.m4
new file mode 100644
index 0000000..40e5b46
--- /dev/null
+++ b/m4/mhd.m4
@@ -0,0 +1,49 @@
+# mhd.m4
+
+#  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, If not, see 
<http://www.gnu.org/license>
+
+# serial 1
+
+dnl MHD_VERSION_AT_LEAST([VERSION])
+dnl
+dnl Check that microhttpd.h can be used to build a program that prints out
+dnl the MHD_VERSION tuple in X.Y.Z format, and that X.Y.Z is greater or equal
+dnl to VERSION.  If not, display message and cause the configure script to
+dnl exit failurefully.
+dnl
+dnl This uses AX_COMPARE_VERSION to do the job.
+dnl It sets shell var mhd_cv_version, as well.
+dnl
+AC_DEFUN([MHD_VERSION_AT_LEAST],
+[AC_CACHE_CHECK([libmicrohttpd version],[mhd_cv_version],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[
+  #include <stdio.h>
+  #include <microhttpd.h>
+]],[[
+  int v = MHD_VERSION;
+  printf ("%x.%x.%x\n",
+          (v >> 24) & 0xff,
+          (v >> 16) & 0xff,
+          (v >>  8) & 0xff);
+]])],
+  [mhd_cv_version=$(./conftest)],
+  [mhd_cv_version=0])])
+AX_COMPARE_VERSION([$mhd_cv_version],[ge],[$1],,
+  [AC_MSG_ERROR([[
+***
+*** You need libmicrohttpd >= $1 to build this program.
+*** ]])])])
+
+# mhd.m4 ends here
diff --git a/m4/nls.m4 b/m4/nls.m4
new file mode 100644
index 0000000..afdb9ca
--- /dev/null
+++ b/m4/nls.m4
@@ -0,0 +1,32 @@
+# nls.m4 serial 5 (gettext-0.18)
+dnl Copyright (C) 1995-2003, 2005-2006, 2008-2014, 2016 Free Software
+dnl Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl   Bruno Haible <haible@clisp.cons.org>, 2000-2003.
+
+AC_PREREQ([2.50])
+
+AC_DEFUN([AM_NLS],
+[
+  AC_MSG_CHECKING([whether NLS is requested])
+  dnl Default is enabled NLS
+  AC_ARG_ENABLE([nls],
+    [  --disable-nls           do not use Native Language Support],
+    USE_NLS=$enableval, USE_NLS=yes)
+  AC_MSG_RESULT([$USE_NLS])
+  AC_SUBST([USE_NLS])
+])
diff --git a/m4/po.m4 b/m4/po.m4
new file mode 100644
index 0000000..c5a2f6b
--- /dev/null
+++ b/m4/po.m4
@@ -0,0 +1,453 @@
+# po.m4 serial 24 (gettext-0.19)
+dnl Copyright (C) 1995-2014, 2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1995-2000.
+dnl   Bruno Haible <haible@clisp.cons.org>, 2000-2003.
+
+AC_PREREQ([2.60])
+
+dnl Checks for all prerequisites of the po subdirectory.
+AC_DEFUN([AM_PO_SUBDIRS],
+[
+  AC_REQUIRE([AC_PROG_MAKE_SET])dnl
+  AC_REQUIRE([AC_PROG_INSTALL])dnl
+  AC_REQUIRE([AC_PROG_MKDIR_P])dnl
+  AC_REQUIRE([AC_PROG_SED])dnl
+  AC_REQUIRE([AM_NLS])dnl
+
+  dnl Release version of the gettext macros. This is used to ensure that
+  dnl the gettext macros and po/Makefile.in.in are in sync.
+  AC_SUBST([GETTEXT_MACRO_VERSION], [0.19])
+
+  dnl Perform the following tests also if --disable-nls has been given,
+  dnl because they are needed for "make dist" to work.
+
+  dnl Search for GNU msgfmt in the PATH.
+  dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions.
+  dnl The second test excludes FreeBSD msgfmt.
+  AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt,
+    [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+     (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage 
>/dev/null; then exit 1; else exit 0; fi)],
+    :)
+  AC_PATH_PROG([GMSGFMT], [gmsgfmt], [$MSGFMT])
+
+  dnl Test whether it is GNU msgfmt >= 0.15.
+changequote(,)dnl
+  case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;;
+    *) MSGFMT_015=$MSGFMT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([MSGFMT_015])
+changequote(,)dnl
+  case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;;
+    *) GMSGFMT_015=$GMSGFMT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([GMSGFMT_015])
+
+  dnl Search for GNU xgettext 0.12 or newer in the PATH.
+  dnl The first test excludes Solaris xgettext and early GNU xgettext versions.
+  dnl The second test excludes FreeBSD xgettext.
+  AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext,
+    [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= 
/dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 &&
+     (if $ac_dir/$ac_word --omit-header --copyright-holder= 
--msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then 
exit 1; else exit 0; fi)],
+    :)
+  dnl Remove leftover from FreeBSD xgettext call.
+  rm -f messages.po
+
+  dnl Test whether it is GNU xgettext >= 0.15.
+changequote(,)dnl
+  case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in
+    '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;;
+    *) XGETTEXT_015=$XGETTEXT ;;
+  esac
+changequote([,])dnl
+  AC_SUBST([XGETTEXT_015])
+
+  dnl Search for GNU msgmerge 0.11 or newer in the PATH.
+  AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge,
+    [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 
2>&1], :)
+
+  dnl Installation directories.
+  dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we
+  dnl have to define it here, so that it can be used in po/Makefile.
+  test -n "$localedir" || localedir='${datadir}/locale'
+  AC_SUBST([localedir])
+
+  dnl Support for AM_XGETTEXT_OPTION.
+  test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS=
+  AC_SUBST([XGETTEXT_EXTRA_OPTIONS])
+
+  AC_CONFIG_COMMANDS([po-directories], [[
+    for ac_file in $CONFIG_FILES; do
+      # Support "outfile[:infile[:infile...]]"
+      case "$ac_file" in
+        *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;;
+      esac
+      # PO directories have a Makefile.in generated from Makefile.in.in.
+      case "$ac_file" in */Makefile.in)
+        # Adjust a relative srcdir.
+        ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+        ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
+        ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+        # In autoconf-2.13 it is called $ac_given_srcdir.
+        # In autoconf-2.50 it is called $srcdir.
+        test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+        case "$ac_given_srcdir" in
+          .)  top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+          /*) top_srcdir="$ac_given_srcdir" ;;
+          *)  top_srcdir="$ac_dots$ac_given_srcdir" ;;
+        esac
+        # Treat a directory as a PO directory if and only if it has a
+        # POTFILES.in file. This allows packages to have multiple PO
+        # directories under different names or in different locations.
+        if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then
+          rm -f "$ac_dir/POTFILES"
+          test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo 
"creating $ac_dir/POTFILES"
+          gt_tab=`printf '\t'`
+          cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ 
${gt_tab}]*\$/d" -e "s,.*,     $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) 
\\\\/\1/" > "$ac_dir/POTFILES"
+          POMAKEFILEDEPS="POTFILES.in"
+          # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend
+          # on $ac_dir but don't depend on user-specified configuration
+          # parameters.
+          if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+            # The LINGUAS file contains the set of available languages.
+            if test -n "$OBSOLETE_ALL_LINGUAS"; then
+              test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in 
configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is 
obsolete"
+            fi
+            ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" 
"$ac_given_srcdir/$ac_dir/LINGUAS"`
+            # Hide the ALL_LINGUAS assignment from automake < 1.5.
+            eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+            POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+          else
+            # The set of available languages was given in configure.in.
+            # Hide the ALL_LINGUAS assignment from automake < 1.5.
+            eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS'
+          fi
+          # Compute POFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+          # Compute UPDATEPOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+          # Compute DUMMYPOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+          # Compute GMOFILES
+          # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+          case "$ac_given_srcdir" in
+            .) srcdirpre= ;;
+            *) srcdirpre='$(srcdir)/' ;;
+          esac
+          POFILES=
+          UPDATEPOFILES=
+          DUMMYPOFILES=
+          GMOFILES=
+          for lang in $ALL_LINGUAS; do
+            POFILES="$POFILES $srcdirpre$lang.po"
+            UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+            DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+            GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+          done
+          # CATALOGS depends on both $ac_dir and the user's LINGUAS
+          # environment variable.
+          INST_LINGUAS=
+          if test -n "$ALL_LINGUAS"; then
+            for presentlang in $ALL_LINGUAS; do
+              useit=no
+              if test "%UNSET%" != "$LINGUAS"; then
+                desiredlanguages="$LINGUAS"
+              else
+                desiredlanguages="$ALL_LINGUAS"
+              fi
+              for desiredlang in $desiredlanguages; do
+                # Use the presentlang catalog if desiredlang is
+                #   a. equal to presentlang, or
+                #   b. a variant of presentlang (because in this case,
+                #      presentlang can be used as a fallback for messages
+                #      which are not translated in the desiredlang catalog).
+                case "$desiredlang" in
+                  "$presentlang"*) useit=yes;;
+                esac
+              done
+              if test $useit = yes; then
+                INST_LINGUAS="$INST_LINGUAS $presentlang"
+              fi
+            done
+          fi
+          CATALOGS=
+          if test -n "$INST_LINGUAS"; then
+            for lang in $INST_LINGUAS; do
+              CATALOGS="$CATALOGS $lang.gmo"
+            done
+          fi
+          test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo 
"creating $ac_dir/Makefile"
+          sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r 
$ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e 
"s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e 
"s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e 
"s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > 
"$ac_dir/Makefile"
+          for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do
+            if test -f "$f"; then
+              case "$f" in
+                *.orig | *.bak | *~) ;;
+                *) cat "$f" >> "$ac_dir/Makefile" ;;
+              esac
+            fi
+          done
+        fi
+        ;;
+      esac
+    done]],
+   [# Capture the value of obsolete ALL_LINGUAS because we need it to compute
+    # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it
+    # from automake < 1.5.
+    eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"'
+    # Capture the value of LINGUAS because we need it to compute CATALOGS.
+    LINGUAS="${LINGUAS-%UNSET%}"
+   ])
+])
+
+dnl Postprocesses a Makefile in a directory containing PO files.
+AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE],
+[
+  # When this code is run, in config.status, two variables have already been
+  # set:
+  # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in,
+  # - LINGUAS is the value of the environment variable LINGUAS at configure
+  #   time.
+
+changequote(,)dnl
+  # Adjust a relative srcdir.
+  ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'`
+  ac_dir_suffix=/`echo "$ac_dir"|sed 's%^\./%%'`
+  ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'`
+  # In autoconf-2.13 it is called $ac_given_srcdir.
+  # In autoconf-2.50 it is called $srcdir.
+  test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir"
+  case "$ac_given_srcdir" in
+    .)  top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;;
+    /*) top_srcdir="$ac_given_srcdir" ;;
+    *)  top_srcdir="$ac_dots$ac_given_srcdir" ;;
+  esac
+
+  # Find a way to echo strings without interpreting backslash.
+  if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then
+    gt_echo='echo'
+  else
+    if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then
+      gt_echo='printf %s\n'
+    else
+      echo_func () {
+        cat <<EOT
+$*
+EOT
+      }
+      gt_echo='echo_func'
+    fi
+  fi
+
+  # A sed script that extracts the value of VARIABLE from a Makefile.
+  tab=`printf '\t'`
+  sed_x_variable='
+# Test if the hold space is empty.
+x
+s/P/P/
+x
+ta
+# Yes it was empty. Look if we have the expected variable definition.
+/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=/{
+  # Seen the first line of the variable definition.
+  s/^['"${tab}"' ]*VARIABLE['"${tab}"' ]*=//
+  ba
+}
+bd
+:a
+# Here we are processing a line from the variable definition.
+# Remove comment, more precisely replace it with a space.
+s/#.*$/ /
+# See if the line ends in a backslash.
+tb
+:b
+s/\\$//
+# Print the line, without the trailing backslash.
+p
+tc
+# There was no trailing backslash. The end of the variable definition is
+# reached. Clear the hold space.
+s/^.*$//
+x
+bd
+:c
+# A trailing backslash means that the variable definition continues in the
+# next line. Put a nonempty string into the hold space to indicate this.
+s/^.*$/P/
+x
+:d
+'
+changequote([,])dnl
+
+  # Set POTFILES to the value of the Makefile variable POTFILES.
+  sed_x_POTFILES=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 
's/VARIABLE/POTFILES/g'`
+  POTFILES=`sed -n -e "$sed_x_POTFILES" < "$ac_file"`
+  # Compute POTFILES_DEPS as
+  #   $(foreach file, $(POTFILES), $(top_srcdir)/$(file))
+  POTFILES_DEPS=
+  for file in $POTFILES; do
+    POTFILES_DEPS="$POTFILES_DEPS "'$(top_srcdir)/'"$file"
+  done
+  POMAKEFILEDEPS=""
+
+  if test -n "$OBSOLETE_ALL_LINGUAS"; then
+    test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is 
obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete"
+  fi
+  if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then
+    # The LINGUAS file contains the set of available languages.
+    ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" 
"$ac_given_srcdir/$ac_dir/LINGUAS"`
+    POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS"
+  else
+    # Set ALL_LINGUAS to the value of the Makefile variable LINGUAS.
+    sed_x_LINGUAS=`$gt_echo "$sed_x_variable" | sed -e '/^ *#/d' -e 
's/VARIABLE/LINGUAS/g'`
+    ALL_LINGUAS_=`sed -n -e "$sed_x_LINGUAS" < "$ac_file"`
+  fi
+  # Hide the ALL_LINGUAS assignment from automake < 1.5.
+  eval 'ALL_LINGUAS''=$ALL_LINGUAS_'
+  # Compute POFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po)
+  # Compute UPDATEPOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(lang).po-update)
+  # Compute DUMMYPOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(lang).nop)
+  # Compute GMOFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo)
+  # Compute PROPERTIESFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), 
$(top_srcdir)/$(DOMAIN)_$(lang).properties)
+  # Compute CLASSFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), 
$(top_srcdir)/$(DOMAIN)_$(lang).class)
+  # Compute QMFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).qm)
+  # Compute MSGFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob $(lang)).msg)
+  # Compute RESOURCESDLLFILES
+  # as      $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(frob 
$(lang))/$(DOMAIN).resources.dll)
+  case "$ac_given_srcdir" in
+    .) srcdirpre= ;;
+    *) srcdirpre='$(srcdir)/' ;;
+  esac
+  POFILES=
+  UPDATEPOFILES=
+  DUMMYPOFILES=
+  GMOFILES=
+  PROPERTIESFILES=
+  CLASSFILES=
+  QMFILES=
+  MSGFILES=
+  RESOURCESDLLFILES=
+  for lang in $ALL_LINGUAS; do
+    POFILES="$POFILES $srcdirpre$lang.po"
+    UPDATEPOFILES="$UPDATEPOFILES $lang.po-update"
+    DUMMYPOFILES="$DUMMYPOFILES $lang.nop"
+    GMOFILES="$GMOFILES $srcdirpre$lang.gmo"
+    PROPERTIESFILES="$PROPERTIESFILES 
\$(top_srcdir)/\$(DOMAIN)_$lang.properties"
+    CLASSFILES="$CLASSFILES \$(top_srcdir)/\$(DOMAIN)_$lang.class"
+    QMFILES="$QMFILES $srcdirpre$lang.qm"
+    frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 
'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+    MSGFILES="$MSGFILES $srcdirpre$frobbedlang.msg"
+    frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 
's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 
's/^uz-UZ$/uz-UZ-Latn/'`
+    RESOURCESDLLFILES="$RESOURCESDLLFILES 
$srcdirpre$frobbedlang/\$(DOMAIN).resources.dll"
+  done
+  # CATALOGS depends on both $ac_dir and the user's LINGUAS
+  # environment variable.
+  INST_LINGUAS=
+  if test -n "$ALL_LINGUAS"; then
+    for presentlang in $ALL_LINGUAS; do
+      useit=no
+      if test "%UNSET%" != "$LINGUAS"; then
+        desiredlanguages="$LINGUAS"
+      else
+        desiredlanguages="$ALL_LINGUAS"
+      fi
+      for desiredlang in $desiredlanguages; do
+        # Use the presentlang catalog if desiredlang is
+        #   a. equal to presentlang, or
+        #   b. a variant of presentlang (because in this case,
+        #      presentlang can be used as a fallback for messages
+        #      which are not translated in the desiredlang catalog).
+        case "$desiredlang" in
+          "$presentlang"*) useit=yes;;
+        esac
+      done
+      if test $useit = yes; then
+        INST_LINGUAS="$INST_LINGUAS $presentlang"
+      fi
+    done
+  fi
+  CATALOGS=
+  JAVACATALOGS=
+  QTCATALOGS=
+  TCLCATALOGS=
+  CSHARPCATALOGS=
+  if test -n "$INST_LINGUAS"; then
+    for lang in $INST_LINGUAS; do
+      CATALOGS="$CATALOGS $lang.gmo"
+      JAVACATALOGS="$JAVACATALOGS \$(DOMAIN)_$lang.properties"
+      QTCATALOGS="$QTCATALOGS $lang.qm"
+      frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 
'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+      TCLCATALOGS="$TCLCATALOGS $frobbedlang.msg"
+      frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 
's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 
's/^uz-UZ$/uz-UZ-Latn/'`
+      CSHARPCATALOGS="$CSHARPCATALOGS $frobbedlang/\$(DOMAIN).resources.dll"
+    done
+  fi
+
+  sed -e "s|@POTFILES_DEPS@|$POTFILES_DEPS|g" -e "s|@POFILES@|$POFILES|g" -e 
"s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e 
"s|@GMOFILES@|$GMOFILES|g" -e "s|@PROPERTIESFILES@|$PROPERTIESFILES|g" -e 
"s|@CLASSFILES@|$CLASSFILES|g" -e "s|@QMFILES@|$QMFILES|g" -e 
"s|@MSGFILES@|$MSGFILES|g" -e "s|@RESOURCESDLLFILES@|$RESOURCESDLLFILES|g" -e 
"s|@CATALOGS@|$CATALOGS|g" -e "s|@JAVACATALOGS@|$JAVACATALOGS|g" -e 
"s|@QTCATALOGS@|$QTCATALOGS|g" -e "s|@TCLCATALOGS@|$TCL [...]
+  tab=`printf '\t'`
+  if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then
+    # Add dependencies that cannot be formulated as a simple suffix rule.
+    for lang in $ALL_LINGUAS; do
+      frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 
'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'`
+      cat >> "$ac_file.tmp" <<EOF
+$frobbedlang.msg: $lang.po
+${tab}@echo "\$(MSGFMT) -c --tcl -d \$(srcdir) -l $lang $srcdirpre$lang.po"; \
+${tab}\$(MSGFMT) -c --tcl -d "\$(srcdir)" -l $lang $srcdirpre$lang.po || { rm 
-f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+    done
+  fi
+  if grep -l '@CSHARPCATALOGS@' "$ac_file" > /dev/null; then
+    # Add dependencies that cannot be formulated as a simple suffix rule.
+    for lang in $ALL_LINGUAS; do
+      frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 
's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 
's/^uz-UZ$/uz-UZ-Latn/'`
+      cat >> "$ac_file.tmp" <<EOF
+$frobbedlang/\$(DOMAIN).resources.dll: $lang.po
+${tab}@echo "\$(MSGFMT) -c --csharp -d \$(srcdir) -l $lang $srcdirpre$lang.po 
-r \$(DOMAIN)"; \
+${tab}\$(MSGFMT) -c --csharp -d "\$(srcdir)" -l $lang $srcdirpre$lang.po -r 
"\$(DOMAIN)" || { rm -f "\$(srcdir)/$frobbedlang.msg"; exit 1; }
+EOF
+    done
+  fi
+  if test -n "$POMAKEFILEDEPS"; then
+    cat >> "$ac_file.tmp" <<EOF
+Makefile: $POMAKEFILEDEPS
+EOF
+  fi
+  mv "$ac_file.tmp" "$ac_file"
+])
+
+dnl Initializes the accumulator used by AM_XGETTEXT_OPTION.
+AC_DEFUN([AM_XGETTEXT_OPTION_INIT],
+[
+  XGETTEXT_EXTRA_OPTIONS=
+])
+
+dnl Registers an option to be passed to xgettext in the po subdirectory.
+AC_DEFUN([AM_XGETTEXT_OPTION],
+[
+  AC_REQUIRE([AM_XGETTEXT_OPTION_INIT])
+  XGETTEXT_EXTRA_OPTIONS="$XGETTEXT_EXTRA_OPTIONS $1"
+])
diff --git a/m4/printf-posix.m4 b/m4/printf-posix.m4
new file mode 100644
index 0000000..f9088c0
--- /dev/null
+++ b/m4/printf-posix.m4
@@ -0,0 +1,48 @@
+# printf-posix.m4 serial 6 (gettext-0.18.2)
+dnl Copyright (C) 2003, 2007, 2009-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+dnl Test whether the printf() function supports POSIX/XSI format strings with
+dnl positions.
+
+AC_DEFUN([gt_PRINTF_POSIX],
+[
+  AC_REQUIRE([AC_PROG_CC])
+  AC_CACHE_CHECK([whether printf() supports POSIX/XSI format strings],
+    gt_cv_func_printf_posix,
+    [
+      AC_RUN_IFELSE(
+        [AC_LANG_SOURCE([[
+#include <stdio.h>
+#include <string.h>
+/* The string "%2$d %1$d", with dollar characters protected from the shell's
+   dollar expansion (possibly an autoconf bug).  */
+static char format[] = { '%', '2', '$', 'd', ' ', '%', '1', '$', 'd', '\0' };
+static char buf[100];
+int main ()
+{
+  sprintf (buf, format, 33, 55);
+  return (strcmp (buf, "55 33") != 0);
+}]])],
+        [gt_cv_func_printf_posix=yes],
+        [gt_cv_func_printf_posix=no],
+        [
+          AC_EGREP_CPP([notposix], [
+#if defined __NetBSD__ || defined __BEOS__ || defined _MSC_VER || defined 
__MINGW32__ || defined __CYGWIN__
+  notposix
+#endif
+            ],
+            [gt_cv_func_printf_posix="guessing no"],
+            [gt_cv_func_printf_posix="guessing yes"])
+        ])
+    ])
+  case $gt_cv_func_printf_posix in
+    *yes)
+      AC_DEFINE([HAVE_POSIX_PRINTF], [1],
+        [Define if your printf() function supports format strings with 
positions.])
+      ;;
+  esac
+])
diff --git a/m4/progtest.m4 b/m4/progtest.m4
new file mode 100644
index 0000000..9ace7c3
--- /dev/null
+++ b/m4/progtest.m4
@@ -0,0 +1,91 @@
+# progtest.m4 serial 7 (gettext-0.18.2)
+dnl Copyright (C) 1996-2003, 2005, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+dnl
+dnl This file can be used in projects which are not available under
+dnl the GNU General Public License or the GNU Library General Public
+dnl License but which still want to provide support for the GNU gettext
+dnl functionality.
+dnl Please note that the actual code of the GNU gettext library is covered
+dnl by the GNU Library General Public License, and the rest of the GNU
+dnl gettext package is covered by the GNU General Public License.
+dnl They are *not* in the public domain.
+
+dnl Authors:
+dnl   Ulrich Drepper <drepper@cygnus.com>, 1996.
+
+AC_PREREQ([2.50])
+
+# Search path for a program which passes the given test.
+
+dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR,
+dnl   TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]])
+AC_DEFUN([AM_PATH_PROG_WITH_TEST],
+[
+# Prepare PATH_SEPARATOR.
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  # Determine PATH_SEPARATOR by trying to find /bin/sh in a PATH which
+  # contains only /bin. Note that ksh looks also at the FPATH variable,
+  # so we have to set that as well for the test.
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+    && { (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 \
+           || PATH_SEPARATOR=';'
+       }
+fi
+
+# Find out how to test for executable files. Don't use a zero-byte file,
+# as systems may use methods other than mode bits to determine executability.
+cat >conf$$.file <<_ASEOF
+#! /bin/sh
+exit 0
+_ASEOF
+chmod +x conf$$.file
+if test -x conf$$.file >/dev/null 2>&1; then
+  ac_executable_p="test -x"
+else
+  ac_executable_p="test -f"
+fi
+rm -f conf$$.file
+
+# Extract the first word of "$2", so it can be a program name with args.
+set dummy $2; ac_word=[$]2
+AC_MSG_CHECKING([for $ac_word])
+AC_CACHE_VAL([ac_cv_path_$1],
+[case "[$]$1" in
+  [[\\/]]* | ?:[[\\/]]*)
+    ac_cv_path_$1="[$]$1" # Let the user override the test with a path.
+    ;;
+  *)
+    ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR
+    for ac_dir in ifelse([$5], , $PATH, [$5]); do
+      IFS="$ac_save_IFS"
+      test -z "$ac_dir" && ac_dir=.
+      for ac_exec_ext in '' $ac_executable_extensions; do
+        if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then
+          echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD
+          if [$3]; then
+            ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext"
+            break 2
+          fi
+        fi
+      done
+    done
+    IFS="$ac_save_IFS"
+dnl If no 4th arg is given, leave the cache variable unset,
+dnl so AC_PATH_PROGS will keep looking.
+ifelse([$4], , , [  test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4"
+])dnl
+    ;;
+esac])dnl
+$1="$ac_cv_path_$1"
+if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then
+  AC_MSG_RESULT([$][$1])
+else
+  AC_MSG_RESULT([no])
+fi
+AC_SUBST([$1])dnl
+])
diff --git a/m4/size_max.m4 b/m4/size_max.m4
new file mode 100644
index 0000000..de69025
--- /dev/null
+++ b/m4/size_max.m4
@@ -0,0 +1,79 @@
+# size_max.m4 serial 10
+dnl Copyright (C) 2003, 2005-2006, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+AC_DEFUN([gl_SIZE_MAX],
+[
+  AC_CHECK_HEADERS([stdint.h])
+  dnl First test whether the system already has SIZE_MAX.
+  AC_CACHE_CHECK([for SIZE_MAX], [gl_cv_size_max], [
+    gl_cv_size_max=
+    AC_EGREP_CPP([Found it], [
+#include <limits.h>
+#if HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef SIZE_MAX
+Found it
+#endif
+], [gl_cv_size_max=yes])
+    if test -z "$gl_cv_size_max"; then
+      dnl Define it ourselves. Here we assume that the type 'size_t' is not 
wider
+      dnl than the type 'unsigned long'. Try hard to find a definition that can
+      dnl be used in a preprocessor #if, i.e. doesn't contain a cast.
+      AC_COMPUTE_INT([size_t_bits_minus_1], [sizeof (size_t) * CHAR_BIT - 1],
+        [#include <stddef.h>
+#include <limits.h>], [size_t_bits_minus_1=])
+      AC_COMPUTE_INT([fits_in_uint], [sizeof (size_t) <= sizeof (unsigned 
int)],
+        [#include <stddef.h>], [fits_in_uint=])
+      if test -n "$size_t_bits_minus_1" && test -n "$fits_in_uint"; then
+        if test $fits_in_uint = 1; then
+          dnl Even though SIZE_MAX fits in an unsigned int, it must be of type
+          dnl 'unsigned long' if the type 'size_t' is the same as 'unsigned 
long'.
+          AC_COMPILE_IFELSE(
+            [AC_LANG_PROGRAM(
+               [[#include <stddef.h>
+                 extern size_t foo;
+                 extern unsigned long foo;
+               ]],
+               [[]])],
+            [fits_in_uint=0])
+        fi
+        dnl We cannot use 'expr' to simplify this expression, because 'expr'
+        dnl works only with 'long' integers in the host environment, while we
+        dnl might be cross-compiling from a 32-bit platform to a 64-bit 
platform.
+        if test $fits_in_uint = 1; then
+          gl_cv_size_max="(((1U << $size_t_bits_minus_1) - 1) * 2 + 1)"
+        else
+          gl_cv_size_max="(((1UL << $size_t_bits_minus_1) - 1) * 2 + 1)"
+        fi
+      else
+        dnl Shouldn't happen, but who knows...
+        gl_cv_size_max='((size_t)~(size_t)0)'
+      fi
+    fi
+  ])
+  if test "$gl_cv_size_max" != yes; then
+    AC_DEFINE_UNQUOTED([SIZE_MAX], [$gl_cv_size_max],
+      [Define as the maximum value of type 'size_t', if the system doesn't 
define it.])
+  fi
+  dnl Don't redefine SIZE_MAX in config.h if config.h is re-included after
+  dnl <stdint.h>. Remember that the #undef in AH_VERBATIM gets replaced with
+  dnl #define by AC_DEFINE_UNQUOTED.
+  AH_VERBATIM([SIZE_MAX],
+[/* Define as the maximum value of type 'size_t', if the system doesn't define
+   it. */
+#ifndef SIZE_MAX
+# undef SIZE_MAX
+#endif])
+])
+
+dnl Autoconf >= 2.61 has AC_COMPUTE_INT built-in.
+dnl Remove this when we can assume autoconf >= 2.61.
+m4_ifdef([AC_COMPUTE_INT], [], [
+  AC_DEFUN([AC_COMPUTE_INT], [_AC_COMPUTE_INT([$2],[$1],[$3],[$4])])
+])
diff --git a/m4/stdint_h.m4 b/m4/stdint_h.m4
new file mode 100644
index 0000000..f823b94
--- /dev/null
+++ b/m4/stdint_h.m4
@@ -0,0 +1,27 @@
+# stdint_h.m4 serial 9
+dnl Copyright (C) 1997-2004, 2006, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Paul Eggert.
+
+# Define HAVE_STDINT_H_WITH_UINTMAX if <stdint.h> exists,
+# doesn't clash with <sys/types.h>, and declares uintmax_t.
+
+AC_DEFUN([gl_AC_HEADER_STDINT_H],
+[
+  AC_CACHE_CHECK([for stdint.h], [gl_cv_header_stdint_h],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <sys/types.h>
+            #include <stdint.h>]],
+          [[uintmax_t i = (uintmax_t) -1; return !i;]])],
+       [gl_cv_header_stdint_h=yes],
+       [gl_cv_header_stdint_h=no])])
+  if test $gl_cv_header_stdint_h = yes; then
+    AC_DEFINE_UNQUOTED([HAVE_STDINT_H_WITH_UINTMAX], [1],
+      [Define if <stdint.h> exists, doesn't clash with <sys/types.h>,
+       and declares uintmax_t. ])
+  fi
+])
diff --git a/m4/threadlib.m4 b/m4/threadlib.m4
new file mode 100644
index 0000000..b43534e
--- /dev/null
+++ b/m4/threadlib.m4
@@ -0,0 +1,389 @@
+# threadlib.m4 serial 11 (gettext-0.18.2)
+dnl Copyright (C) 2005-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+dnl gl_THREADLIB
+dnl ------------
+dnl Tests for a multithreading library to be used.
+dnl If the configure.ac contains a definition of the gl_THREADLIB_DEFAULT_NO
+dnl (it must be placed before the invocation of gl_THREADLIB_EARLY!), then the
+dnl default is 'no', otherwise it is system dependent. In both cases, the user
+dnl can change the choice through the options --enable-threads=choice or
+dnl --disable-threads.
+dnl Defines at most one of the macros USE_POSIX_THREADS, USE_SOLARIS_THREADS,
+dnl USE_PTH_THREADS, USE_WINDOWS_THREADS
+dnl Sets the variables LIBTHREAD and LTLIBTHREAD to the linker options for use
+dnl in a Makefile (LIBTHREAD for use without libtool, LTLIBTHREAD for use with
+dnl libtool).
+dnl Sets the variables LIBMULTITHREAD and LTLIBMULTITHREAD similarly, for
+dnl programs that really need multithread functionality. The difference
+dnl between LIBTHREAD and LIBMULTITHREAD is that on platforms supporting weak
+dnl symbols, typically LIBTHREAD is empty whereas LIBMULTITHREAD is not.
+dnl Adds to CPPFLAGS the flag -D_REENTRANT or -D_THREAD_SAFE if needed for
+dnl multithread-safe programs.
+
+AC_DEFUN([gl_THREADLIB_EARLY],
+[
+  AC_REQUIRE([gl_THREADLIB_EARLY_BODY])
+])
+
+dnl The guts of gl_THREADLIB_EARLY. Needs to be expanded only once.
+
+AC_DEFUN([gl_THREADLIB_EARLY_BODY],
+[
+  dnl Ordering constraints: This macro modifies CPPFLAGS in a way that
+  dnl influences the result of the autoconf tests that test for *_unlocked
+  dnl declarations, on AIX 5 at least. Therefore it must come early.
+  AC_BEFORE([$0], [gl_FUNC_GLIBC_UNLOCKED_IO])dnl
+  AC_BEFORE([$0], [gl_ARGP])dnl
+
+  AC_REQUIRE([AC_CANONICAL_HOST])
+  dnl _GNU_SOURCE is needed for pthread_rwlock_t on glibc systems.
+  dnl AC_USE_SYSTEM_EXTENSIONS was introduced in autoconf 2.60 and obsoletes
+  dnl AC_GNU_SOURCE.
+  m4_ifdef([AC_USE_SYSTEM_EXTENSIONS],
+    [AC_REQUIRE([AC_USE_SYSTEM_EXTENSIONS])],
+    [AC_REQUIRE([AC_GNU_SOURCE])])
+  dnl Check for multithreading.
+  m4_ifdef([gl_THREADLIB_DEFAULT_NO],
+    [m4_divert_text([DEFAULTS], [gl_use_threads_default=no])],
+    [m4_divert_text([DEFAULTS], [gl_use_threads_default=])])
+  AC_ARG_ENABLE([threads],
+AC_HELP_STRING([--enable-threads={posix|solaris|pth|windows}], [specify 
multithreading API])m4_ifdef([gl_THREADLIB_DEFAULT_NO], [], [
+AC_HELP_STRING([--disable-threads], [build without multithread safety])]),
+    [gl_use_threads=$enableval],
+    [if test -n "$gl_use_threads_default"; then
+       gl_use_threads="$gl_use_threads_default"
+     else
+changequote(,)dnl
+       case "$host_os" in
+         dnl Disable multithreading by default on OSF/1, because it interferes
+         dnl with fork()/exec(): When msgexec is linked with -lpthread, its
+         dnl child process gets an endless segmentation fault inside execvp().
+         dnl Disable multithreading by default on Cygwin 1.5.x, because it has
+         dnl bugs that lead to endless loops or crashes. See
+         dnl <http://cygwin.com/ml/cygwin/2009-08/msg00283.html>.
+         osf*) gl_use_threads=no ;;
+         cygwin*)
+               case `uname -r` in
+                 1.[0-5].*) gl_use_threads=no ;;
+                 *)         gl_use_threads=yes ;;
+               esac
+               ;;
+         *)    gl_use_threads=yes ;;
+       esac
+changequote([,])dnl
+     fi
+    ])
+  if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then
+    # For using <pthread.h>:
+    case "$host_os" in
+      osf*)
+        # On OSF/1, the compiler needs the flag -D_REENTRANT so that it
+        # groks <pthread.h>. cc also understands the flag -pthread, but
+        # we don't use it because 1. gcc-2.95 doesn't understand -pthread,
+        # 2. putting a flag into CPPFLAGS that has an effect on the linker
+        # causes the AC_LINK_IFELSE test below to succeed unexpectedly,
+        # leading to wrong values of LIBTHREAD and LTLIBTHREAD.
+        CPPFLAGS="$CPPFLAGS -D_REENTRANT"
+        ;;
+    esac
+    # Some systems optimize for single-threaded programs by default, and
+    # need special flags to disable these optimizations. For example, the
+    # definition of 'errno' in <errno.h>.
+    case "$host_os" in
+      aix* | freebsd*) CPPFLAGS="$CPPFLAGS -D_THREAD_SAFE" ;;
+      solaris*) CPPFLAGS="$CPPFLAGS -D_REENTRANT" ;;
+    esac
+  fi
+])
+
+dnl The guts of gl_THREADLIB. Needs to be expanded only once.
+
+AC_DEFUN([gl_THREADLIB_BODY],
+[
+  AC_REQUIRE([gl_THREADLIB_EARLY_BODY])
+  gl_threads_api=none
+  LIBTHREAD=
+  LTLIBTHREAD=
+  LIBMULTITHREAD=
+  LTLIBMULTITHREAD=
+  if test "$gl_use_threads" != no; then
+    dnl Check whether the compiler and linker support weak declarations.
+    AC_CACHE_CHECK([whether imported symbols can be declared weak],
+      [gl_cv_have_weak],
+      [gl_cv_have_weak=no
+       dnl First, test whether the compiler accepts it syntactically.
+       AC_LINK_IFELSE(
+         [AC_LANG_PROGRAM(
+            [[extern void xyzzy ();
+#pragma weak xyzzy]],
+            [[xyzzy();]])],
+         [gl_cv_have_weak=maybe])
+       if test $gl_cv_have_weak = maybe; then
+         dnl Second, test whether it actually works. On Cygwin 1.7.2, with
+         dnl gcc 4.3, symbols declared weak always evaluate to the address 0.
+         AC_RUN_IFELSE(
+           [AC_LANG_SOURCE([[
+#include <stdio.h>
+#pragma weak fputs
+int main ()
+{
+  return (fputs == NULL);
+}]])],
+           [gl_cv_have_weak=yes],
+           [gl_cv_have_weak=no],
+           [dnl When cross-compiling, assume that only ELF platforms support
+            dnl weak symbols.
+            AC_EGREP_CPP([Extensible Linking Format],
+              [#ifdef __ELF__
+               Extensible Linking Format
+               #endif
+              ],
+              [gl_cv_have_weak="guessing yes"],
+              [gl_cv_have_weak="guessing no"])
+           ])
+       fi
+      ])
+    if test "$gl_use_threads" = yes || test "$gl_use_threads" = posix; then
+      # On OSF/1, the compiler needs the flag -pthread or -D_REENTRANT so that
+      # it groks <pthread.h>. It's added above, in gl_THREADLIB_EARLY_BODY.
+      AC_CHECK_HEADER([pthread.h],
+        [gl_have_pthread_h=yes], [gl_have_pthread_h=no])
+      if test "$gl_have_pthread_h" = yes; then
+        # Other possible tests:
+        #   -lpthreads (FSU threads, PCthreads)
+        #   -lgthreads
+        gl_have_pthread=
+        # Test whether both pthread_mutex_lock and pthread_mutexattr_init exist
+        # in libc. IRIX 6.5 has the first one in both libc and libpthread, but
+        # the second one only in libpthread, and lock.c needs it.
+        #
+        # If -pthread works, prefer it to -lpthread, since Ubuntu 14.04
+        # needs -pthread for some reason.  See:
+        # http://lists.gnu.org/archive/html/bug-gnulib/2014-09/msg00023.html
+        save_LIBS=$LIBS
+        for gl_pthread in '' '-pthread'; do
+          LIBS="$LIBS $gl_pthread"
+          AC_LINK_IFELSE(
+            [AC_LANG_PROGRAM(
+               [[#include <pthread.h>
+                 pthread_mutex_t m;
+                 pthread_mutexattr_t ma;
+               ]],
+               [[pthread_mutex_lock (&m);
+                 pthread_mutexattr_init (&ma);]])],
+            [gl_have_pthread=yes
+             LIBTHREAD=$gl_pthread LTLIBTHREAD=$gl_pthread
+             LIBMULTITHREAD=$gl_pthread LTLIBMULTITHREAD=$gl_pthread])
+          LIBS=$save_LIBS
+          test -n "$gl_have_pthread" && break
+        done
+
+        # Test for libpthread by looking for pthread_kill. (Not pthread_self,
+        # since it is defined as a macro on OSF/1.)
+        if test -n "$gl_have_pthread" && test -z "$LIBTHREAD"; then
+          # The program links fine without libpthread. But it may actually
+          # need to link with libpthread in order to create multiple threads.
+          AC_CHECK_LIB([pthread], [pthread_kill],
+            [LIBMULTITHREAD=-lpthread LTLIBMULTITHREAD=-lpthread
+             # On Solaris and HP-UX, most pthread functions exist also in libc.
+             # Therefore pthread_in_use() needs to actually try to create a
+             # thread: pthread_create from libc will fail, whereas
+             # pthread_create will actually create a thread.
+             case "$host_os" in
+               solaris* | hpux*)
+                 AC_DEFINE([PTHREAD_IN_USE_DETECTION_HARD], [1],
+                   [Define if the pthread_in_use() detection is hard.])
+             esac
+            ])
+        elif test -z "$gl_have_pthread"; then
+          # Some library is needed. Try libpthread and libc_r.
+          AC_CHECK_LIB([pthread], [pthread_kill],
+            [gl_have_pthread=yes
+             LIBTHREAD=-lpthread LTLIBTHREAD=-lpthread
+             LIBMULTITHREAD=-lpthread LTLIBMULTITHREAD=-lpthread])
+          if test -z "$gl_have_pthread"; then
+            # For FreeBSD 4.
+            AC_CHECK_LIB([c_r], [pthread_kill],
+              [gl_have_pthread=yes
+               LIBTHREAD=-lc_r LTLIBTHREAD=-lc_r
+               LIBMULTITHREAD=-lc_r LTLIBMULTITHREAD=-lc_r])
+          fi
+        fi
+        if test -n "$gl_have_pthread"; then
+          gl_threads_api=posix
+          AC_DEFINE([USE_POSIX_THREADS], [1],
+            [Define if the POSIX multithreading library can be used.])
+          if test -n "$LIBMULTITHREAD" || test -n "$LTLIBMULTITHREAD"; then
+            if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then
+              AC_DEFINE([USE_POSIX_THREADS_WEAK], [1],
+                [Define if references to the POSIX multithreading library 
should be made weak.])
+              LIBTHREAD=
+              LTLIBTHREAD=
+            fi
+          fi
+        fi
+      fi
+    fi
+    if test -z "$gl_have_pthread"; then
+      if test "$gl_use_threads" = yes || test "$gl_use_threads" = solaris; then
+        gl_have_solaristhread=
+        gl_save_LIBS="$LIBS"
+        LIBS="$LIBS -lthread"
+        AC_LINK_IFELSE(
+          [AC_LANG_PROGRAM(
+             [[
+#include <thread.h>
+#include <synch.h>
+             ]],
+             [[thr_self();]])],
+          [gl_have_solaristhread=yes])
+        LIBS="$gl_save_LIBS"
+        if test -n "$gl_have_solaristhread"; then
+          gl_threads_api=solaris
+          LIBTHREAD=-lthread
+          LTLIBTHREAD=-lthread
+          LIBMULTITHREAD="$LIBTHREAD"
+          LTLIBMULTITHREAD="$LTLIBTHREAD"
+          AC_DEFINE([USE_SOLARIS_THREADS], [1],
+            [Define if the old Solaris multithreading library can be used.])
+          if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then
+            AC_DEFINE([USE_SOLARIS_THREADS_WEAK], [1],
+              [Define if references to the old Solaris multithreading library 
should be made weak.])
+            LIBTHREAD=
+            LTLIBTHREAD=
+          fi
+        fi
+      fi
+    fi
+    if test "$gl_use_threads" = pth; then
+      gl_save_CPPFLAGS="$CPPFLAGS"
+      AC_LIB_LINKFLAGS([pth])
+      gl_have_pth=
+      gl_save_LIBS="$LIBS"
+      LIBS="$LIBS $LIBPTH"
+      AC_LINK_IFELSE(
+        [AC_LANG_PROGRAM([[#include <pth.h>]], [[pth_self();]])],
+        [gl_have_pth=yes])
+      LIBS="$gl_save_LIBS"
+      if test -n "$gl_have_pth"; then
+        gl_threads_api=pth
+        LIBTHREAD="$LIBPTH"
+        LTLIBTHREAD="$LTLIBPTH"
+        LIBMULTITHREAD="$LIBTHREAD"
+        LTLIBMULTITHREAD="$LTLIBTHREAD"
+        AC_DEFINE([USE_PTH_THREADS], [1],
+          [Define if the GNU Pth multithreading library can be used.])
+        if test -n "$LIBMULTITHREAD" || test -n "$LTLIBMULTITHREAD"; then
+          if case "$gl_cv_have_weak" in *yes) true;; *) false;; esac; then
+            AC_DEFINE([USE_PTH_THREADS_WEAK], [1],
+              [Define if references to the GNU Pth multithreading library 
should be made weak.])
+            LIBTHREAD=
+            LTLIBTHREAD=
+          fi
+        fi
+      else
+        CPPFLAGS="$gl_save_CPPFLAGS"
+      fi
+    fi
+    if test -z "$gl_have_pthread"; then
+      case "$gl_use_threads" in
+        yes | windows | win32) # The 'win32' is for backward compatibility.
+          if { case "$host_os" in
+                 mingw*) true;;
+                 *) false;;
+               esac
+             }; then
+            gl_threads_api=windows
+            AC_DEFINE([USE_WINDOWS_THREADS], [1],
+              [Define if the native Windows multithreading API can be used.])
+          fi
+          ;;
+      esac
+    fi
+  fi
+  AC_MSG_CHECKING([for multithread API to use])
+  AC_MSG_RESULT([$gl_threads_api])
+  AC_SUBST([LIBTHREAD])
+  AC_SUBST([LTLIBTHREAD])
+  AC_SUBST([LIBMULTITHREAD])
+  AC_SUBST([LTLIBMULTITHREAD])
+])
+
+AC_DEFUN([gl_THREADLIB],
+[
+  AC_REQUIRE([gl_THREADLIB_EARLY])
+  AC_REQUIRE([gl_THREADLIB_BODY])
+])
+
+
+dnl gl_DISABLE_THREADS
+dnl ------------------
+dnl Sets the gl_THREADLIB default so that threads are not used by default.
+dnl The user can still override it at installation time, by using the
+dnl configure option '--enable-threads'.
+
+AC_DEFUN([gl_DISABLE_THREADS], [
+  m4_divert_text([INIT_PREPARE], [gl_use_threads_default=no])
+])
+
+
+dnl Survey of platforms:
+dnl
+dnl Platform           Available  Compiler    Supports   test-lock
+dnl                    flavours   option      weak       result
+dnl ---------------    ---------  ---------   --------   ---------
+dnl Linux 2.4/glibc    posix      -lpthread       Y      OK
+dnl
+dnl GNU Hurd/glibc     posix
+dnl
+dnl Ubuntu 14.04       posix      -pthread        Y      OK
+dnl
+dnl FreeBSD 5.3        posix      -lc_r           Y
+dnl                    posix      -lkse ?         Y
+dnl                    posix      -lpthread ?     Y
+dnl                    posix      -lthr           Y
+dnl
+dnl FreeBSD 5.2        posix      -lc_r           Y
+dnl                    posix      -lkse           Y
+dnl                    posix      -lthr           Y
+dnl
+dnl FreeBSD 4.0,4.10   posix      -lc_r           Y      OK
+dnl
+dnl NetBSD 1.6         --
+dnl
+dnl OpenBSD 3.4        posix      -lpthread       Y      OK
+dnl
+dnl Mac OS X 10.[123]  posix      -lpthread       Y      OK
+dnl
+dnl Solaris 7,8,9      posix      -lpthread       Y      Sol 7,8: 0.0; Sol 9: 
OK
+dnl                    solaris    -lthread        Y      Sol 7,8: 0.0; Sol 9: 
OK
+dnl
+dnl HP-UX 11           posix      -lpthread       N (cc) OK
+dnl                                               Y (gcc)
+dnl
+dnl IRIX 6.5           posix      -lpthread       Y      0.5
+dnl
+dnl AIX 4.3,5.1        posix      -lpthread       N      AIX 4: 0.5; AIX 5: OK
+dnl
+dnl OSF/1 4.0,5.1      posix      -pthread (cc)   N      OK
+dnl                               -lpthread (gcc) Y
+dnl
+dnl Cygwin             posix      -lpthread       Y      OK
+dnl
+dnl Any of the above   pth        -lpth                  0.0
+dnl
+dnl Mingw              windows                    N      OK
+dnl
+dnl BeOS 5             --
+dnl
+dnl The test-lock result shows what happens if in test-lock.c EXPLICIT_YIELD is
+dnl turned off:
+dnl   OK if all three tests terminate OK,
+dnl   0.5 if the first test terminates OK but the second one loops endlessly,
+dnl   0.0 if the first test already loops endlessly.
diff --git a/m4/uintmax_t.m4 b/m4/uintmax_t.m4
new file mode 100644
index 0000000..30f4dd5
--- /dev/null
+++ b/m4/uintmax_t.m4
@@ -0,0 +1,30 @@
+# uintmax_t.m4 serial 12
+dnl Copyright (C) 1997-2004, 2007-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Paul Eggert.
+
+AC_PREREQ([2.13])
+
+# Define uintmax_t to 'unsigned long' or 'unsigned long long'
+# if it is not already defined in <stdint.h> or <inttypes.h>.
+
+AC_DEFUN([gl_AC_TYPE_UINTMAX_T],
+[
+  AC_REQUIRE([gl_AC_HEADER_INTTYPES_H])
+  AC_REQUIRE([gl_AC_HEADER_STDINT_H])
+  if test $gl_cv_header_inttypes_h = no && test $gl_cv_header_stdint_h = no; 
then
+    AC_REQUIRE([AC_TYPE_UNSIGNED_LONG_LONG_INT])
+    test $ac_cv_type_unsigned_long_long_int = yes \
+      && ac_type='unsigned long long' \
+      || ac_type='unsigned long'
+    AC_DEFINE_UNQUOTED([uintmax_t], [$ac_type],
+      [Define to unsigned long or unsigned long long
+       if <stdint.h> and <inttypes.h> don't define.])
+  else
+    AC_DEFINE([HAVE_UINTMAX_T], [1],
+      [Define if you have the 'uintmax_t' type in <stdint.h> or <inttypes.h>.])
+  fi
+])
diff --git a/m4/visibility.m4 b/m4/visibility.m4
new file mode 100644
index 0000000..e99e3fb
--- /dev/null
+++ b/m4/visibility.m4
@@ -0,0 +1,77 @@
+# visibility.m4 serial 5 (gettext-0.18.2)
+dnl Copyright (C) 2005, 2008, 2010-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+
+dnl Tests whether the compiler supports the command-line option
+dnl -fvisibility=hidden and the function and variable attributes
+dnl __attribute__((__visibility__("hidden"))) and
+dnl __attribute__((__visibility__("default"))).
+dnl Does *not* test for __visibility__("protected") - which has tricky
+dnl semantics (see the 'vismain' test in glibc) and does not exist e.g. on
+dnl Mac OS X.
+dnl Does *not* test for __visibility__("internal") - which has processor
+dnl dependent semantics.
+dnl Does *not* test for #pragma GCC visibility push(hidden) - which is
+dnl "really only recommended for legacy code".
+dnl Set the variable CFLAG_VISIBILITY.
+dnl Defines and sets the variable HAVE_VISIBILITY.
+
+AC_DEFUN([gl_VISIBILITY],
+[
+  AC_REQUIRE([AC_PROG_CC])
+  CFLAG_VISIBILITY=
+  HAVE_VISIBILITY=0
+  if test -n "$GCC"; then
+    dnl First, check whether -Werror can be added to the command line, or
+    dnl whether it leads to an error because of some other option that the
+    dnl user has put into $CC $CFLAGS $CPPFLAGS.
+    AC_MSG_CHECKING([whether the -Werror option is usable])
+    AC_CACHE_VAL([gl_cv_cc_vis_werror], [
+      gl_save_CFLAGS="$CFLAGS"
+      CFLAGS="$CFLAGS -Werror"
+      AC_COMPILE_IFELSE(
+        [AC_LANG_PROGRAM([[]], [[]])],
+        [gl_cv_cc_vis_werror=yes],
+        [gl_cv_cc_vis_werror=no])
+      CFLAGS="$gl_save_CFLAGS"])
+    AC_MSG_RESULT([$gl_cv_cc_vis_werror])
+    dnl Now check whether visibility declarations are supported.
+    AC_MSG_CHECKING([for simple visibility declarations])
+    AC_CACHE_VAL([gl_cv_cc_visibility], [
+      gl_save_CFLAGS="$CFLAGS"
+      CFLAGS="$CFLAGS -fvisibility=hidden"
+      dnl We use the option -Werror and a function dummyfunc, because on some
+      dnl platforms (Cygwin 1.7) the use of -fvisibility triggers a warning
+      dnl "visibility attribute not supported in this configuration; ignored"
+      dnl at the first function definition in every compilation unit, and we
+      dnl don't want to use the option in this case.
+      if test $gl_cv_cc_vis_werror = yes; then
+        CFLAGS="$CFLAGS -Werror"
+      fi
+      AC_COMPILE_IFELSE(
+        [AC_LANG_PROGRAM(
+           [[extern __attribute__((__visibility__("hidden"))) int hiddenvar;
+             extern __attribute__((__visibility__("default"))) int exportedvar;
+             extern __attribute__((__visibility__("hidden"))) int hiddenfunc 
(void);
+             extern __attribute__((__visibility__("default"))) int 
exportedfunc (void);
+             void dummyfunc (void) {}
+           ]],
+           [[]])],
+        [gl_cv_cc_visibility=yes],
+        [gl_cv_cc_visibility=no])
+      CFLAGS="$gl_save_CFLAGS"])
+    AC_MSG_RESULT([$gl_cv_cc_visibility])
+    if test $gl_cv_cc_visibility = yes; then
+      CFLAG_VISIBILITY="-fvisibility=hidden"
+      HAVE_VISIBILITY=1
+    fi
+  fi
+  AC_SUBST([CFLAG_VISIBILITY])
+  AC_SUBST([HAVE_VISIBILITY])
+  AC_DEFINE_UNQUOTED([HAVE_VISIBILITY], [$HAVE_VISIBILITY],
+    [Define to 1 or 0, depending whether the compiler supports simple 
visibility declarations.])
+])
diff --git a/m4/wchar_t.m4 b/m4/wchar_t.m4
new file mode 100644
index 0000000..2db8c3f
--- /dev/null
+++ b/m4/wchar_t.m4
@@ -0,0 +1,24 @@
+# wchar_t.m4 serial 4 (gettext-0.18.2)
+dnl Copyright (C) 2002-2003, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+dnl Test whether <stddef.h> has the 'wchar_t' type.
+dnl Prerequisite: AC_PROG_CC
+
+AC_DEFUN([gt_TYPE_WCHAR_T],
+[
+  AC_CACHE_CHECK([for wchar_t], [gt_cv_c_wchar_t],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[#include <stddef.h>
+            wchar_t foo = (wchar_t)'\0';]],
+          [[]])],
+       [gt_cv_c_wchar_t=yes],
+       [gt_cv_c_wchar_t=no])])
+  if test $gt_cv_c_wchar_t = yes; then
+    AC_DEFINE([HAVE_WCHAR_T], [1], [Define if you have the 'wchar_t' type.])
+  fi
+])
diff --git a/m4/wint_t.m4 b/m4/wint_t.m4
new file mode 100644
index 0000000..8ff2a5b
--- /dev/null
+++ b/m4/wint_t.m4
@@ -0,0 +1,32 @@
+# wint_t.m4 serial 5 (gettext-0.18.2)
+dnl Copyright (C) 2003, 2007-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+dnl From Bruno Haible.
+dnl Test whether <wchar.h> has the 'wint_t' type.
+dnl Prerequisite: AC_PROG_CC
+
+AC_DEFUN([gt_TYPE_WINT_T],
+[
+  AC_CACHE_CHECK([for wint_t], [gt_cv_c_wint_t],
+    [AC_COMPILE_IFELSE(
+       [AC_LANG_PROGRAM(
+          [[
+/* Tru64 with Desktop Toolkit C has a bug: <stdio.h> must be included before
+   <wchar.h>.
+   BSD/OS 4.0.1 has a bug: <stddef.h>, <stdio.h> and <time.h> must be included
+   before <wchar.h>.  */
+#include <stddef.h>
+#include <stdio.h>
+#include <time.h>
+#include <wchar.h>
+            wint_t foo = (wchar_t)'\0';]],
+          [[]])],
+       [gt_cv_c_wint_t=yes],
+       [gt_cv_c_wint_t=no])])
+  if test $gt_cv_c_wint_t = yes; then
+    AC_DEFINE([HAVE_WINT_T], [1], [Define if you have the 'wint_t' type.])
+  fi
+])
diff --git a/m4/xsize.m4 b/m4/xsize.m4
new file mode 100644
index 0000000..16764e8
--- /dev/null
+++ b/m4/xsize.m4
@@ -0,0 +1,12 @@
+# xsize.m4 serial 5
+dnl Copyright (C) 2003-2004, 2008-2016 Free Software Foundation, Inc.
+dnl This file is free software; the Free Software Foundation
+dnl gives unlimited permission to copy and/or distribute it,
+dnl with or without modifications, as long as this notice is preserved.
+
+AC_DEFUN([gl_XSIZE],
+[
+  dnl Prerequisites of lib/xsize.h.
+  AC_REQUIRE([gl_SIZE_MAX])
+  AC_CHECK_HEADERS([stdint.h])
+])
diff --git a/po/ChangeLog b/po/ChangeLog
new file mode 100644
index 0000000..9f94652
--- /dev/null
+++ b/po/ChangeLog
@@ -0,0 +1,12 @@
+2021-04-05  gettextize  <bug-gnu-gettext@gnu.org>
+
+       * Makefile.in.in: New file, from gettext-0.19.8.1.
+       * Rules-quot: New file, from gettext-0.19.8.1.
+       * boldquot.sed: New file, from gettext-0.19.8.1.
+       * en@boldquot.header: New file, from gettext-0.19.8.1.
+       * en@quot.header: New file, from gettext-0.19.8.1.
+       * insert-header.sin: New file, from gettext-0.19.8.1.
+       * quot.sed: New file, from gettext-0.19.8.1.
+       * remove-potcdate.sin: New file, from gettext-0.19.8.1.
+       * POTFILES.in: New file.
+
diff --git a/po/Makefile.in.in b/po/Makefile.in.in
new file mode 100644
index 0000000..38c293d
--- /dev/null
+++ b/po/Makefile.in.in
@@ -0,0 +1,483 @@
+# Makefile for PO directory in any package using GNU gettext.
+# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper 
<drepper@gnu.ai.mit.edu>
+#
+# Copying and distribution of this file, with or without modification,
+# are permitted in any medium without royalty provided the copyright
+# notice and this notice are preserved.  This file is offered as-is,
+# without any warranty.
+#
+# Origin: gettext-0.19.8
+GETTEXT_MACRO_VERSION = 0.19
+
+PACKAGE = @PACKAGE@
+VERSION = @VERSION@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+
+SED = @SED@
+SHELL = /bin/sh
+@SET_MAKE@
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+datadir = @datadir@
+localedir = @localedir@
+gettextsrcdir = $(datadir)/gettext/po
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+
+# We use $(mkdir_p).
+# In automake <= 1.9.x, $(mkdir_p) is defined either as "mkdir -p --" or as
+# "$(mkinstalldirs)" or as "$(install_sh) -d". For these automake versions,
+# @install_sh@ does not start with $(SHELL), so we add it.
+# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined
+# either as "/path/to/mkdir -p" or ".../install-sh -c -d". For these automake
+# versions, $(mkinstalldirs) and $(install_sh) are unused.
+mkinstalldirs = $(SHELL) @install_sh@ -d
+install_sh = $(SHELL) @install_sh@
+MKDIR_P = @MKDIR_P@
+mkdir_p = @mkdir_p@
+
+# When building gettext-tools, we prefer to use the built programs
+# rather than installed programs.  However, we can't do that when we
+# are cross compiling.
+CROSS_COMPILING = @CROSS_COMPILING@
+
+GMSGFMT_ = @GMSGFMT@
+GMSGFMT_no = @GMSGFMT@
+GMSGFMT_yes = @GMSGFMT_015@
+GMSGFMT = $(GMSGFMT_$(USE_MSGCTXT))
+MSGFMT_ = @MSGFMT@
+MSGFMT_no = @MSGFMT@
+MSGFMT_yes = @MSGFMT_015@
+MSGFMT = $(MSGFMT_$(USE_MSGCTXT))
+XGETTEXT_ = @XGETTEXT@
+XGETTEXT_no = @XGETTEXT@
+XGETTEXT_yes = @XGETTEXT_015@
+XGETTEXT = $(XGETTEXT_$(USE_MSGCTXT))
+MSGMERGE = msgmerge
+MSGMERGE_UPDATE = @MSGMERGE@ --update
+MSGINIT = msginit
+MSGCONV = msgconv
+MSGFILTER = msgfilter
+
+POFILES = @POFILES@
+GMOFILES = @GMOFILES@
+UPDATEPOFILES = @UPDATEPOFILES@
+DUMMYPOFILES = @DUMMYPOFILES@
+DISTFILES.common = Makefile.in.in remove-potcdate.sin \
+$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) 
$(DISTFILES.common.extra3)
+DISTFILES = $(DISTFILES.common) Makevars POTFILES.in \
+$(POFILES) $(GMOFILES) \
+$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)
+
+POTFILES = \
+
+CATALOGS = @CATALOGS@
+
+POFILESDEPS_ = $(srcdir)/$(DOMAIN).pot
+POFILESDEPS_yes = $(POFILESDEPS_)
+POFILESDEPS_no =
+POFILESDEPS = $(POFILESDEPS_$(PO_DEPENDS_ON_POT))
+
+DISTFILESDEPS_ = update-po
+DISTFILESDEPS_yes = $(DISTFILESDEPS_)
+DISTFILESDEPS_no =
+DISTFILESDEPS = $(DISTFILESDEPS_$(DIST_DEPENDS_ON_UPDATE_PO))
+
+# Makevars gets inserted here. (Don't remove this line!)
+
+.SUFFIXES:
+.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update
+
+.po.mo:
+       @echo "$(MSGFMT) -c -o $@ $<"; \
+       $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@
+
+.po.gmo:
+       @lang=`echo $* | sed -e 's,.*/,,'`; \
+       test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+       echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics 
--verbose -o $${lang}.gmo $${lang}.po"; \
+       cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics 
--verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo
+
+.sin.sed:
+       sed -e '/^#/d' $< > t-$@
+       mv t-$@ $@
+
+
+all: all-@USE_NLS@
+
+all-yes: stamp-po
+all-no:
+
+# Ensure that the gettext macros and this Makefile.in.in are in sync.
+CHECK_MACRO_VERSION = \
+       test "$(GETTEXT_MACRO_VERSION)" = "@GETTEXT_MACRO_VERSION@" \
+         || { echo "*** error: gettext infrastructure mismatch: using a 
Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf 
macros are from gettext version @GETTEXT_MACRO_VERSION@" 1>&2; \
+              exit 1; \
+            }
+
+# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no
+# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because
+# we don't want to bother translators with empty POT files). We assume that
+# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty.
+# In this case, stamp-po is a nop (i.e. a phony target).
+
+# stamp-po is a timestamp denoting the last time at which the CATALOGS have
+# been loosely updated. Its purpose is that when a developer or translator
+# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,
+# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent
+# invocations of "make" will do nothing. This timestamp would not be necessary
+# if updating the $(CATALOGS) would always touch them; however, the rule for
+# $(POFILES) has been designed to not touch files that don't need to be
+# changed.
+stamp-po: $(srcdir)/$(DOMAIN).pot
+       @$(CHECK_MACRO_VERSION)
+       test ! -f $(srcdir)/$(DOMAIN).pot || \
+         test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES)
+       @test ! -f $(srcdir)/$(DOMAIN).pot || { \
+         echo "touch stamp-po" && \
+         echo timestamp > stamp-poT && \
+         mv stamp-poT stamp-po; \
+       }
+
+# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',
+# otherwise packages like GCC can not be built if only parts of the source
+# have been downloaded.
+
+# This target rebuilds $(DOMAIN).pot; it is an expensive operation.
+# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.
+# The determination of whether the package xyz is a GNU one is based on the
+# heuristic whether some file in the top level directory mentions "GNU xyz".
+# If GNU 'find' is available, we avoid grepping through monster files.
+$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed
+       package_gnu="$(PACKAGE_GNU)"; \
+       test -n "$$package_gnu" || { \
+         if { if (LC_ALL=C find --version) 2>/dev/null | grep GNU >/dev/null; 
then \
+                LC_ALL=C find -L $(top_srcdir) -maxdepth 1 -type f \
+                              -size -10000000c -exec grep 'GNU @PACKAGE@' \
+                              /dev/null '{}' ';' 2>/dev/null; \
+              else \
+                LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null; \
+              fi; \
+            } | grep -v 'libtool:' >/dev/null; then \
+            package_gnu=yes; \
+          else \
+            package_gnu=no; \
+          fi; \
+       }; \
+       if test "$$package_gnu" = "yes"; then \
+         package_prefix='GNU '; \
+       else \
+         package_prefix=''; \
+       fi; \
+       if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = 
'@'PACKAGE_BUGREPORT'@'; then \
+         msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \
+       else \
+         msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \
+       fi; \
+       case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+         '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 
0.16.[0-1]*) \
+           $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
+             --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) 
@XGETTEXT_EXTRA_OPTIONS@ \
+             --files-from=$(srcdir)/POTFILES.in \
+             --copyright-holder='$(COPYRIGHT_HOLDER)' \
+             --msgid-bugs-address="$$msgid_bugs_address" \
+           ;; \
+         *) \
+           $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \
+             --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) 
@XGETTEXT_EXTRA_OPTIONS@ \
+             --files-from=$(srcdir)/POTFILES.in \
+             --copyright-holder='$(COPYRIGHT_HOLDER)' \
+             --package-name="$${package_prefix}@PACKAGE@" \
+             --package-version='@VERSION@' \
+             --msgid-bugs-address="$$msgid_bugs_address" \
+           ;; \
+       esac
+       test ! -f $(DOMAIN).po || { \
+         if test -f $(srcdir)/$(DOMAIN).pot-header; then \
+           sed -e '1,/^#$$/d' < $(DOMAIN).po > $(DOMAIN).1po && \
+           cat $(srcdir)/$(DOMAIN).pot-header $(DOMAIN).1po > $(DOMAIN).po; \
+           rm -f $(DOMAIN).1po; \
+         fi; \
+         if test -f $(srcdir)/$(DOMAIN).pot; then \
+           sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > 
$(DOMAIN).1po && \
+           sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \
+           if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \
+             rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \
+           else \
+             rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \
+             mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+           fi; \
+         else \
+           mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \
+         fi; \
+       }
+
+# This rule has no dependencies: we don't need to update $(DOMAIN).pot at
+# every "make" invocation, only create it when it is missing.
+# Only "make $(DOMAIN).pot-update" or "make dist" will force an update.
+$(srcdir)/$(DOMAIN).pot:
+       $(MAKE) $(DOMAIN).pot-update
+
+# This target rebuilds a PO file if $(DOMAIN).pot has changed.
+# Note that a PO file is not touched if it doesn't need to be changed.
+$(POFILES): $(POFILESDEPS)
+       @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \
+       if test -f "$(srcdir)/$${lang}.po"; then \
+         test -f $(srcdir)/$(DOMAIN).pot || $(MAKE) $(srcdir)/$(DOMAIN).pot; \
+         test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+         echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} 
$${lang}.po $(DOMAIN).pot"; \
+         cd $(srcdir) \
+           && { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` 
in \
+                  '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
+                    $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po 
$(DOMAIN).pot;; \
+                  *) \
+                    $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} 
$${lang}.po $(DOMAIN).pot;; \
+                esac; \
+              }; \
+       else \
+         $(MAKE) $${lang}.po-create; \
+       fi
+
+
+install: install-exec install-data
+install-exec:
+install-data: install-data-@USE_NLS@
+       if test "$(PACKAGE)" = "gettext-tools"; then \
+         $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
+         for file in $(DISTFILES.common) Makevars.template; do \
+           $(INSTALL_DATA) $(srcdir)/$$file \
+                           $(DESTDIR)$(gettextsrcdir)/$$file; \
+         done; \
+         for file in Makevars; do \
+           rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+         done; \
+       else \
+         : ; \
+       fi
+install-data-no: all
+install-data-yes: all
+       @catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+         dir=$(localedir)/$$lang/LC_MESSAGES; \
+         $(mkdir_p) $(DESTDIR)$$dir; \
+         if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; 
fi; \
+         $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \
+         echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \
+         for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+           if test -n "$$lc"; then \
+             if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 
2>/dev/null) | grep ' -> ' >/dev/null; then \
+               link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d 
$$lc | sed -e 's/^.* -> //'`; \
+               mv $(DESTDIR)$(localedir)/$$lang/$$lc 
$(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+               mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+               (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+                for file in *; do \
+                  if test -f $$file; then \
+                    ln -s ../$$link/$$file 
$(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+                  fi; \
+                done); \
+               rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+             else \
+               if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+                 :; \
+               else \
+                 rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+                 mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+               fi; \
+             fi; \
+             rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+             ln -s ../LC_MESSAGES/$(DOMAIN).mo 
$(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+             ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo 
$(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \
+             cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo 
$(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+             echo "installing $$realcat link as 
$(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \
+           fi; \
+         done; \
+       done
+
+install-strip: install
+
+installdirs: installdirs-exec installdirs-data
+installdirs-exec:
+installdirs-data: installdirs-data-@USE_NLS@
+       if test "$(PACKAGE)" = "gettext-tools"; then \
+         $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \
+       else \
+         : ; \
+       fi
+installdirs-data-no:
+installdirs-data-yes:
+       @catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+         dir=$(localedir)/$$lang/LC_MESSAGES; \
+         $(mkdir_p) $(DESTDIR)$$dir; \
+         for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \
+           if test -n "$$lc"; then \
+             if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 
2>/dev/null) | grep ' -> ' >/dev/null; then \
+               link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d 
$$lc | sed -e 's/^.* -> //'`; \
+               mv $(DESTDIR)$(localedir)/$$lang/$$lc 
$(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+               mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+               (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \
+                for file in *; do \
+                  if test -f $$file; then \
+                    ln -s ../$$link/$$file 
$(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \
+                  fi; \
+                done); \
+               rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \
+             else \
+               if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \
+                 :; \
+               else \
+                 rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \
+                 mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \
+               fi; \
+             fi; \
+           fi; \
+         done; \
+       done
+
+# Define this as empty until I found a useful application.
+installcheck:
+
+uninstall: uninstall-exec uninstall-data
+uninstall-exec:
+uninstall-data: uninstall-data-@USE_NLS@
+       if test "$(PACKAGE)" = "gettext-tools"; then \
+         for file in $(DISTFILES.common) Makevars.template; do \
+           rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \
+         done; \
+       else \
+         : ; \
+       fi
+uninstall-data-no:
+uninstall-data-yes:
+       catalogs='$(CATALOGS)'; \
+       for cat in $$catalogs; do \
+         cat=`basename $$cat`; \
+         lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \
+         for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \
+           rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \
+         done; \
+       done
+
+check: all
+
+info dvi ps pdf html tags TAGS ctags CTAGS ID:
+
+mostlyclean:
+       rm -f remove-potcdate.sed
+       rm -f stamp-poT
+       rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po
+       rm -fr *.o
+
+clean: mostlyclean
+
+distclean: clean
+       rm -f Makefile Makefile.in POTFILES *.mo
+
+maintainer-clean: distclean
+       @echo "This command is intended for maintainers to use;"
+       @echo "it deletes files that may require special tools to rebuild."
+       rm -f stamp-po $(GMOFILES)
+
+distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)
+dist distdir:
+       test -z "$(DISTFILESDEPS)" || $(MAKE) $(DISTFILESDEPS)
+       @$(MAKE) dist2
+# This is a separate target because 'update-po' must be executed before.
+dist2: stamp-po $(DISTFILES)
+       dists="$(DISTFILES)"; \
+       if test "$(PACKAGE)" = "gettext-tools"; then \
+         dists="$$dists Makevars.template"; \
+       fi; \
+       if test -f $(srcdir)/$(DOMAIN).pot; then \
+         dists="$$dists $(DOMAIN).pot stamp-po"; \
+       fi; \
+       if test -f $(srcdir)/ChangeLog; then \
+         dists="$$dists ChangeLog"; \
+       fi; \
+       for i in 0 1 2 3 4 5 6 7 8 9; do \
+         if test -f $(srcdir)/ChangeLog.$$i; then \
+           dists="$$dists ChangeLog.$$i"; \
+         fi; \
+       done; \
+       if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \
+       for file in $$dists; do \
+         if test -f $$file; then \
+           cp -p $$file $(distdir) || exit 1; \
+         else \
+           cp -p $(srcdir)/$$file $(distdir) || exit 1; \
+         fi; \
+       done
+
+update-po: Makefile
+       $(MAKE) $(DOMAIN).pot-update
+       test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES)
+       $(MAKE) update-gmo
+
+# General rule for creating PO files.
+
+.nop.po-create:
+       @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \
+       echo "File $$lang.po does not exist. If you are a translator, you can 
create it through 'msginit'." 1>&2; \
+       exit 1
+
+# General rule for updating PO files.
+
+.nop.po-update:
+       @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \
+       if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != 
"yes"; then PATH=`pwd`/../src:$$PATH; fi; \
+       tmpdir=`pwd`; \
+       echo "$$lang:"; \
+       test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \
+       echo "$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po 
$(DOMAIN).pot -o $$lang.new.po"; \
+       cd $(srcdir); \
+       if { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+              '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \
+                $(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po 
$$lang.po $(DOMAIN).pot;; \
+              *) \
+                $(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o 
$$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \
+            esac; \
+          }; then \
+         if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+           rm -f $$tmpdir/$$lang.new.po; \
+         else \
+           if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+             :; \
+           else \
+             echo "msgmerge for $$lang.po failed: cannot move 
$$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+             exit 1; \
+           fi; \
+         fi; \
+       else \
+         echo "msgmerge for $$lang.po failed!" 1>&2; \
+         rm -f $$tmpdir/$$lang.new.po; \
+       fi
+
+$(DUMMYPOFILES):
+
+update-gmo: Makefile $(GMOFILES)
+       @:
+
+# Recreate Makefile by invoking config.status. Explicitly invoke the shell,
+# because execution permission bits may not work on the current file system.
+# Use @SHELL@, which is the shell determined by autoconf for the use by its
+# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient.
+Makefile: Makefile.in.in Makevars $(top_builddir)/config.status 
@POMAKEFILEDEPS@
+       cd $(top_builddir) \
+         && @SHELL@ ./config.status $(subdir)/$@.in po-directories
+
+force:
+
+# Tell versions [3.59,3.63) of GNU make not to export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/po/Makevars b/po/Makevars
new file mode 100644
index 0000000..6c805d1
--- /dev/null
+++ b/po/Makevars
@@ -0,0 +1,78 @@
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file.  Set this to the copyright holder of the surrounding
+# package.  (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.)  Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright.  The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Taler Systems SA
+
+# This tells whether or not to prepend "GNU " prefix to the package
+# name that gets inserted into the header of the $(DOMAIN).pot file.
+# Possible values are "yes", "no", or empty.  If it is empty, try to
+# detect it automatically by scanning the files in $(top_srcdir) for
+# "GNU packagename" string.
+PACKAGE_GNU = yes
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+#   in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+#   understood.
+# - Strings which make invalid assumptions about notation of date, time or
+#   money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS = taler@gnu.org
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used.  It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
+
+# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
+# context.  Possible values are "yes" and "no".  Set this to yes if the
+# package uses functions taking also a message context, like pgettext(), or
+# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
+USE_MSGCTXT = no
+
+# These options get passed to msgmerge.
+# Useful options are in particular:
+#   --previous            to keep previous msgids of translated messages,
+#   --quiet               to reduce the verbosity.
+MSGMERGE_OPTIONS = --quiet
+
+# These options get passed to msginit.
+# If you want to disable line wrapping when writing PO files, add
+# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
+# MSGINIT_OPTIONS.
+MSGINIT_OPTIONS =
+
+# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
+# has changed.  Possible values are "yes" and "no".  Set this to no if
+# the POT file is checked in the repository and the version control
+# program ignores timestamps.
+PO_DEPENDS_ON_POT = yes
+
+# This tells whether or not to forcibly update $(DOMAIN).pot and
+# regenerate PO files on "make dist".  Possible values are "yes" and
+# "no".  Set this to no if the POT file and PO files are maintained
+# externally.
+DIST_DEPENDS_ON_UPDATE_PO = yes
diff --git a/po/Makevars.template b/po/Makevars.template
new file mode 100644
index 0000000..0648ec7
--- /dev/null
+++ b/po/Makevars.template
@@ -0,0 +1,78 @@
+# Makefile variables for PO directory in any package using GNU gettext.
+
+# Usually the message domain is the same as the package name.
+DOMAIN = $(PACKAGE)
+
+# These two variables depend on the location of this directory.
+subdir = po
+top_builddir = ..
+
+# These options get passed to xgettext.
+XGETTEXT_OPTIONS = --keyword=_ --keyword=N_
+
+# This is the copyright holder that gets inserted into the header of the
+# $(DOMAIN).pot file.  Set this to the copyright holder of the surrounding
+# package.  (Note that the msgstr strings, extracted from the package's
+# sources, belong to the copyright holder of the package.)  Translators are
+# expected to transfer the copyright for their translations to this person
+# or entity, or to disclaim their copyright.  The empty string stands for
+# the public domain; in this case the translators are expected to disclaim
+# their copyright.
+COPYRIGHT_HOLDER = Free Software Foundation, Inc.
+
+# This tells whether or not to prepend "GNU " prefix to the package
+# name that gets inserted into the header of the $(DOMAIN).pot file.
+# Possible values are "yes", "no", or empty.  If it is empty, try to
+# detect it automatically by scanning the files in $(top_srcdir) for
+# "GNU packagename" string.
+PACKAGE_GNU =
+
+# This is the email address or URL to which the translators shall report
+# bugs in the untranslated strings:
+# - Strings which are not entire sentences, see the maintainer guidelines
+#   in the GNU gettext documentation, section 'Preparing Strings'.
+# - Strings which use unclear terms or require additional context to be
+#   understood.
+# - Strings which make invalid assumptions about notation of date, time or
+#   money.
+# - Pluralisation problems.
+# - Incorrect English spelling.
+# - Incorrect formatting.
+# It can be your email address, or a mailing list address where translators
+# can write to without being subscribed, or the URL of a web page through
+# which the translators can contact you.
+MSGID_BUGS_ADDRESS =
+
+# This is the list of locale categories, beyond LC_MESSAGES, for which the
+# message catalogs shall be used.  It is usually empty.
+EXTRA_LOCALE_CATEGORIES =
+
+# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'
+# context.  Possible values are "yes" and "no".  Set this to yes if the
+# package uses functions taking also a message context, like pgettext(), or
+# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.
+USE_MSGCTXT = no
+
+# These options get passed to msgmerge.
+# Useful options are in particular:
+#   --previous            to keep previous msgids of translated messages,
+#   --quiet               to reduce the verbosity.
+MSGMERGE_OPTIONS =
+
+# These options get passed to msginit.
+# If you want to disable line wrapping when writing PO files, add
+# --no-wrap to MSGMERGE_OPTIONS, XGETTEXT_OPTIONS, and
+# MSGINIT_OPTIONS.
+MSGINIT_OPTIONS =
+
+# This tells whether or not to regenerate a PO file when $(DOMAIN).pot
+# has changed.  Possible values are "yes" and "no".  Set this to no if
+# the POT file is checked in the repository and the version control
+# program ignores timestamps.
+PO_DEPENDS_ON_POT = yes
+
+# This tells whether or not to forcibly update $(DOMAIN).pot and
+# regenerate PO files on "make dist".  Possible values are "yes" and
+# "no".  Set this to no if the POT file and PO files are maintained
+# externally.
+DIST_DEPENDS_ON_UPDATE_PO = yes
diff --git a/po/POTFILES.in b/po/POTFILES.in
new file mode 100644
index 0000000..a38c16c
--- /dev/null
+++ b/po/POTFILES.in
@@ -0,0 +1,2 @@
+# List of source files which contain translatable strings.
+src/util/taler_error_codes.c
diff --git a/po/Rules-quot b/po/Rules-quot
new file mode 100644
index 0000000..baf6528
--- /dev/null
+++ b/po/Rules-quot
@@ -0,0 +1,58 @@
+# This file, Rules-quot, can be copied and used freely without restrictions.
+# Special Makefile rules for English message catalogs with quotation marks.
+
+DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header 
en@boldquot.header insert-header.sin Rules-quot
+
+.SUFFIXES: .insert-header .po-update-en
+
+en@quot.po-create:
+       $(MAKE) en@quot.po-update
+en@boldquot.po-create:
+       $(MAKE) en@boldquot.po-update
+
+en@quot.po-update: en@quot.po-update-en
+en@boldquot.po-update: en@boldquot.po-update-en
+
+.insert-header.po-update-en:
+       @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \
+       if test "$(PACKAGE)" = "gettext-tools" && test "$(CROSS_COMPILING)" != 
"yes"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && 
pwd`; export GETTEXTLIBDIR; fi; \
+       tmpdir=`pwd`; \
+       echo "$$lang:"; \
+       ll=`echo $$lang | sed -e 's/@.*//'`; \
+       LC_ALL=C; export LC_ALL; \
+       cd $(srcdir); \
+       if $(MSGINIT) $(MSGINIT_OPTIONS) -i $(DOMAIN).pot --no-translator -l 
$$lang -o - 2>/dev/null \
+          | $(SED) -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | \
+          { case `$(MSGFILTER) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \
+            '' | 0.[0-9] | 0.[0-9].* | 0.1[0-8] | 0.1[0-8].*) \
+              $(MSGFILTER) $(SED) -f `echo $$lang | sed -e 's/.*@//'`.sed \
+              ;; \
+            *) \
+              $(MSGFILTER) `echo $$lang | sed -e 's/.*@//'` \
+              ;; \
+            esac } 2>/dev/null > $$tmpdir/$$lang.new.po \
+            ; then \
+         if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \
+           rm -f $$tmpdir/$$lang.new.po; \
+         else \
+           if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \
+             :; \
+           else \
+             echo "creation of $$lang.po failed: cannot move 
$$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \
+             exit 1; \
+           fi; \
+         fi; \
+       else \
+         echo "creation of $$lang.po failed!" 1>&2; \
+         rm -f $$tmpdir/$$lang.new.po; \
+       fi
+
+en@quot.insert-header: insert-header.sin
+       sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' 
$(srcdir)/insert-header.sin > en@quot.insert-header
+
+en@boldquot.insert-header: insert-header.sin
+       sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' 
$(srcdir)/insert-header.sin > en@boldquot.insert-header
+
+mostlyclean: mostlyclean-quot
+mostlyclean-quot:
+       rm -f *.insert-header
diff --git a/po/boldquot.sed b/po/boldquot.sed
new file mode 100644
index 0000000..4b937aa
--- /dev/null
+++ b/po/boldquot.sed
@@ -0,0 +1,10 @@
+s/"\([^"]*\)"/“\1”/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“”/""/g
+s/“/“/g
+s/”/”/g
+s/‘/‘/g
+s/’/’/g
diff --git a/po/en@boldquot.header b/po/en@boldquot.header
new file mode 100644
index 0000000..fedb6a0
--- /dev/null
+++ b/po/en@boldquot.header
@@ -0,0 +1,25 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
+# This catalog furthermore displays the text between the quotation marks in
+# bold face, assuming the VT100/XTerm escape sequences.
+#
diff --git a/po/en@quot.header b/po/en@quot.header
new file mode 100644
index 0000000..a9647fc
--- /dev/null
+++ b/po/en@quot.header
@@ -0,0 +1,22 @@
+# All this catalog "translates" are quotation characters.
+# The msgids must be ASCII and therefore cannot contain real quotation
+# characters, only substitutes like grave accent (0x60), apostrophe (0x27)
+# and double quote (0x22). These substitutes look strange; see
+# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html
+#
+# This catalog translates grave accent (0x60) and apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019).
+# It also translates pairs of apostrophe (0x27) to
+# left single quotation mark (U+2018) and right single quotation mark (U+2019)
+# and pairs of quotation mark (0x22) to
+# left double quotation mark (U+201C) and right double quotation mark (U+201D).
+#
+# When output to an UTF-8 terminal, the quotation characters appear perfectly.
+# When output to an ISO-8859-1 terminal, the single quotation marks are
+# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to
+# grave/acute accent (by libiconv), and the double quotation marks are
+# transliterated to 0x22.
+# When output to an ASCII terminal, the single quotation marks are
+# transliterated to apostrophes, and the double quotation marks are
+# transliterated to 0x22.
+#
diff --git a/po/insert-header.sin b/po/insert-header.sin
new file mode 100644
index 0000000..b26de01
--- /dev/null
+++ b/po/insert-header.sin
@@ -0,0 +1,23 @@
+# Sed script that inserts the file called HEADER before the header entry.
+#
+# At each occurrence of a line starting with "msgid ", we execute the following
+# commands. At the first occurrence, insert the file. At the following
+# occurrences, do nothing. The distinction between the first and the following
+# occurrences is achieved by looking at the hold space.
+/^msgid /{
+x
+# Test if the hold space is empty.
+s/m/m/
+ta
+# Yes it was empty. First occurrence. Read the file.
+r HEADER
+# Output the file's contents by reading the next line. But don't lose the
+# current line while doing this.
+g
+N
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/po/quot.sed b/po/quot.sed
new file mode 100644
index 0000000..0122c46
--- /dev/null
+++ b/po/quot.sed
@@ -0,0 +1,6 @@
+s/"\([^"]*\)"/“\1”/g
+s/`\([^`']*\)'/‘\1’/g
+s/ '\([^`']*\)' / ‘\1’ /g
+s/ '\([^`']*\)'$/ ‘\1’/g
+s/^'\([^`']*\)' /‘\1’ /g
+s/“”/""/g
diff --git a/po/remove-potcdate.sin b/po/remove-potcdate.sin
new file mode 100644
index 0000000..2436c49
--- /dev/null
+++ b/po/remove-potcdate.sin
@@ -0,0 +1,19 @@
+# Sed script that remove the POT-Creation-Date line in the header entry
+# from a POT file.
+#
+# The distinction between the first and the following occurrences of the
+# pattern is achieved by looking at the hold space.
+/^"POT-Creation-Date: .*"$/{
+x
+# Test if the hold space is empty.
+s/P/P/
+ta
+# Yes it was empty. First occurrence. Remove the line.
+g
+d
+bb
+:a
+# The hold space was nonempty. Following occurrences. Do nothing.
+x
+:b
+}
diff --git a/po/stamp-po b/po/stamp-po
new file mode 100644
index 0000000..9788f70
--- /dev/null
+++ b/po/stamp-po
@@ -0,0 +1 @@
+timestamp
diff --git a/po/taler-exchange.pot b/po/taler-exchange.pot
new file mode 100644
index 0000000..6d05be5
--- /dev/null
+++ b/po/taler-exchange.pot
@@ -0,0 +1,2374 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR Taler Systems SA
+# This file is distributed under the same license as the GNU taler-exchange 
package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: GNU taler-exchange 0.9.2\n"
+"Report-Msgid-Bugs-To: taler@gnu.org\n"
+"POT-Creation-Date: 2023-02-21 16:40+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: src/util/taler_error_codes.c:62
+msgid "Special code to indicate success (no error)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:68
+msgid "A non-integer error code was returned in the JSON response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:74
+msgid "An internal failure happened on the client side."
+msgstr ""
+
+#: src/util/taler_error_codes.c:80
+msgid "The response we got from the server was not even in JSON format."
+msgstr ""
+
+#: src/util/taler_error_codes.c:86
+msgid "An operation timed out."
+msgstr ""
+
+#: src/util/taler_error_codes.c:92
+msgid ""
+"The version string given does not follow the expected CURRENT:REVISION:AGE "
+"Format."
+msgstr ""
+
+#: src/util/taler_error_codes.c:98
+msgid ""
+"The service responded with a reply that was in JSON but did not satsify the "
+"protocol. Note that invalid cryptographic signatures should have signature-"
+"specific error codes."
+msgstr ""
+
+#: src/util/taler_error_codes.c:104
+msgid ""
+"There is an error in the client-side configuration, for example the base URL "
+"specified is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:110
+msgid ""
+"The client made a request to a service, but received an error response it "
+"does not know how to handle."
+msgstr ""
+
+#: src/util/taler_error_codes.c:116
+msgid "The HTTP method used is invalid for this endpoint."
+msgstr ""
+
+#: src/util/taler_error_codes.c:122
+msgid "There is no endpoint defined for the URL provided by the client."
+msgstr ""
+
+#: src/util/taler_error_codes.c:128
+msgid "The JSON in the client's request was malformed (generic parse error)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:134
+msgid ""
+"Some of the HTTP headers provided by the client caused the server to not be "
+"able to handle the request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:140
+msgid "The payto:// URI provided by the client is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:146
+msgid "A required parameter in the request was missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:152
+msgid "A parameter in the request was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:158
+msgid ""
+"The reserve public key given as part of a /reserves/ endpoint was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:164
+msgid "The currencies involved in the operation do not match."
+msgstr ""
+
+#: src/util/taler_error_codes.c:170
+msgid ""
+"The URI is longer than the longest URI the HTTP server is willing to parse."
+msgstr ""
+
+#: src/util/taler_error_codes.c:176
+msgid "The body is too large to be permissible for the endpoint."
+msgstr ""
+
+#: src/util/taler_error_codes.c:182
+msgid "The service failed initialize its connection to the database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:188
+msgid ""
+"The service encountered an error event to just start the database "
+"transaction."
+msgstr ""
+
+#: src/util/taler_error_codes.c:194
+msgid "The service failed to store information in its database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:200
+msgid "The service failed to fetch information from its database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:206
+msgid ""
+"The service encountered an error event to commit the database transaction "
+"(hard, unrecoverable error)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:212
+msgid ""
+"The service encountered an error event to commit the database transaction, "
+"even after repeatedly retrying it there was always a conflicting "
+"transaction. (This indicates a repeated serialization error; should only "
+"happen if some client maliciously tries to create conflicting concurrent "
+"transactions.)"
+msgstr ""
+
+#: src/util/taler_error_codes.c:218
+msgid ""
+"The service's database is inconsistent and violates service-internal "
+"invariants."
+msgstr ""
+
+#: src/util/taler_error_codes.c:224
+msgid "The HTTP server experienced an internal invariant failure (bug)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:230
+msgid ""
+"The service could not compute a cryptographic hash over some JSON value."
+msgstr ""
+
+#: src/util/taler_error_codes.c:236
+msgid "The service could not compute an amount."
+msgstr ""
+
+#: src/util/taler_error_codes.c:242
+msgid "The HTTP server had insufficient memory to parse the request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:248
+msgid "The HTTP server failed to allocate memory."
+msgstr ""
+
+#: src/util/taler_error_codes.c:254
+msgid "The HTTP server failed to allocate memory for building JSON reply."
+msgstr ""
+
+#: src/util/taler_error_codes.c:260
+msgid "The HTTP server failed to allocate memory for making a CURL request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:266
+msgid ""
+"The backend could not locate a required template to generate an HTML reply."
+msgstr ""
+
+#: src/util/taler_error_codes.c:272
+msgid "The backend could not expand the template to generate an HTML reply."
+msgstr ""
+
+#: src/util/taler_error_codes.c:278
+msgid "Exchange is badly configured and thus cannot operate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:284
+msgid "Operation specified unknown for this endpoint."
+msgstr ""
+
+#: src/util/taler_error_codes.c:290
+msgid ""
+"The number of segments included in the URI does not match the number of "
+"segments expected by the endpoint."
+msgstr ""
+
+#: src/util/taler_error_codes.c:296
+msgid ""
+"The same coin was already used with a different denomination previously."
+msgstr ""
+
+#: src/util/taler_error_codes.c:302
+msgid ""
+"The public key of given to a \"/coins/\" endpoint of the exchange was "
+"malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:308
+msgid ""
+"The exchange is not aware of the denomination key the wallet requested for "
+"the operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:314
+msgid "The signature of the denomination key over the coin is not valid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:320
+msgid ""
+"The exchange failed to perform the operation as it could not find the "
+"private keys. This is a problem with the exchange setup, not with the "
+"client's request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:326
+msgid "Validity period of the denomination lies in the future."
+msgstr ""
+
+#: src/util/taler_error_codes.c:332
+msgid ""
+"Denomination key of the coin is past its expiration time for the requested "
+"operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:338
+msgid "Denomination key of the coin has been revoked."
+msgstr ""
+
+#: src/util/taler_error_codes.c:344
+msgid ""
+"An operation where the exchange interacted with a security module timed out."
+msgstr ""
+
+#: src/util/taler_error_codes.c:350
+msgid ""
+"The respective coin did not have sufficient residual value for the "
+"operation.  The \"history\" in this response provides the \"residual_value\" "
+"of the coin, which may be less than its \"original_value\"."
+msgstr ""
+
+#: src/util/taler_error_codes.c:356
+msgid ""
+"The exchange had an internal error reconstructing the transaction history of "
+"the coin that was being processed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:362
+msgid ""
+"The exchange failed to obtain the transaction history of the given coin from "
+"the database while generating an insufficient funds errors."
+msgstr ""
+
+#: src/util/taler_error_codes.c:368
+msgid "The same coin was already used with a different age hash previously."
+msgstr ""
+
+#: src/util/taler_error_codes.c:374
+msgid ""
+"The requested operation is not valid for the cipher used by the selected "
+"denomination."
+msgstr ""
+
+#: src/util/taler_error_codes.c:380
+msgid "The provided arguments for the operation use inconsistent ciphers."
+msgstr ""
+
+#: src/util/taler_error_codes.c:386
+msgid ""
+"The number of denominations specified in the request exceeds the limit of "
+"the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:392
+msgid ""
+"The time at the server is too far off from the time specified in the "
+"request. Most likely the client system time is wrong."
+msgstr ""
+
+#: src/util/taler_error_codes.c:398
+msgid ""
+"The specified amount for the coin is higher than the value of the "
+"denomination of the coin."
+msgstr ""
+
+#: src/util/taler_error_codes.c:404
+msgid "The exchange was not properly configured with global fees."
+msgstr ""
+
+#: src/util/taler_error_codes.c:410
+msgid "The exchange was not properly configured with wire fees."
+msgstr ""
+
+#: src/util/taler_error_codes.c:416
+msgid "The purse public key was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:422
+msgid "The purse is unknown."
+msgstr ""
+
+#: src/util/taler_error_codes.c:428
+msgid "The purse has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:434
+msgid ""
+"The exchange has no information about the \"reserve_pub\" that was given."
+msgstr ""
+
+#: src/util/taler_error_codes.c:440
+msgid ""
+"The exchange is not allowed to proceed with the operation until the client "
+"has satisfied a KYC check."
+msgstr ""
+
+#: src/util/taler_error_codes.c:446
+msgid ""
+"Inconsistency between provided age commitment and attest: either none or "
+"both must be provided"
+msgstr ""
+
+#: src/util/taler_error_codes.c:452
+msgid ""
+"The provided attestation for the minimum age couldn't be verified by the "
+"exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:458
+msgid "The purse was deleted."
+msgstr ""
+
+#: src/util/taler_error_codes.c:464
+msgid "The public key of the AML officer in the URL was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:470
+msgid "The signature affirming the GET request of the AML officer is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:476
+msgid "The specified AML officer does not have access at this time."
+msgstr ""
+
+#: src/util/taler_error_codes.c:482
+msgid ""
+"The requested operation is denied pending the resolution of an anti-money "
+"laundering investigation by the exchange operator. This is a manual process, "
+"please wait and retry later."
+msgstr ""
+
+#: src/util/taler_error_codes.c:488
+msgid ""
+"The requested operation is denied as the account was frozen on suspicion of "
+"money laundering. Please contact the exchange operator."
+msgstr ""
+
+#: src/util/taler_error_codes.c:494
+msgid ""
+"The exchange did not find information about the specified transaction in the "
+"database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:500
+msgid "The wire hash of given to a \"/deposits/\" handler was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:506
+msgid "The merchant key of given to a \"/deposits/\" handler was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:512
+msgid ""
+"The hash of the contract terms given to a \"/deposits/\" handler was "
+"malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:518
+msgid "The coin public key of given to a \"/deposits/\" handler was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:524
+msgid ""
+"The signature returned by the exchange in a /deposits/ request was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:530 src/util/taler_error_codes.c:860
+msgid "The signature of the merchant is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:536
+msgid "The provided policy data was not accepted"
+msgstr ""
+
+#: src/util/taler_error_codes.c:542
+msgid ""
+"The given reserve does not have sufficient funds to admit the requested "
+"withdraw operation at this time.  The response includes the current \"balance"
+"\" of the reserve as well as the transaction \"history\" that lead to this "
+"balance."
+msgstr ""
+
+#: src/util/taler_error_codes.c:548
+msgid ""
+"The given reserve does not have sufficient funds to admit the requested age-"
+"withdraw operation at this time.  The response includes the current \"balance"
+"\" of the reserve as well as the transaction \"history\" that lead to this "
+"balance."
+msgstr ""
+
+#: src/util/taler_error_codes.c:554
+msgid ""
+"The amount to withdraw together with the fee exceeds the numeric range for "
+"Taler amounts.  This is not a client failure, as the coin value and fees "
+"come from the exchange's configuration."
+msgstr ""
+
+#: src/util/taler_error_codes.c:560
+msgid "The exchange failed to create the signature using the denomination key."
+msgstr ""
+
+#: src/util/taler_error_codes.c:566
+msgid "The signature of the reserve is not valid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:572
+msgid ""
+"When computing the reserve history, we ended up with a negative overall "
+"balance, which should be impossible."
+msgstr ""
+
+#: src/util/taler_error_codes.c:578
+msgid ""
+"The reserve did not have sufficient funds in it to pay for a full reserve "
+"history statement."
+msgstr ""
+
+#: src/util/taler_error_codes.c:584
+msgid "Withdraw period of the coin to be withdrawn is in the past."
+msgstr ""
+
+#: src/util/taler_error_codes.c:590
+msgid "The client failed to unblind the blind signature."
+msgstr ""
+
+#: src/util/taler_error_codes.c:596
+msgid "The client re-used a withdraw nonce, which is not allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:602
+msgid ""
+"The batch withdraw included a planchet that was already withdrawn. This is "
+"not allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:608
+msgid ""
+"The signature made by the coin over the deposit permission is not valid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:614
+msgid ""
+"The same coin was already deposited for the same merchant and contract with "
+"other details."
+msgstr ""
+
+#: src/util/taler_error_codes.c:620
+msgid ""
+"The stated value of the coin after the deposit fee is subtracted would be "
+"negative."
+msgstr ""
+
+#: src/util/taler_error_codes.c:626
+msgid "The stated refund deadline is after the wire deadline."
+msgstr ""
+
+#: src/util/taler_error_codes.c:632
+msgid "The stated wire deadline is \"never\", which makes no sense."
+msgstr ""
+
+#: src/util/taler_error_codes.c:638
+msgid ""
+"The exchange failed to canonicalize and hash the given wire format. For "
+"example, the merchant failed to provide the \"salt\" or a valid payto:// URI "
+"in the wire details.  Note that while the exchange will do some basic sanity "
+"checking on the wire details, it cannot warrant that the banking system will "
+"ultimately be able to route to the specified address, even if this check "
+"passed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:644
+msgid ""
+"The hash of the given wire address does not match the wire hash specified in "
+"the proposal data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:650
+msgid "The signature provided by the exchange is not valid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:656
+msgid ""
+"The deposited amount is smaller than the deposit fee, which would result in "
+"a negative contribution."
+msgstr ""
+
+#: src/util/taler_error_codes.c:662
+msgid "The proof of policy fulfillment was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:668
+msgid ""
+"The reserve balance, status or history was requested for a reserve which is "
+"not known to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:674
+msgid "The reserve status was requested with a bad signature."
+msgstr ""
+
+#: src/util/taler_error_codes.c:680
+msgid "The reserve history was requested with a bad signature."
+msgstr ""
+
+#: src/util/taler_error_codes.c:686
+msgid ""
+"The exchange encountered melt fees exceeding the melted coin's contribution."
+msgstr ""
+
+#: src/util/taler_error_codes.c:692
+msgid "The signature made with the coin to be melted is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:698
+msgid ""
+"The denomination of the given coin has past its expiration date and it is "
+"also not a valid zombie (that is, was not refreshed with the fresh coin "
+"being subjected to recoup)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:704
+msgid "The signature returned by the exchange in a melt request was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:710
+msgid ""
+"The provided transfer keys do not match up with the original commitment.  "
+"Information about the original commitment is included in the response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:716
+msgid "Failed to produce the blinded signatures over the coins to be returned."
+msgstr ""
+
+#: src/util/taler_error_codes.c:722
+msgid ""
+"The exchange is unaware of the refresh session specified in the request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:728
+msgid ""
+"The size of the cut-and-choose dimension of the private transfer keys "
+"request does not match #TALER_CNC_KAPPA - 1."
+msgstr ""
+
+#: src/util/taler_error_codes.c:734
+msgid ""
+"The number of envelopes given does not match the number of denomination keys "
+"given."
+msgstr ""
+
+#: src/util/taler_error_codes.c:740
+msgid ""
+"The exchange encountered a numeric overflow totaling up the cost for the "
+"refresh operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:746
+msgid ""
+"The exchange's cost calculation shows that the melt amount is below the "
+"costs of the transaction."
+msgstr ""
+
+#: src/util/taler_error_codes.c:752
+msgid "The signature made with the coin over the link data is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:758
+msgid "The refresh session hash given to a /refreshes/ handler was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:764
+msgid "Operation specified invalid for this endpoint."
+msgstr ""
+
+#: src/util/taler_error_codes.c:770
+msgid ""
+"The client provided age commitment data, but age restriction is not "
+"supported on this server."
+msgstr ""
+
+#: src/util/taler_error_codes.c:776
+msgid ""
+"The client provided invalid age commitment data: missing, not an array, or  "
+"array of invalid size."
+msgstr ""
+
+#: src/util/taler_error_codes.c:782
+msgid "The coin specified in the link request is unknown to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:788
+msgid "The public key of given to a /transfers/ handler was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:794
+msgid ""
+"The exchange did not find information about the specified wire transfer "
+"identifier in the database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:800
+msgid ""
+"The exchange did not find information about the wire transfer fees it "
+"charged."
+msgstr ""
+
+#: src/util/taler_error_codes.c:806
+msgid ""
+"The exchange found a wire fee that was above the total transfer value (and "
+"thus could not have been charged)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:812
+msgid "The wait target of the URL was not in the set of expected values."
+msgstr ""
+
+#: src/util/taler_error_codes.c:818
+msgid "The signature on the purse status returned by the exchange was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:824
+msgid ""
+"The exchange knows literally nothing about the coin we were asked to refund. "
+"But without a transaction history, we cannot issue a refund. This is kind-of "
+"OK, the owner should just refresh it directly without executing the refund."
+msgstr ""
+
+#: src/util/taler_error_codes.c:830
+msgid ""
+"We could not process the refund request as the coin's transaction history "
+"does not permit the requested refund because then refunds would exceed the "
+"deposit amount.  The \"history\" in the response proves this."
+msgstr ""
+
+#: src/util/taler_error_codes.c:836
+msgid ""
+"The exchange knows about the coin we were asked to refund, but not about the "
+"specific /deposit operation.  Hence, we cannot issue a refund (as we do not "
+"know if this merchant public key is authorized to do a refund)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:842
+msgid ""
+"The exchange can no longer refund the customer/coin as the money was already "
+"transferred (paid out) to the merchant. (It should be past the refund "
+"deadline.)"
+msgstr ""
+
+#: src/util/taler_error_codes.c:848
+msgid ""
+"The refund fee specified for the request is lower than the refund fee "
+"charged by the exchange for the given denomination key of the refunded coin."
+msgstr ""
+
+#: src/util/taler_error_codes.c:854
+msgid ""
+"The refunded amount is smaller than the refund fee, which would result in a "
+"negative refund."
+msgstr ""
+
+#: src/util/taler_error_codes.c:866
+msgid "Merchant backend failed to create the refund confirmation signature."
+msgstr ""
+
+#: src/util/taler_error_codes.c:872
+msgid ""
+"The signature returned by the exchange in a refund request was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:878
+msgid "The failure proof returned by the exchange is incorrect."
+msgstr ""
+
+#: src/util/taler_error_codes.c:884
+msgid ""
+"Conflicting refund granted before with different amount but same refund "
+"transaction ID."
+msgstr ""
+
+#: src/util/taler_error_codes.c:890 src/util/taler_error_codes.c:926
+msgid "The given coin signature is invalid for the request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:896
+msgid ""
+"The exchange could not find the corresponding withdraw operation. The "
+"request is denied."
+msgstr ""
+
+#: src/util/taler_error_codes.c:902 src/util/taler_error_codes.c:914
+msgid "The coin's remaining balance is zero.  The request is denied."
+msgstr ""
+
+#: src/util/taler_error_codes.c:908 src/util/taler_error_codes.c:938
+msgid "The exchange failed to reproduce the coin's blinding."
+msgstr ""
+
+#: src/util/taler_error_codes.c:920 src/util/taler_error_codes.c:944
+msgid "The coin's denomination has not been revoked yet."
+msgstr ""
+
+#: src/util/taler_error_codes.c:932
+msgid ""
+"The exchange could not find the corresponding melt operation. The request is "
+"denied."
+msgstr ""
+
+#: src/util/taler_error_codes.c:950
+msgid ""
+"This exchange does not allow clients to request /keys for times other than "
+"the current (exchange) time."
+msgstr ""
+
+#: src/util/taler_error_codes.c:956
+msgid "A signature in the server's response was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:962
+msgid ""
+"No bank accounts are enabled for the exchange. The administrator should "
+"enable-account using the taler-exchange-offline tool."
+msgstr ""
+
+#: src/util/taler_error_codes.c:968
+msgid ""
+"The payto:// URI stored in the exchange database for its bank account is "
+"malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:974
+msgid ""
+"No wire fees are configured for an enabled wire method of the exchange. The "
+"administrator must set the wire-fee using the taler-exchange-offline tool."
+msgstr ""
+
+#: src/util/taler_error_codes.c:980
+msgid "This purse was previously created with different meta data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:986
+msgid "This purse was previously merged with different meta data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:992
+msgid "The reserve has insufficient funds to create another purse."
+msgstr ""
+
+#: src/util/taler_error_codes.c:998
+msgid ""
+"The purse fee specified for the request is lower than the purse fee charged "
+"by the exchange at this time."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1004
+msgid ""
+"The payment request cannot be deleted anymore, as it either already "
+"completed or timed out."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1010
+msgid "The signature affirming the purse deletion is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1016
+msgid ""
+"The exchange failed to talk to the process responsible for its private "
+"denomination keys."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1022
+msgid "The response from the denomination key helper process was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1028 src/util/taler_error_codes.c:1052
+msgid ""
+"The helper refuses to sign with the key, because it is too early: the "
+"validity period has not yet started."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1034
+msgid "The signature of the exchange on the reply was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1040
+msgid ""
+"The exchange failed to talk to the process responsible for its private "
+"signing keys."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1046
+msgid "The response from the online signing key helper process was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1058
+msgid "The purse expiration time is in the past at the time of its creation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1064
+msgid "The purse expiration time is set to never, which is not allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1070
+msgid "The signature affirming the merge of the purse is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1076
+msgid "The signature by the reserve affirming the merge is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1082
+msgid "The signature by the reserve affirming the open operation is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1088
+msgid "The signature by the reserve affirming the close operation is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1094
+msgid ""
+"The signature by the reserve affirming the attestion request is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1100
+msgid ""
+"The exchange does not know an origin account to which the remaining reserve "
+"balance could be wired to, and the wallet failed to provide one."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1106
+msgid "The reserve balance is insufficient to pay for the open operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1112
+msgid ""
+"The auditor that was supposed to be disabled is unknown to this exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1118 src/util/taler_error_codes.c:1148
+msgid ""
+"The exchange has a more recently signed conflicting instruction and is thus "
+"refusing the current change (replay detected)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1124
+msgid "The signature to add or enable the auditor does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1130
+msgid "The signature to disable the auditor does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1136
+msgid "The signature to revoke the denomination does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1142
+msgid "The signature to revoke the online signing key does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1154
+msgid "The signingkey specified is unknown to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1160
+msgid "The signature to publish wire account does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1166
+msgid "The signature to add the wire account does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1172
+msgid "The signature to disable the wire account does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1178
+msgid "The wire account to be disabled is unknown to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1184
+msgid "The signature to affirm wire fees does not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1190 src/util/taler_error_codes.c:1208
+msgid ""
+"The signature conflicts with a previous signature affirming different fees."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1196
+msgid "The signature affirming the denomination key is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1202
+msgid "The signature affirming the signing key is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1214
+msgid "The signature affirming the fee structure is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1220
+msgid "The signature affirming the profit drain is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1226
+msgid "The signature affirming the AML decision is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1232
+msgid ""
+"The AML officer specified is not allowed to make AML decisions right now."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1238
+msgid ""
+"There is a more recent AML decision on file. The decision was rejected as "
+"timestamps of AML decisions must be monotonically increasing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1244
+msgid ""
+"There AML decision would impose an AML check of a type that is not provided "
+"by any KYC provider known to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1250
+msgid ""
+"The signature affirming the change in the AML officer status is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1256
+msgid ""
+"A more recent decision about the AML officer status is known to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1262
+msgid "The purse was previously created with different meta data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1268
+msgid "The purse was previously created with a different contract."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1274 src/util/taler_error_codes.c:1502
+msgid "A coin signature for a deposit into the purse is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1280
+msgid "The purse expiration time is in the past."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1286
+msgid "The purse expiration time is \"never\"."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1292
+msgid "The purse signature over the purse meta data is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1298
+msgid "The signature over the encrypted contract is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1304 src/util/taler_error_codes.c:1346
+msgid "The signature from the exchange over the confirmation is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1310
+msgid "The coin was previously deposited with different meta data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1316
+msgid ""
+"The encrypted contract was previously uploaded with different meta data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1322
+msgid "The deposited amount is less than the purse fee."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1328
+msgid "The signature using the merge key is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1334
+msgid "The signature using the reserve key is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1340
+msgid ""
+"The targeted purse is not yet full and thus cannot be merged. Retrying the "
+"request later may succeed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1352
+msgid "The exchange of the target account is not a partner of this exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1358
+msgid "The signature affirming the new partner is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1364
+msgid "Conflicting data for the partner already exists with the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1370
+msgid "The auditor signature over the denomination meta data is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1376
+msgid "The auditor that was specified is unknown to this exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1382
+msgid "The auditor that was specified is no longer used by this exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1388
+msgid "The signature affirming the wallet's KYC request was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1394
+msgid ""
+"The exchange received an unexpected malformed response from its KYC backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1400
+msgid "The backend signaled an unexpected failure."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1406
+msgid "The backend signaled an authorization failure."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1412
+msgid "The exchange is unaware of having made an the authorization request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1418
+msgid "The payto-URI hash did not match. Hence the request was denied."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1424
+msgid "The request used a logic specifier that is not known to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1430
+msgid ""
+"The request requires a logic which is no longer configured at the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1436
+msgid "The logic plugin had a bug in its interaction with the KYC provider."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1442
+msgid ""
+"The exchange could not process the request with its KYC provider because the "
+"provider refused access to the service. This indicates some configuration "
+"issue at the Taler exchange operator."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1448
+msgid ""
+"There was a timeout in the interaction between the exchange and the KYC "
+"provider. The most likely cause is some networking problem. Trying again "
+"later might succeed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1454
+msgid ""
+"The KYC provider responded with a status that was completely unexpected by "
+"the KYC logic of the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1460
+msgid ""
+"The rate limit of the exchange at the KYC provider has been exceeded. Trying "
+"much later might work."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1466
+msgid ""
+"The request to the webhook lacked proper authorization or authentication "
+"data."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1472
+msgid ""
+"The exchange does not know a contract under the given contract public key."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1478
+msgid "The URL does not encode a valid exchange public key in its path."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1484
+msgid "The returned encrypted contract did not decrypt."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1490
+msgid "The signature on the encrypted contract did not validate."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1496
+msgid "The decrypted contract was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1508
+msgid "It is too late to deposit coins into the purse."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1514
+msgid ""
+"The backend could not find the merchant instance specified in the request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1520
+msgid ""
+"The start and end-times in the wire fee structure leave a hole. This is not "
+"allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1526
+msgid ""
+"The merchant was unable to obtain a valid answer to /wire from the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1532
+msgid "The proposal is not known to the backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1538
+msgid ""
+"The order provided to the backend could not be completed, because a product "
+"to be completed via inventory data is not actually in our inventory."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1544
+msgid "The tip ID is unknown.  This could happen if the tip has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1550
+msgid "The contract obtained from the merchant backend was malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1556
+msgid "The order we found does not match the provided contract hash."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1562
+msgid ""
+"The exchange failed to provide a valid response to the merchant's /keys "
+"request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1568
+msgid "The exchange failed to respond to the merchant on time."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1574
+msgid "The merchant failed to talk to the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1580
+msgid "The exchange returned a maformed response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1586
+msgid "The exchange returned an unexpected response status."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1592
+msgid "The merchant refused the request due to lack of authorization."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1598
+msgid "The merchant instance specified in the request was deleted."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1604
+msgid ""
+"The backend could not find the inbound wire transfer specified in the "
+"request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1610
+msgid "The backend could not find the template(id) because it is not exist."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1616
+msgid "The backend could not find the webhook(id) because it is not exist."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1622
+msgid "The backend could not find the webhook(serial) because it is not exist."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1628
+msgid ""
+"The exchange failed to provide a valid answer to the tracking request, thus "
+"those details are not in the response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1634
+msgid ""
+"The merchant backend failed to construct the request for tracking to the "
+"exchange, thus tracking details are not in the response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1640
+msgid ""
+"The merchant backend failed trying to contact the exchange for tracking "
+"details, thus those details are not in the response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1646
+msgid ""
+"The claim token used to authenticate the client is invalid for this order."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1652
+msgid ""
+"The contract terms hash used to authenticate the client is invalid for this "
+"order."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1658
+msgid ""
+"The exchange responded saying that funds were insufficient (for example, due "
+"to double-spending)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1664
+msgid ""
+"The denomination key used for payment is not listed among the denomination "
+"keys of the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1670
+msgid ""
+"The denomination key used for payment is not audited by an auditor approved "
+"by the merchant."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1676
+msgid ""
+"There was an integer overflow totaling up the amounts or deposit fees in the "
+"payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1682
+msgid "The deposit fees exceed the total value of the payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1688
+msgid ""
+"After considering deposit and wire fees, the payment is insufficient to "
+"satisfy the required amount for the contract.  The client should revisit the "
+"logic used to calculate fees it must cover."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1694
+msgid ""
+"Even if we do not consider deposit and wire fees, the payment is "
+"insufficient to satisfy the required amount for the contract."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1700
+msgid "The signature over the contract of one of the coins was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1706
+msgid ""
+"When we tried to find information about the exchange to issue the deposit, "
+"we failed.  This usually only happens if the merchant backend is somehow "
+"unable to get its own HTTP client logic to work."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1712
+msgid "The refund deadline in the contract is after the transfer deadline."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1718
+msgid "The order was already paid (maybe by another wallet)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1724
+msgid "The payment is too late, the offer has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1730
+msgid ""
+"The \"merchant\" field is missing in the proposal data. This is an internal "
+"error as the proposal is from the merchant's own database at this point."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1736
+msgid ""
+"Failed to locate merchant's account information matching the wire hash given "
+"in the proposal."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1742
+msgid "The deposit time for the denomination has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1748
+msgid ""
+"The exchange of the deposited coin charges a wire fee that could not be "
+"added to the total (total amount too high)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1754
+msgid ""
+"The contract was not fully paid because of refunds. Note that clients MAY "
+"treat this as paid if, for example, contracts must be executed despite of "
+"refunds."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1760
+msgid ""
+"According to our database, we have refunded more than we were paid (which "
+"should not be possible)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1766
+msgid "Legacy stuff. Remove me with protocol v1."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1772
+msgid "The payment failed at the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1778
+msgid ""
+"The payment required a minimum age but one of the coins (of a denomination "
+"with support for age restriction) did not provide any age_commitment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1784
+msgid ""
+"The payment required a minimum age but one of the coins provided an "
+"age_commitment that contained a wrong number of public keys compared to the "
+"number of age groups defined in the denomination of the coin."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1790
+msgid ""
+"The payment required a minimum age but one of the coins provided a "
+"minimum_age_sig that couldn't be verified with the given age_commitment for "
+"that particular minimum age."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1796
+msgid ""
+"The payment required no minimum age but one of the coins (of a denomination "
+"with support for age restriction) did not provide the required "
+"h_age_commitment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1802
+msgid "The contract hash does not match the given order ID."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1808
+msgid "The signature of the merchant is not valid for the given contract hash."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1814
+msgid "The merchant failed to send the exchange the refund request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1820
+msgid "The merchant failed to find the exchange to process the lookup."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1826
+msgid "The merchant could not find the contract."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1832
+msgid "The payment was already completed and thus cannot be aborted anymore."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1838
+msgid "The hash provided by the wallet does not match the order."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1844
+msgid "The array of coins cannot be empty."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1850
+msgid "We could not claim the order because the backend is unaware of it."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1856
+msgid "We could not claim the order because someone else claimed it first."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1862
+msgid "The client-side experienced an internal failure."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1868
+msgid "The backend failed to sign the refund request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1874
+msgid "The client failed to unblind the signature returned by the merchant."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1880
+msgid "The exchange returned a failure code for the withdraw operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1886
+msgid "The merchant failed to add up the amounts to compute the pick up value."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1892
+msgid "The tip expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1898
+msgid ""
+"The requested withdraw amount exceeds the amount remaining to be picked up."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1904
+msgid ""
+"The merchant did not find the specified denomination key in the exchange's "
+"key set."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1910
+msgid ""
+"The backend lacks a wire transfer method configuration option for the given "
+"instance. Thus, this instance is unavailable (not findable for creating new "
+"orders)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1916
+msgid ""
+"The proposal had no timestamp and the backend failed to obtain the local "
+"time. Likely to be an internal error."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1922
+msgid ""
+"The order provided to the backend could not be parsed, some required fields "
+"were missing or ill-formed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1928
+msgid "The backend encountered an error: the proposal already exists."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1934
+msgid ""
+"The request is invalid: the wire deadline is before the refund deadline."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1940
+msgid ""
+"The request is invalid: a delivery date was given, but it is in the past."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1946
+msgid ""
+"The request is invalid: the wire deadline for the order would be \"never\"."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1952
+msgid ""
+"The request is invalid: a payment deadline was given, but it is in the past."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1958
+msgid ""
+"The request is invalid: a refund deadline was given, but it is in the past."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1964
+msgid "One of the paths to forget is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1970
+msgid "One of the paths to forget was not marked as forgettable."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1976
+msgid ""
+"The order provided to the backend could not be deleted, our offer is still "
+"valid and awaiting payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1982
+msgid ""
+"The order provided to the backend could not be deleted as the order was "
+"already paid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1988
+msgid ""
+"The amount to be refunded is inconsistent: either is lower than the previous "
+"amount being awarded, or it is too big to be paid back. In this second case, "
+"the fault stays on the business dept. side."
+msgstr ""
+
+#: src/util/taler_error_codes.c:1994
+msgid "The frontend gave an unpaid order id to issue the refund to."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2000
+msgid ""
+"The refund delay was set to 0 and thus no refunds are allowed for this order."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2006
+msgid "The exchange says it does not know this transfer."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2012
+msgid "We internally failed to execute the /track/transfer request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2018
+msgid ""
+"The amount transferred differs between what was submitted and what the "
+"exchange claimed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2024
+msgid ""
+"The exchange gave conflicting information about a coin which has been wire "
+"transferred."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2030
+msgid ""
+"The exchange charged a different wire fee than what it originally "
+"advertised, and it is higher."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2036
+msgid "We did not find the account that the transfer was made to."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2042
+msgid ""
+"The backend could not delete the transfer as the echange already replied to "
+"our inquiry about it and we have integrated the result."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2048
+msgid ""
+"The backend was previously informed about a wire transfer with the same ID "
+"but a different amount. Multiple wire transfers with the same ID are not "
+"allowed. If the new amount is correct, the old transfer should first be "
+"deleted."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2054
+msgid ""
+"The merchant backend cannot create an instance under the given identifier as "
+"one already exists. Use PATCH to modify the existing entry."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2060
+msgid ""
+"The merchant backend cannot create an instance because the authentication "
+"configuration field is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2066
+msgid ""
+"The merchant backend cannot update an instance's authentication settings "
+"because the provided authentication settings are malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2072
+msgid ""
+"The merchant backend cannot create an instance under the given identifier, "
+"the previous one was deleted but must be purged first."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2078
+msgid ""
+"The merchant backend cannot update an instance under the given identifier, "
+"the previous one was deleted but must be purged first."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2084
+msgid "The product ID exists."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2090
+msgid ""
+"The update would have reduced the total amount of product lost, which is not "
+"allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2096
+msgid ""
+"The update would have mean that more stocks were lost than what remains from "
+"total inventory after sales, which is not allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2102
+msgid ""
+"The update would have reduced the total amount of product in stock, which is "
+"not allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2108
+msgid ""
+"The update would have reduced the total amount of product sold, which is not "
+"allowed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2114
+msgid ""
+"The lock request is for more products than we have left (unlocked) in stock."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2120
+msgid "The deletion request is for a product that is locked."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2126
+msgid "The requested wire method is not supported by the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2132
+msgid "The reserve could not be deleted because it is unknown."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2138
+msgid "The reserve that was used to fund the tips has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2144
+msgid "The reserve that was used to fund the tips was not found in the DB."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2150
+msgid ""
+"The backend knows the instance that was supposed to support the tip, and it "
+"was configured for tipping. However, the funds remaining are insufficient to "
+"cover the tip, and the merchant should top up the reserve."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2156
+msgid "The backend failed to find a reserve needed to authorize the tip."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2162
+msgid ""
+"The merchant backend encountered a failure in computing the deposit total."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2168
+msgid "The template ID already exists."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2174
+msgid ""
+"Amount given in the using template and in the template contract. There is a "
+"conflict."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2180
+msgid ""
+"Subject given in the using template and in the template contract. There is a "
+"conflict."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2186
+msgid ""
+"Amount not given in the using template and in the template contract. There "
+"is a conflict."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2192
+msgid ""
+"Subject not given in the using template and in the template contract. There "
+"is a conflict."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2198
+msgid "The webhook ID elready exists."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2204
+msgid "The webhook serial elready exists."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2210
+msgid "The signature from the exchange on the deposit confirmation is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2216
+msgid ""
+"The exchange key used for the signature on the deposit confirmation was "
+"revoked."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2222
+msgid ""
+"Wire transfer attempted with credit and debit party being the same bank "
+"account."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2228
+msgid ""
+"Wire transfer impossible, due to financial limitation of the party that "
+"attempted the payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2234
+msgid ""
+"Negative numbers are not allowed (as value and/or fraction) to instantiate "
+"an amount object."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2240
+msgid ""
+"A too big number was used (as value and/or fraction) to instantiate an "
+"amount object."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2246
+msgid "Could not login for the requested operation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2252
+msgid "The bank account referenced in the requested operation was not found."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2258
+msgid ""
+"The transaction referenced in the requested operation (typically a reject "
+"operation), was not found."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2264
+msgid "Bank received a malformed amount string."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2270
+msgid ""
+"The client does not own the account credited by the transaction which is to "
+"be rejected, so it has no rights do reject it."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2276
+msgid ""
+"This error code is returned when no known exception types captured the "
+"exception."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2282
+msgid ""
+"This error code is used for all those exceptions that do not really need a "
+"specific error code to return to the client. Used for example when a client "
+"is trying to register with a unavailable username."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2288
+msgid ""
+"The request UID for a request to transfer funds has already been used, but "
+"with different details for the transfer."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2294
+msgid ""
+"The withdrawal operation already has a reserve selected.  The current "
+"request conflicts with the existing selection."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2300
+msgid ""
+"The wire transfer subject duplicates an existing reserve public key. But "
+"wire transfer subjects must be unique."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2306
+msgid ""
+"The client requested a transaction that is so far in the past, that it has "
+"been forgotten by the bank."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2312
+msgid "The client attempted to abort a transaction that was already confirmed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2318
+msgid "The client attempted to confirm a transaction that was already aborted."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2324
+msgid "The client attempted to register an account with the same name."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2330
+msgid ""
+"The client attempted to confirm a withdrawal operation before the wallet "
+"posted the required details."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2336
+msgid "The sync service failed find the account in its database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2342
+msgid "The SHA-512 hash provided in the If-None-Match header is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2348
+msgid ""
+"The SHA-512 hash provided in the If-Match header is malformed or missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2354
+msgid ""
+"The signature provided in the \"Sync-Signature\" header is malformed or "
+"missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2360
+msgid ""
+"The signature provided in the \"Sync-Signature\" header does not match the "
+"account, old or new Etags."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2366
+msgid "The \"Content-length\" field for the upload is not a number."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2372
+msgid ""
+"The \"Content-length\" field for the upload is too big based on the server's "
+"terms of service."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2378 src/util/taler_error_codes.c:2840
+msgid ""
+"The server is out of memory to handle the upload. Trying again later may "
+"succeed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2384 src/util/taler_error_codes.c:2858
+msgid "The uploaded data does not match the Etag."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2390 src/util/taler_error_codes.c:2654
+msgid "HTTP server experienced a timeout while awaiting promised payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2396
+msgid "Sync could not setup the payment request with its own backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2402
+msgid "The sync service failed find the backup to be updated in its database."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2408 src/util/taler_error_codes.c:2618
+msgid "The \"Content-length\" field for the upload is missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2414
+msgid "Sync had problems communicating with its payment backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2420
+msgid "Sync experienced a timeout communicating with its payment backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2426
+msgid ""
+"The wallet does not implement a version of the exchange protocol that is "
+"compatible with the protocol version of the exchange."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2432
+msgid ""
+"The wallet encountered an unexpected exception.  This is likely a bug in the "
+"wallet implementation."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2438
+msgid ""
+"The wallet received a response from a server, but the response can't be "
+"parsed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2444
+msgid ""
+"The wallet tried to make a network request, but it received no response."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2450
+msgid "The wallet tried to make a network request, but it was throttled."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2456
+msgid ""
+"The wallet made a request to a service, but received an error response it "
+"does not know how to handle."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2462
+msgid ""
+"The denominations offered by the exchange are insufficient.  Likely the "
+"exchange is badly configured or not maintained."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2468
+msgid "The wallet does not support the operation requested by a client."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2474
+msgid "The given taler://pay URI is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2480
+msgid ""
+"The signature on a coin by the exchange's denomination key is invalid after "
+"unblinding it."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2486
+msgid ""
+"The exchange does not know about the reserve (yet), and thus withdrawal "
+"can't progress."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2492
+msgid "The wallet core service is not available."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2498
+msgid ""
+"The bank has aborted a withdrawal operation, and thus a withdrawal can't "
+"complete."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2504
+msgid "An HTTP request made by the wallet timed out."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2510
+msgid "The order has already been claimed by another wallet."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2516
+msgid ""
+"A group of withdrawal operations (typically for the same reserve at the same "
+"exchange) has errors and will be tried again later."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2522
+msgid ""
+"The signature on a coin by the exchange's denomination key (obtained through "
+"the merchant via tipping) is invalid after unblinding it."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2528
+msgid ""
+"The wallet does not implement a version of the bank integration API that is "
+"compatible with the version offered by the bank."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2534
+msgid ""
+"The wallet processed a taler://pay URI, but the merchant base URL in the "
+"downloaded contract terms does not match the merchant base URL derived from "
+"the URI."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2540
+msgid "The merchant's signature on the contract terms is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2546
+msgid "The contract terms given by the merchant are malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2552
+msgid "A pending operation failed, and thus the request can't be completed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2558
+msgid ""
+"A payment was attempted, but the merchant had an internal server error (5xx)."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2564
+msgid "The crypto worker failed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2570
+msgid "The crypto worker received a bad request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2576
+msgid "A KYC step is required before withdrawal can proceed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2582
+msgid "The wallet does not have sufficient balance to create a deposit group."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2588
+msgid ""
+"The wallet does not have sufficient balance to create a peer push payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2594
+msgid "The wallet does not have sufficient balance to pay for an invoice."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2600
+msgid "We encountered a timeout with our payment backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2606
+msgid "The backend requested payment, but the request is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2612
+msgid "The backend got an unexpected reply from the payment processor."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2624
+msgid "The \"Content-length\" field for the upload is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2630
+msgid "The backend failed to setup an order with the payment processor."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2636
+msgid ""
+"The backend was not authorized to check for payment with the payment "
+"processor."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2642
+msgid "The backend could not check payment status with the payment processor."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2648
+msgid "The Anastasis provider could not be reached."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2660
+msgid "The key share is unknown to the provider."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2666
+msgid ""
+"The authorization method used for the key share is no longer supported by "
+"the provider."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2672
+msgid "The client needs to respond to the challenge."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2678
+msgid "The client's response to the challenge was invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2684
+msgid ""
+"The backend is not aware of having issued the provided challenge code. "
+"Either this is the wrong code, or it has expired."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2690
+msgid "The backend failed to initiate the authorization process."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2696
+msgid "The authorization succeeded, but the key share is no longer available."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2702
+msgid "The backend forgot the order we asked the client to pay for"
+msgstr ""
+
+#: src/util/taler_error_codes.c:2708
+msgid "The backend itself reported a bad exchange interaction."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2714
+msgid "The backend reported a payment status we did not expect."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2720
+msgid "The backend failed to setup the order for payment."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2726
+msgid "The decryption of the key share failed with the provided key."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2732
+msgid ""
+"The request rate is too high. The server is refusing requests to guard "
+"against brute-force attacks."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2738
+msgid ""
+"A request to issue a challenge is not valid for this authentication method."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2744
+msgid ""
+"The backend failed to store the key share because the UUID is already in use."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2750
+msgid ""
+"The backend failed to store the key share because the authorization method "
+"is not supported."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2756
+msgid "The provided phone number is not an acceptable number."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2762
+msgid "Failed to run the SMS transmission helper process."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2768
+msgid ""
+"Provider failed to send SMS. Helper terminated with a non-successful result."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2774
+msgid "The provided email address is not an acceptable address."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2780
+msgid "Failed to run the E-mail transmission helper process."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2786
+msgid ""
+"Provider failed to send E-mail. Helper terminated with a non-successful "
+"result."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2792
+msgid "The provided postal address is not an acceptable address."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2798
+msgid "Failed to run the mail transmission helper process."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2804
+msgid ""
+"Provider failed to send mail. Helper terminated with a non-successful result."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2810
+msgid "The provided IBAN address is not an acceptable IBAN."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2816
+msgid ""
+"The provider has not yet received the IBAN wire transfer authorizing the "
+"disclosure of the key share."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2822
+msgid "The backend did not find a TOTP key in the data provided."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2828
+msgid ""
+"The key provided does not satisfy the format restrictions for an Anastasis "
+"TOTP key."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2834
+msgid "The given if-none-match header is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2846
+msgid ""
+"The signature provided in the \"Anastasis-Policy-Signature\" header is "
+"malformed or missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2852
+msgid "The given if-match header is malformed."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2864
+msgid "The provider is unaware of the requested policy."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2870
+msgid "The given action is invalid for the current state of the reducer."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2876
+msgid "The given state of the reducer is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2882
+msgid "The given input to the reducer is invalid."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2888
+msgid ""
+"The selected authentication method does not work for the Anastasis provider."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2894
+msgid "The given input and action do not work for the current state."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2900
+msgid "We experienced an unexpected failure interacting with the backend."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2906
+msgid "The contents of a resource file did not match our expectations."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2912
+msgid "A required resource file is missing."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2918
+msgid "An input did not match the regular expression."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2924
+msgid "An input did not match the custom validation logic."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2930
+msgid ""
+"Our attempts to download the recovery document failed with all providers. "
+"Most likely the personal information you entered differs from the "
+"information you provided during the backup process and you should go back to "
+"the previous step. Alternatively, if you used a backup provider that is "
+"unknown to this application, you should add that provider manually."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2936
+msgid "Anastasis provider reported a fatal failure."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2942
+msgid "Anastasis provider failed to respond to the configuration request."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2948
+msgid ""
+"The policy we downloaded is malformed. Must have been a client error while "
+"creating the backup."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2954
+msgid "We failed to obtain the policy, likely due to a network issue."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2960
+msgid "The recovered secret did not match the required syntax."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2966
+msgid "The challenge data provided is too large for the available providers."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2972
+msgid "The provided core secret is too large for some of the providers."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2978
+msgid "The provider returned in invalid configuration."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2984
+msgid ""
+"The reducer encountered an internal error, likely a bug that needs to be "
+"reported."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2990
+msgid "The reducer already synchronized with all providers."
+msgstr ""
+
+#: src/util/taler_error_codes.c:2996
+msgid ""
+"A generic error happened in the LibEuFin nexus.  See the enclose details "
+"JSON for more information."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3002
+msgid "An uncaught exception happened in the LibEuFin nexus service."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3008
+msgid ""
+"A generic error happened in the LibEuFin sandbox.  See the enclose details "
+"JSON for more information."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3014
+msgid "An uncaught exception happened in the LibEuFin sandbox service."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3020
+msgid "This validation method is not supported by the service."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3026
+msgid "Number of allowed attempts for initiating a challenge exceeded."
+msgstr ""
+
+#: src/util/taler_error_codes.c:3032
+msgid "End of error code range."
+msgstr ""
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..9369502
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,8 @@
+*.o
+*.deps
+*.libs
+*.lo
+*.la
+*.log
+*.trs
+*/__pycache__
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..e10ecf8
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,37 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+if HAVE_POSTGRESQL
+  PQ_DIR = pq
+endif
+if HAVE_SQLITE
+  SQ_DIR = sq
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+pkgcfg_DATA = \
+  taler.conf
+
+EXTRA_DIST = \
+  taler.conf
+
+SUBDIRS = \
+  include \
+  util \
+  json \
+  extensions \
+  curl \
+  $(PQ_DIR) \
+  $(SQ_DIR) \
+  mhd \
+  templating \
+  bank-lib \
+  exchangedb \
+  kyclogic \
+  exchange \
+  auditordb \
+  auditor \
+  lib \
+  exchange-tools \
+  extensions/age_restriction \
+  testing \
+  benchmark
diff --git a/src/exchange-tools/.gitignore b/src/exchange-tools/.gitignore
new file mode 100644
index 0000000..69279d7
--- /dev/null
+++ b/src/exchange-tools/.gitignore
@@ -0,0 +1,3 @@
+taler-exchange-offline
+taler-auditor-offline
+taler-crypto-worker
diff --git a/src/exchange-tools/Makefile.am b/src/exchange-tools/Makefile.am
new file mode 100644
index 0000000..9555445
--- /dev/null
+++ b/src/exchange-tools/Makefile.am
@@ -0,0 +1,70 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+  coins.conf \
+  exchange-offline.conf
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+bin_PROGRAMS = \
+  taler-auditor-offline \
+  taler-exchange-offline \
+  taler-exchange-dbinit
+
+taler_exchange_offline_SOURCES = \
+  taler-exchange-offline.c
+taler_exchange_offline_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -ljansson \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_auditor_offline_SOURCES = \
+  taler-auditor-offline.c
+taler_auditor_offline_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -ljansson \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_dbinit_SOURCES = \
+  taler-exchange-dbinit.c
+taler_exchange_dbinit_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -lgnunetutil \
+  $(XLIB)
+taler_exchange_dbinit_CPPFLAGS = \
+  -I$(top_srcdir)/src/include \
+  -I$(top_srcdir)/src/pq/ \
+  $(POSTGRESQL_CPPFLAGS)
+
+
+# Testcases
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+# Distribution
+
+EXTRA_DIST = \
+  $(pkgcfg_DATA)
diff --git a/src/exchange-tools/coins.conf b/src/exchange-tools/coins.conf
new file mode 100644
index 0000000..4a32a9f
--- /dev/null
+++ b/src/exchange-tools/coins.conf
@@ -0,0 +1,25 @@
+# This configuration file is in the public domain
+#
+# This is a template file for coin definitions. There are no
+# reasonable defaults, as legal and business concerns influence each
+# value given.
+#
+# Note that while we only give one section here, you can define
+# any number of coins by providing many "coin_" sections.
+#
+# Coin definitions are detected because the section name begins with
+# "coin_".  The rest of the name is free, but of course following the
+# convention of "coin_$CURRENCY[_$SUBUNIT]_$VALUE" make sense.
+#
+# [coin_eur_ct_1]
+
+# All options are mandatory!
+# value = EUR:0.01
+# duration_withdraw = 7 days
+# duration_spend = 2 years
+# duration_legal = 3 years
+# fee_withdraw = EUR:0.00
+# fee_deposit = EUR:0.00
+# fee_refresh = EUR:0.01
+# fee_refund = EUR:0.01
+# rsa_keysize = 1024
diff --git a/src/exchange-tools/exchange-offline.conf 
b/src/exchange-tools/exchange-offline.conf
new file mode 100644
index 0000000..020eb34
--- /dev/null
+++ b/src/exchange-tools/exchange-offline.conf
@@ -0,0 +1,15 @@
+# This file is in the public domain.
+#
+[exchange-offline]
+
+# Where do we store the offline master private key of the exchange?
+MASTER_PRIV_FILE = ${TALER_DATA_HOME}exchange-offline/master.priv
+
+# Where do we store the TOFU key material?
+SECM_TOFU_FILE = ${TALER_DATA_HOME}exchange-offline/secm_tofus.pub
+
+# Base32-encoded public key of the RSA helper.
+# SECM_DENOM_PUBKEY =
+
+# Base32-encoded public key of the EdDSA helper.
+# SECM_ESIGN_PUBKEY =
diff --git a/src/exchange-tools/taler-exchange-dbinit.c 
b/src/exchange-tools/taler-exchange-dbinit.c
new file mode 100644
index 0000000..d2cd22c
--- /dev/null
+++ b/src/exchange-tools/taler-exchange-dbinit.c
@@ -0,0 +1,202 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchange-tools/taler-exchange-dbinit.c
+ * @brief Create tables for the exchange database.
+ * @author Florian Dold
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_exchangedb_lib.h"
+
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * -r option: do full DB reset
+ */
+static int reset_db;
+
+/**
+ * -s option: clear revolving shard locks
+ */
+static int clear_shards;
+
+/**
+ * -g option: garbage collect DB reset
+ */
+static int gc_db;
+
+/**
+ * -P option: setup a partitioned database
+ */
+static uint32_t num_partitions;
+
+/**
+ * -f option: force partitions to be created when there is only one
+ */
+static int force_create_partitions;
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be 
NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  struct TALER_EXCHANGEDB_Plugin *plugin;
+
+  (void) cls;
+  (void) args;
+  (void) cfgfile;
+
+  if (NULL ==
+      (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+  {
+    fprintf (stderr,
+             "Failed to initialize database plugin.\n");
+    global_ret = EXIT_NOTINSTALLED;
+    return;
+  }
+  if (reset_db)
+  {
+    if (GNUNET_OK !=
+        plugin->drop_tables (plugin->cls))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Could not drop tables as requested. Either database was not 
yet initialized, or permission denied. Consult the logs. Will still try to 
create new tables.\n");
+    }
+  }
+  if (GNUNET_OK !=
+      plugin->create_tables (plugin->cls,
+                             force_create_partitions || num_partitions > 0,
+                             num_partitions))
+  {
+    fprintf (stderr,
+             "Failed to initialize database.\n");
+    TALER_EXCHANGEDB_plugin_unload (plugin);
+    plugin = NULL;
+    global_ret = EXIT_NOPERMISSION;
+    return;
+  }
+  if (gc_db || clear_shards)
+  {
+    if (GNUNET_OK !=
+        plugin->preflight (plugin->cls))
+    {
+      fprintf (stderr,
+               "Failed to prepare database.\n");
+      TALER_EXCHANGEDB_plugin_unload (plugin);
+      plugin = NULL;
+      global_ret = EXIT_NOPERMISSION;
+      return;
+    }
+    if (clear_shards)
+    {
+      if (GNUNET_OK !=
+          plugin->delete_shard_locks (plugin->cls))
+      {
+        fprintf (stderr,
+                 "Clearing revolving shards failed!\n");
+      }
+    }
+    if (gc_db)
+    {
+      if (GNUNET_SYSERR == plugin->gc (plugin->cls))
+      {
+        fprintf (stderr,
+                 "Garbage collection failed!\n");
+      }
+    }
+  }
+  TALER_EXCHANGEDB_plugin_unload (plugin);
+  plugin = NULL;
+}
+
+
+/**
+ * The main function of the database initialization tool.
+ * Used to initialize the Taler Exchange's database.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, non-zero on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  const struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_option_flag ('g',
+                               "gc",
+                               "garbage collect database",
+                               &gc_db),
+    GNUNET_GETOPT_option_flag ('r',
+                               "reset",
+                               "reset database (DANGEROUS: all existing data 
is lost!)",
+                               &reset_db),
+    GNUNET_GETOPT_option_flag ('s',
+                               "shardunlock",
+                               "unlock all revolving shard locks (use after 
system crash or shard size change while services are not running)",
+                               &clear_shards),
+    GNUNET_GETOPT_option_uint ('P',
+                               "partition",
+                               "NUMBER",
+                               "Setup a partitioned database where each table 
which can be partitioned holds NUMBER partitions on a single DB node",
+                               &num_partitions),
+    GNUNET_GETOPT_option_flag ('f',
+                               "force",
+                               "Force partitions to be created if there is 
only one partition",
+                               &force_create_partitions),
+    GNUNET_GETOPT_OPTION_END
+  };
+  enum GNUNET_GenericReturnValue ret;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_get_utf8_args (argc, argv,
+                                    &argc, &argv))
+    return EXIT_INVALIDARGUMENT;
+  /* force linker to link against libtalerutil; if we do
+     not do this, the linker may "optimize" libtalerutil
+     away and skip #TALER_OS_init(), which we do need */
+  TALER_OS_init ();
+  ret = GNUNET_PROGRAM_run (
+    argc, argv,
+    "taler-exchange-dbinit",
+    gettext_noop ("Initialize Taler exchange database"),
+    options,
+    &run, NULL);
+  GNUNET_free_nz ((void *) argv);
+  if (GNUNET_SYSERR == ret)
+    return EXIT_INVALIDARGUMENT;
+  if (GNUNET_NO == ret)
+    return EXIT_SUCCESS;
+  return global_ret;
+}
+
+
+/* end of taler-exchange-dbinit.c */
diff --git a/src/exchange-tools/taler-exchange-offline.c 
b/src/exchange-tools/taler-exchange-offline.c
new file mode 100644
index 0000000..fed2943
--- /dev/null
+++ b/src/exchange-tools/taler-exchange-offline.c
@@ -0,0 +1,5429 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2020-2023 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file taler-exchange-offline.c
+ * @brief Support for operations involving the exchange's offline master key.
+ * @author Christian Grothoff
+ */
+#include <platform.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_extensions.h"
+#include <regex.h>
+
+
+/**
+ * Name of the input for the 'sign' and 'show' operation.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_INPUT_KEYS "exchange-input-keys-0"
+
+/**
+ * Name of the operation to 'disable auditor'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_DISABLE_AUDITOR "exchange-disable-auditor-0"
+
+/**
+ * Name of the operation to 'enable auditor'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_ENABLE_AUDITOR "exchange-enable-auditor-0"
+
+/**
+ * Name of the operation to 'enable wire'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_ENABLE_WIRE "exchange-enable-wire-0"
+
+/**
+ * Name of the operation to 'disable wire'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_DISABLE_WIRE "exchange-disable-wire-0"
+
+/**
+ * Name of the operation to set a 'wire-fee'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_SET_WIRE_FEE "exchange-set-wire-fee-0"
+
+/**
+ * Name of the operation to set a 'global-fee'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_SET_GLOBAL_FEE "exchange-set-global-fee-0"
+
+/**
+ * Name of the operation to 'upload' key signatures
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_UPLOAD_SIGS "exchange-upload-sigs-0"
+
+/**
+ * Name of the operation to 'revoke-denomination' key
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_REVOKE_DENOMINATION "exchange-revoke-denomination-0"
+
+/**
+ * Name of the operation to 'revoke-signkey'
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_REVOKE_SIGNKEY "exchange-revoke-signkey-0"
+
+/**
+ * Show the offline signing key.
+ * The last component --by convention-- identifies the protocol version
+ * and should be incremented whenever the JSON format of the 'argument' 
changes.
+ */
+#define OP_SETUP "exchange-setup-0"
+
+/**
+ * sign the enabled and configured extensions.
+ */
+#define OP_EXTENSIONS "exchange-extensions-0"
+
+/**
+ * Generate message to drain profits.
+ */
+#define OP_DRAIN_PROFITS "exchange-drain-profits-0"
+
+/**
+ * Setup AML staff.
+ */
+#define OP_UPDATE_AML_STAFF "exchange-add-aml-staff-0"
+
+/**
+ * Setup partner exchange for wad transfers.
+ */
+#define OP_ADD_PARTNER "exchange-add-partner-0"
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_MasterPrivateKeyP master_priv;
+
+/**
+ * Our private key, initialized in #load_offline_key().
+ */
+static struct TALER_MasterPublicKeyP master_pub;
+
+/**
+ * Our context for making HTTP requests.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Reschedule context for #ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Handle to the exchange's configuration
+ */
+static const struct GNUNET_CONFIGURATION_Handle *kcfg;
+
+/**
+ * Age restriction configuration
+ */
+static bool ar_enabled = false;
+static struct TALER_AgeRestrictionConfig ar_config = {0};
+
+/**
+ * Return value from main().
+ */
+static int global_ret;
+
+/**
+ * Input to consume.
+ */
+static json_t *in;
+
+/**
+ * Array of actions to perform.
+ */
+static json_t *out;
+
+/**
+ * Currency we have configured.
+ */
+static char *currency;
+
+/**
+ * URL of the exchange we are interacting with
+ * as per our configuration.
+ */
+static char *CFG_exchange_url;
+
+/**
+ * A subcommand supported by this program.
+ */
+struct SubCommand
+{
+  /**
+   * Name of the command.
+   */
+  const char *name;
+
+  /**
+   * Help text for the command.
+   */
+  const char *help;
+
+  /**
+   * Function implementing the command.
+   *
+   * @param args subsequent command line arguments (char **)
+   */
+  void (*cb)(char *const *args);
+};
+
+
+/**
+ * Data structure for denomination revocation requests.
+ */
+struct DenomRevocationRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DenomRevocationRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DenomRevocationRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for signkey revocation requests.
+ */
+struct SignkeyRevocationRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct SignkeyRevocationRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct SignkeyRevocationRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for auditor add requests.
+ */
+struct AuditorAddRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AuditorAddRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AuditorAddRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementAuditorEnableHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for auditor del requests.
+ */
+struct AuditorDelRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AuditorDelRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AuditorDelRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementAuditorDisableHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for wire add requests.
+ */
+struct WireAddRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireAddRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireAddRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementWireEnableHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for wire del requests.
+ */
+struct WireDelRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireDelRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireDelRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementWireDisableHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for announcing wire fees.
+ */
+struct WireFeeRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireFeeRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireFeeRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementSetWireFeeHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for draining profits.
+ */
+struct DrainProfitsRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DrainProfitsRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DrainProfitsRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementDrainProfitsHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for announcing global fees.
+ */
+struct GlobalFeeRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct GlobalFeeRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct GlobalFeeRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Ongoing /keys request.
+ */
+struct UploadKeysRequest
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadKeysRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadKeysRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementPostKeysHandle *h;
+
+  /**
+   * Operation index.
+   */
+  size_t idx;
+};
+
+/**
+ * Ongoing /management/extensions request.
+ */
+struct UploadExtensionsRequest
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadExtensionsRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct UploadExtensionsRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementPostExtensionsHandle *h;
+
+  /**
+   * Operation index.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for AML staff requests.
+ */
+struct AmlStaffRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AmlStaffRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct AmlStaffRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Data structure for partner add requests.
+ */
+struct PartnerAddRequest
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PartnerAddRequest *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct PartnerAddRequest *prev;
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_EXCHANGE_ManagementAddPartner *h;
+
+  /**
+   * Array index of the associated command.
+   */
+  size_t idx;
+};
+
+
+/**
+ * Next work item to perform.
+ */
+static struct GNUNET_SCHEDULER_Task *nxt;
+
+/**
+ * Handle for #do_download.
+ */
+static struct TALER_EXCHANGE_ManagementGetKeysHandle *mgkh;
+
+
+/**
+ * Active AML staff change requests.
+ */
+static struct AmlStaffRequest *asr_head;
+
+/**
+ * Active AML staff change requests.
+ */
+static struct AmlStaffRequest *asr_tail;
+
+/**
+ * Active partner add requests.
+ */
+static struct PartnerAddRequest *par_head;
+
+/**
+ * Active partner add requests.
+ */
+static struct PartnerAddRequest *par_tail;
+
+/**
+ * Active denomiantion revocation requests.
+ */
+static struct DenomRevocationRequest *drr_head;
+
+/**
+ * Active denomiantion revocation requests.
+ */
+static struct DenomRevocationRequest *drr_tail;
+
+/**
+ * Active signkey revocation requests.
+ */
+static struct SignkeyRevocationRequest *srr_head;
+
+/**
+ * Active signkey revocation requests.
+ */
+static struct SignkeyRevocationRequest *srr_tail;
+
+/**
+ * Active auditor add requests.
+ */
+static struct AuditorAddRequest *aar_head;
+
+/**
+ * Active auditor add requests.
+ */
+static struct AuditorAddRequest *aar_tail;
+
+/**
+ * Active auditor del requests.
+ */
+static struct AuditorDelRequest *adr_head;
+
+/**
+ * Active auditor del requests.
+ */
+static struct AuditorDelRequest *adr_tail;
+
+/**
+ * Active wire add requests.
+ */
+static struct WireAddRequest *war_head;
+
+/**
+ * Active wire add requests.
+ */
+static struct WireAddRequest *war_tail;
+
+/**
+ * Active wire del requests.
+ */
+static struct WireDelRequest *wdr_head;
+
+/**
+ * Active wire del requests.
+ */
+static struct WireDelRequest *wdr_tail;
+
+/**
+ * Active wire fee requests.
+ */
+static struct WireFeeRequest *wfr_head;
+
+/**
+ * Active wire fee requests.
+ */
+static struct WireFeeRequest *wfr_tail;
+
+/**
+ * Active global fee requests.
+ */
+static struct GlobalFeeRequest *gfr_head;
+
+/**
+ * Active global fee requests.
+ */
+static struct GlobalFeeRequest *gfr_tail;
+
+/**
+ * Active keys upload requests.
+ */
+static struct UploadKeysRequest *ukr_head;
+
+/**
+ * Active keys upload requests.
+ */
+static struct UploadKeysRequest *ukr_tail;
+
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_head;
+
+/**
+ * Active extensions upload requests.
+ */
+static struct UploadExtensionsRequest *uer_tail;
+
+/**
+ * Active drain profits requests.
+ */
+struct DrainProfitsRequest *dpr_head;
+
+/**
+ * Active drain profits requests.
+ */
+static struct DrainProfitsRequest *dpr_tail;
+
+
+/**
+ * Shutdown task. Invoked when the application is being terminated.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+  (void) cls;
+
+  {
+    struct AmlStaffRequest *asr;
+
+    while (NULL != (asr = asr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete AML staff update #%u\n",
+                  (unsigned int) asr->idx);
+      TALER_EXCHANGE_management_update_aml_officer_cancel (asr->h);
+      GNUNET_CONTAINER_DLL_remove (asr_head,
+                                   asr_tail,
+                                   asr);
+      GNUNET_free (asr);
+    }
+  }
+  {
+    struct PartnerAddRequest *par;
+
+    while (NULL != (par = par_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete partner add request #%u\n",
+                  (unsigned int) par->idx);
+      TALER_EXCHANGE_management_add_partner_cancel (par->h);
+      GNUNET_CONTAINER_DLL_remove (par_head,
+                                   par_tail,
+                                   par);
+      GNUNET_free (par);
+    }
+  }
+  {
+    struct DenomRevocationRequest *drr;
+
+    while (NULL != (drr = drr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete denomination revocation #%u\n",
+                  (unsigned int) drr->idx);
+      TALER_EXCHANGE_management_revoke_denomination_key_cancel (drr->h);
+      GNUNET_CONTAINER_DLL_remove (drr_head,
+                                   drr_tail,
+                                   drr);
+      GNUNET_free (drr);
+    }
+  }
+  {
+    struct SignkeyRevocationRequest *srr;
+
+    while (NULL != (srr = srr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete signkey revocation #%u\n",
+                  (unsigned int) srr->idx);
+      TALER_EXCHANGE_management_revoke_signing_key_cancel (srr->h);
+      GNUNET_CONTAINER_DLL_remove (srr_head,
+                                   srr_tail,
+                                   srr);
+      GNUNET_free (srr);
+    }
+  }
+
+  {
+    struct AuditorAddRequest *aar;
+
+    while (NULL != (aar = aar_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete auditor add #%u\n",
+                  (unsigned int) aar->idx);
+      TALER_EXCHANGE_management_enable_auditor_cancel (aar->h);
+      GNUNET_CONTAINER_DLL_remove (aar_head,
+                                   aar_tail,
+                                   aar);
+      GNUNET_free (aar);
+    }
+  }
+  {
+    struct AuditorDelRequest *adr;
+
+    while (NULL != (adr = adr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete auditor del #%u\n",
+                  (unsigned int) adr->idx);
+      TALER_EXCHANGE_management_disable_auditor_cancel (adr->h);
+      GNUNET_CONTAINER_DLL_remove (adr_head,
+                                   adr_tail,
+                                   adr);
+      GNUNET_free (adr);
+    }
+  }
+  {
+    struct WireAddRequest *war;
+
+    while (NULL != (war = war_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete wire add #%u\n",
+                  (unsigned int) war->idx);
+      TALER_EXCHANGE_management_enable_wire_cancel (war->h);
+      GNUNET_CONTAINER_DLL_remove (war_head,
+                                   war_tail,
+                                   war);
+      GNUNET_free (war);
+    }
+  }
+  {
+    struct WireDelRequest *wdr;
+
+    while (NULL != (wdr = wdr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete wire del #%u\n",
+                  (unsigned int) wdr->idx);
+      TALER_EXCHANGE_management_disable_wire_cancel (wdr->h);
+      GNUNET_CONTAINER_DLL_remove (wdr_head,
+                                   wdr_tail,
+                                   wdr);
+      GNUNET_free (wdr);
+    }
+  }
+  {
+    struct WireFeeRequest *wfr;
+
+    while (NULL != (wfr = wfr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete wire fee #%u\n",
+                  (unsigned int) wfr->idx);
+      TALER_EXCHANGE_management_set_wire_fees_cancel (wfr->h);
+      GNUNET_CONTAINER_DLL_remove (wfr_head,
+                                   wfr_tail,
+                                   wfr);
+      GNUNET_free (wfr);
+    }
+  }
+  {
+    struct GlobalFeeRequest *gfr;
+
+    while (NULL != (gfr = gfr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete global fee #%u\n",
+                  (unsigned int) gfr->idx);
+      TALER_EXCHANGE_management_set_global_fees_cancel (gfr->h);
+      GNUNET_CONTAINER_DLL_remove (gfr_head,
+                                   gfr_tail,
+                                   gfr);
+      GNUNET_free (gfr);
+    }
+  }
+  {
+    struct UploadKeysRequest *ukr;
+
+    while (NULL != (ukr = ukr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete key signature upload #%u\n",
+                  (unsigned int) ukr->idx);
+      TALER_EXCHANGE_post_management_keys_cancel (ukr->h);
+      GNUNET_CONTAINER_DLL_remove (ukr_head,
+                                   ukr_tail,
+                                   ukr);
+      GNUNET_free (ukr);
+    }
+  }
+  {
+    struct UploadExtensionsRequest *uer;
+
+    while (NULL != (uer = uer_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete extensions signature upload #%u\n",
+                  (unsigned int) uer->idx);
+      TALER_EXCHANGE_management_post_extensions_cancel (uer->h);
+      GNUNET_CONTAINER_DLL_remove (uer_head,
+                                   uer_tail,
+                                   uer);
+      GNUNET_free (uer);
+    }
+  }
+
+  {
+    struct DrainProfitsRequest *dpr;
+
+    while (NULL != (dpr = dpr_head))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Aborting incomplete drain profits request #%u\n",
+                  (unsigned int) dpr->idx);
+      TALER_EXCHANGE_management_drain_profits_cancel (dpr->h);
+      GNUNET_CONTAINER_DLL_remove (dpr_head,
+                                   dpr_tail,
+                                   dpr);
+      GNUNET_free (dpr);
+    }
+  }
+
+  if (NULL != out)
+  {
+    json_dumpf (out,
+                stdout,
+                JSON_INDENT (2));
+    json_decref (out);
+    out = NULL;
+  }
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Input not consumed!\n");
+    json_decref (in);
+    in = NULL;
+  }
+  if (NULL != nxt)
+  {
+    GNUNET_SCHEDULER_cancel (nxt);
+    nxt = NULL;
+  }
+  if (NULL != mgkh)
+  {
+    TALER_EXCHANGE_get_management_keys_cancel (mgkh);
+    mgkh = NULL;
+  }
+  if (NULL != ctx)
+  {
+    GNUNET_CURL_fini (ctx);
+    ctx = NULL;
+  }
+  if (NULL != rc)
+  {
+    GNUNET_CURL_gnunet_rc_destroy (rc);
+    rc = NULL;
+  }
+}
+
+
+/**
+ * Test if we should shut down because all tasks are done.
+ */
+static void
+test_shutdown (void)
+{
+  if ( (NULL == drr_head) &&
+       (NULL == par_head) &&
+       (NULL == asr_head) &&
+       (NULL == srr_head) &&
+       (NULL == aar_head) &&
+       (NULL == adr_head) &&
+       (NULL == war_head) &&
+       (NULL == wdr_head) &&
+       (NULL == wfr_head) &&
+       (NULL == gfr_head) &&
+       (NULL == ukr_head) &&
+       (NULL == uer_head) &&
+       (NULL == dpr_head) &&
+       (NULL == mgkh) &&
+       (NULL == nxt) )
+    GNUNET_SCHEDULER_shutdown ();
+}
+
+
+/**
+ * Function to continue processing the next command.
+ *
+ * @param cls must be a `char *const*` with the array of
+ *        command-line arguments to process next
+ */
+static void
+work (void *cls);
+
+
+/**
+ * Function to schedule job to process the next command.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+next (char *const *args)
+{
+  GNUNET_assert (NULL == nxt);
+  if (NULL == args[0])
+  {
+    test_shutdown ();
+    return;
+  }
+  nxt = GNUNET_SCHEDULER_add_now (&work,
+                                  (void *) args);
+}
+
+
+/**
+ * Add an operation to the #out JSON array for processing later.
+ *
+ * @param op_name name of the operation
+ * @param op_value values for the operation (consumed)
+ */
+static void
+output_operation (const char *op_name,
+                  json_t *op_value)
+{
+  json_t *action;
+
+  GNUNET_break (NULL != op_value);
+  if (NULL == out)
+  {
+    out = json_array ();
+    GNUNET_assert (NULL != out);
+  }
+  action = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("operation",
+                             op_name),
+    GNUNET_JSON_pack_object_steal ("arguments",
+                                   op_value));
+  GNUNET_assert (0 ==
+                 json_array_append_new (out,
+                                        action));
+}
+
+
+/**
+ * Information about a subroutine for an upload.
+ */
+struct UploadHandler
+{
+  /**
+   * Key to trigger this subroutine.
+   */
+  const char *key;
+
+  /**
+   * Function implementing an upload.
+   *
+   * @param exchange_url URL of the exchange
+   * @param idx index of the operation we are performing
+   * @param value arguments to drive the upload.
+   */
+  void (*cb)(const char *exchange_url,
+             size_t idx,
+             const json_t *value);
+
+};
+
+
+/**
+ * Load the offline key (if not yet done). Triggers shutdown on failure.
+ *
+ * @param do_create #GNUNET_YES if the key may be created
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_offline_key (int do_create)
+{
+  static bool done;
+  int ret;
+  char *fn;
+
+  if (done)
+    return GNUNET_OK;
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (kcfg,
+                                               "exchange-offline",
+                                               "MASTER_PRIV_FILE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "MASTER_PRIV_FILE");
+    test_shutdown ();
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_YES !=
+      GNUNET_DISK_file_test (fn))
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Exchange master private key `%s' does not exist yet, creating 
it!\n",
+                fn);
+  ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
+                                           do_create,
+                                           &master_priv.eddsa_priv);
+  if (GNUNET_SYSERR == ret)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to initialize master key from file `%s': %s\n",
+                fn,
+                "could not create file");
+    GNUNET_free (fn);
+    test_shutdown ();
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (fn);
+  GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv,
+                                      &master_pub.eddsa_pub);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Using master public key %s\n",
+              TALER_B2S (&master_pub));
+  done = true;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct DenomRevocationRequest`
+ * @param dr response data
+ */
+static void
+denom_revocation_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *dr)
+{
+  struct DenomRevocationRequest *drr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &dr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) drr->idx,
+                hr->http_status,
+                hr->hint,
+                TALER_JSON_get_error_hint (hr->reply));
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (drr_head,
+                               drr_tail,
+                               drr);
+  GNUNET_free (drr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload denomination revocation request data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_denom_revocation (const char *exchange_url,
+                         size_t idx,
+                         const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct TALER_DenominationHashP h_denom_pub;
+  struct DenomRevocationRequest *drr;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 &h_denom_pub),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input for denomination revocation: %s#%u at %u 
(skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  drr = GNUNET_new (struct DenomRevocationRequest);
+  drr->idx = idx;
+  drr->h =
+    TALER_EXCHANGE_management_revoke_denomination_key (ctx,
+                                                       exchange_url,
+                                                       &h_denom_pub,
+                                                       &master_sig,
+                                                       &denom_revocation_cb,
+                                                       drr);
+  GNUNET_CONTAINER_DLL_insert (drr_head,
+                               drr_tail,
+                               drr);
+}
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct SignkeyRevocationRequest`
+ * @param sr response data
+ */
+static void
+signkey_revocation_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *sr)
+{
+  struct SignkeyRevocationRequest *srr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &sr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) srr->idx,
+                hr->http_status,
+                hr->hint,
+                TALER_JSON_get_error_hint (hr->reply));
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (srr_head,
+                               srr_tail,
+                               srr);
+  GNUNET_free (srr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload signkey revocation request data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_signkey_revocation (const char *exchange_url,
+                           size_t idx,
+                           const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct SignkeyRevocationRequest *srr;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input for signkey revocation: %s#%u at %u 
(skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  srr = GNUNET_new (struct SignkeyRevocationRequest);
+  srr->idx = idx;
+  srr->h =
+    TALER_EXCHANGE_management_revoke_signing_key (ctx,
+                                                  exchange_url,
+                                                  &exchange_pub,
+                                                  &master_sig,
+                                                  &signkey_revocation_cb,
+                                                  srr);
+  GNUNET_CONTAINER_DLL_insert (srr_head,
+                               srr_tail,
+                               srr);
+}
+
+
+/**
+ * Function called with information about the post auditor add operation 
result.
+ *
+ * @param cls closure with a `struct AuditorAddRequest`
+ * @param mer response data
+ */
+static void
+auditor_add_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *mer)
+{
+  struct AuditorAddRequest *aar = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &mer->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) aar->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (aar_head,
+                               aar_tail,
+                               aar);
+  GNUNET_free (aar);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload auditor add data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_auditor_add (const char *exchange_url,
+                    size_t idx,
+                    const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  const char *auditor_url;
+  const char *auditor_name;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct TALER_AuditorPublicKeyP auditor_pub;
+  struct AuditorAddRequest *aar;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("auditor_url",
+                             &auditor_url),
+    GNUNET_JSON_spec_string ("auditor_name",
+                             &auditor_name),
+    GNUNET_JSON_spec_timestamp ("validity_start",
+                                &start_time),
+    GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+                                 &auditor_pub),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input for adding auditor: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  aar = GNUNET_new (struct AuditorAddRequest);
+  aar->idx = idx;
+  aar->h =
+    TALER_EXCHANGE_management_enable_auditor (ctx,
+                                              exchange_url,
+                                              &auditor_pub,
+                                              auditor_url,
+                                              auditor_name,
+                                              start_time,
+                                              &master_sig,
+                                              &auditor_add_cb,
+                                              aar);
+  GNUNET_CONTAINER_DLL_insert (aar_head,
+                               aar_tail,
+                               aar);
+}
+
+
+/**
+ * Function called with information about the post auditor del operation 
result.
+ *
+ * @param cls closure with a `struct AuditorDelRequest`
+ * @param mdr response data
+ */
+static void
+auditor_del_cb (void *cls,
+                const struct
+                TALER_EXCHANGE_ManagementAuditorDisableResponse *mdr)
+{
+  struct AuditorDelRequest *adr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) adr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (adr_head,
+                               adr_tail,
+                               adr);
+  GNUNET_free (adr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload auditor del data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_auditor_del (const char *exchange_url,
+                    size_t idx,
+                    const json_t *value)
+{
+  struct TALER_AuditorPublicKeyP auditor_pub;
+  struct TALER_MasterSignatureP master_sig;
+  struct GNUNET_TIME_Timestamp end_time;
+  struct AuditorDelRequest *adr;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+                                 &auditor_pub),
+    GNUNET_JSON_spec_timestamp ("validity_end",
+                                &end_time),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to disable auditor: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  adr = GNUNET_new (struct AuditorDelRequest);
+  adr->idx = idx;
+  adr->h =
+    TALER_EXCHANGE_management_disable_auditor (ctx,
+                                               exchange_url,
+                                               &auditor_pub,
+                                               end_time,
+                                               &master_sig,
+                                               &auditor_del_cb,
+                                               adr);
+  GNUNET_CONTAINER_DLL_insert (adr_head,
+                               adr_tail,
+                               adr);
+}
+
+
+/**
+ * Function called with information about the post wire add operation result.
+ *
+ * @param cls closure with a `struct WireAddRequest`
+ * @param wer response data
+ */
+static void
+wire_add_cb (void *cls,
+             const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer)
+{
+  struct WireAddRequest *war = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &wer->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) war->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (war_head,
+                               war_tail,
+                               war);
+  GNUNET_free (war);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload wire add data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_add (const char *exchange_url,
+                 size_t idx,
+                 const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig_add;
+  struct TALER_MasterSignatureP master_sig_wire;
+  const char *payto_uri;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct WireAddRequest *war;
+  const char *err_name;
+  const char *conversion_url = NULL;
+  const json_t *debit_restrictions;
+  const json_t *credit_restrictions;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("conversion_url",
+                               &conversion_url),
+      NULL),
+    GNUNET_JSON_spec_array_const ("debit_restrictions",
+                                  &debit_restrictions),
+    GNUNET_JSON_spec_array_const ("credit_restrictions",
+                                  &credit_restrictions),
+    GNUNET_JSON_spec_timestamp ("validity_start",
+                                &start_time),
+    GNUNET_JSON_spec_fixed_auto ("master_sig_add",
+                                 &master_sig_add),
+    GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
+                                 &master_sig_wire),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input for adding wire account: %s#%u at %u 
(skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  {
+    char *wire_method;
+
+    wire_method = TALER_payto_get_method (payto_uri);
+    if (NULL == wire_method)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "payto:// URI `%s' is malformed\n",
+                  payto_uri);
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+    GNUNET_free (wire_method);
+  }
+  {
+    char *msg = TALER_payto_validate (payto_uri);
+
+    if (NULL != msg)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "payto URI is malformed: %s\n",
+                  msg);
+      GNUNET_free (msg);
+      test_shutdown ();
+      global_ret = EXIT_INVALIDARGUMENT;
+      return;
+    }
+  }
+  war = GNUNET_new (struct WireAddRequest);
+  war->idx = idx;
+  war->h =
+    TALER_EXCHANGE_management_enable_wire (ctx,
+                                           exchange_url,
+                                           payto_uri,
+                                           conversion_url,
+                                           debit_restrictions,
+                                           credit_restrictions,
+                                           start_time,
+                                           &master_sig_add,
+                                           &master_sig_wire,
+                                           &wire_add_cb,
+                                           war);
+  GNUNET_CONTAINER_DLL_insert (war_head,
+                               war_tail,
+                               war);
+}
+
+
+/**
+ * Function called with information about the post wire del operation result.
+ *
+ * @param cls closure with a `struct WireDelRequest`
+ * @param wdres response data
+ */
+static void
+wire_del_cb (void *cls,
+             const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdres)
+{
+  struct WireDelRequest *wdr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &wdres->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) wdr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (wdr_head,
+                               wdr_tail,
+                               wdr);
+  GNUNET_free (wdr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload wire del data.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_del (const char *exchange_url,
+                 size_t idx,
+                 const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  const char *payto_uri;
+  struct GNUNET_TIME_Timestamp end_time;
+  struct WireDelRequest *wdr;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_timestamp ("validity_end",
+                                &end_time),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to disable wire account: %s#%u at %u 
(skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  wdr = GNUNET_new (struct WireDelRequest);
+  wdr->idx = idx;
+  wdr->h =
+    TALER_EXCHANGE_management_disable_wire (ctx,
+                                            exchange_url,
+                                            payto_uri,
+                                            end_time,
+                                            &master_sig,
+                                            &wire_del_cb,
+                                            wdr);
+  GNUNET_CONTAINER_DLL_insert (wdr_head,
+                               wdr_tail,
+                               wdr);
+}
+
+
+/**
+ * Function called with information about the post wire fee operation result.
+ *
+ * @param cls closure with a `struct WireFeeRequest`
+ * @param swr response data
+ */
+static void
+wire_fee_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *swr)
+{
+  struct WireFeeRequest *wfr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &swr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) wfr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (wfr_head,
+                               wfr_tail,
+                               wfr);
+  GNUNET_free (wfr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload wire fee.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_wire_fee (const char *exchange_url,
+                 size_t idx,
+                 const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  const char *wire_method;
+  struct WireFeeRequest *wfr;
+  const char *err_name;
+  unsigned int err_line;
+  struct TALER_WireFeeSet fees;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct GNUNET_TIME_Timestamp end_time;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("wire_method",
+                             &wire_method),
+    TALER_JSON_spec_amount ("wire_fee",
+                            currency,
+                            &fees.wire),
+    TALER_JSON_spec_amount ("closing_fee",
+                            currency,
+                            &fees.closing),
+    GNUNET_JSON_spec_timestamp ("start_time",
+                                &start_time),
+    GNUNET_JSON_spec_timestamp ("end_time",
+                                &end_time),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to set wire fee: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  wfr = GNUNET_new (struct WireFeeRequest);
+  wfr->idx = idx;
+  wfr->h =
+    TALER_EXCHANGE_management_set_wire_fees (ctx,
+                                             exchange_url,
+                                             wire_method,
+                                             start_time,
+                                             end_time,
+                                             &fees,
+                                             &master_sig,
+                                             &wire_fee_cb,
+                                             wfr);
+  GNUNET_CONTAINER_DLL_insert (wfr_head,
+                               wfr_tail,
+                               wfr);
+}
+
+
+/**
+ * Function called with information about the post global fee operation result.
+ *
+ * @param cls closure with a `struct WireFeeRequest`
+ * @param gr response data
+ */
+static void
+global_fee_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse *gr)
+{
+  struct GlobalFeeRequest *gfr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &gr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) gfr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (gfr_head,
+                               gfr_tail,
+                               gfr);
+  GNUNET_free (gfr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload global fee.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for denomination revocation
+ */
+static void
+upload_global_fee (const char *exchange_url,
+                   size_t idx,
+                   const json_t *value)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct GlobalFeeRequest *gfr;
+  const char *err_name;
+  unsigned int err_line;
+  struct TALER_GlobalFeeSet fees;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct GNUNET_TIME_Timestamp end_time;
+  struct GNUNET_TIME_Relative purse_timeout;
+  struct GNUNET_TIME_Relative history_expiration;
+  uint32_t purse_account_limit;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("history_fee",
+                            currency,
+                            &fees.history),
+    TALER_JSON_spec_amount ("account_fee",
+                            currency,
+                            &fees.account),
+    TALER_JSON_spec_amount ("purse_fee",
+                            currency,
+                            &fees.purse),
+    GNUNET_JSON_spec_relative_time ("purse_timeout",
+                                    &purse_timeout),
+    GNUNET_JSON_spec_relative_time ("history_expiration",
+                                    &history_expiration),
+    GNUNET_JSON_spec_uint32 ("purse_account_limit",
+                             &purse_account_limit),
+    GNUNET_JSON_spec_timestamp ("start_time",
+                                &start_time),
+    GNUNET_JSON_spec_timestamp ("end_time",
+                                &end_time),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to set wire fee: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  gfr = GNUNET_new (struct GlobalFeeRequest);
+  gfr->idx = idx;
+  gfr->h =
+    TALER_EXCHANGE_management_set_global_fees (ctx,
+                                               exchange_url,
+                                               start_time,
+                                               end_time,
+                                               &fees,
+                                               purse_timeout,
+                                               history_expiration,
+                                               purse_account_limit,
+                                               &master_sig,
+                                               &global_fee_cb,
+                                               gfr);
+  GNUNET_CONTAINER_DLL_insert (gfr_head,
+                               gfr_tail,
+                               gfr);
+}
+
+
+/**
+ * Function called with information about the drain profits operation.
+ *
+ * @param cls closure with a `struct DrainProfitsRequest`
+ * @param mdr response data
+ */
+static void
+drain_profits_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementDrainResponse *mdr)
+{
+  struct DrainProfitsRequest *dpr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &mdr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) dpr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (dpr_head,
+                               dpr_tail,
+                               dpr);
+  GNUNET_free (dpr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload drain profit action.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for drain profits
+ */
+static void
+upload_drain (const char *exchange_url,
+              size_t idx,
+              const json_t *value)
+{
+  struct TALER_WireTransferIdentifierRawP wtid;
+  struct TALER_MasterSignatureP master_sig;
+  const char *err_name;
+  unsigned int err_line;
+  struct TALER_Amount amount;
+  struct GNUNET_TIME_Timestamp date;
+  const char *payto_uri;
+  const char *account_section;
+  struct DrainProfitsRequest *dpr;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("wtid",
+                                 &wtid),
+    TALER_JSON_spec_amount ("amount",
+                            currency,
+                            &amount),
+    GNUNET_JSON_spec_timestamp ("date",
+                                &date),
+    GNUNET_JSON_spec_string ("account_section",
+                             &account_section),
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to drain profits: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  dpr = GNUNET_new (struct DrainProfitsRequest);
+  dpr->idx = idx;
+  dpr->h =
+    TALER_EXCHANGE_management_drain_profits (ctx,
+                                             exchange_url,
+                                             &wtid,
+                                             &amount,
+                                             date,
+                                             account_section,
+                                             payto_uri,
+                                             &master_sig,
+                                             &drain_profits_cb,
+                                             dpr);
+  GNUNET_CONTAINER_DLL_insert (dpr_head,
+                               dpr_tail,
+                               dpr);
+}
+
+
+/**
+ * Function called with information about the post upload keys operation 
result.
+ *
+ * @param cls closure with a `struct UploadKeysRequest`
+ * @param mr response data
+ */
+static void
+keys_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr)
+{
+  struct UploadKeysRequest *ukr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &mr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) ukr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (ukr_head,
+                               ukr_tail,
+                               ukr);
+  GNUNET_free (ukr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload (denomination and signing) key master signatures.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for POSTing keys
+ */
+static void
+upload_keys (const char *exchange_url,
+             size_t idx,
+             const json_t *value)
+{
+  struct TALER_EXCHANGE_ManagementPostKeysData pkd;
+  struct UploadKeysRequest *ukr;
+  const char *err_name;
+  unsigned int err_line;
+  const json_t *denom_sigs;
+  const json_t *signkey_sigs;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_array_const ("denom_sigs",
+                                  &denom_sigs),
+    GNUNET_JSON_spec_array_const ("signkey_sigs",
+                                  &signkey_sigs),
+    GNUNET_JSON_spec_end ()
+  };
+  bool ok = true;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to 'upload': %s#%u (skipping)\n",
+                err_name,
+                err_line);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  pkd.num_sign_sigs = json_array_size (signkey_sigs);
+  pkd.num_denom_sigs = json_array_size (denom_sigs);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Uploading %u denomination and %u signing key signatures\n",
+              pkd.num_denom_sigs,
+              pkd.num_sign_sigs);
+  pkd.sign_sigs = GNUNET_new_array (
+    pkd.num_sign_sigs,
+    struct TALER_EXCHANGE_SigningKeySignature);
+  pkd.denom_sigs = GNUNET_new_array (
+    pkd.num_denom_sigs,
+    struct TALER_EXCHANGE_DenominationKeySignature);
+  for (unsigned int i = 0; i<pkd.num_sign_sigs; i++)
+  {
+    struct TALER_EXCHANGE_SigningKeySignature *ss = &pkd.sign_sigs[i];
+    json_t *val = json_array_get (signkey_sigs,
+                                  i);
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                   &ss->exchange_pub),
+      GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                   &ss->master_sig),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (val,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for signkey validity: %s#%u at %u 
(aborting)\n",
+                  err_name,
+                  err_line,
+                  i);
+      json_dumpf (val,
+                  stderr,
+                  JSON_INDENT (2));
+      ok = false;
+    }
+  }
+  for (unsigned int i = 0; i<pkd.num_denom_sigs; i++)
+  {
+    struct TALER_EXCHANGE_DenominationKeySignature *ds = &pkd.denom_sigs[i];
+    json_t *val = json_array_get (denom_sigs,
+                                  i);
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                   &ds->h_denom_pub),
+      GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                   &ds->master_sig),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (val,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for denomination validity: %s#%u at %u 
(aborting)\n",
+                  err_name,
+                  err_line,
+                  i);
+      json_dumpf (val,
+                  stderr,
+                  JSON_INDENT (2));
+      ok = false;
+    }
+  }
+
+  if (ok)
+  {
+    ukr = GNUNET_new (struct UploadKeysRequest);
+    ukr->idx = idx;
+    ukr->h =
+      TALER_EXCHANGE_post_management_keys (ctx,
+                                           exchange_url,
+                                           &pkd,
+                                           &keys_cb,
+                                           ukr);
+    GNUNET_CONTAINER_DLL_insert (ukr_head,
+                                 ukr_tail,
+                                 ukr);
+  }
+  else
+  {
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+  }
+  GNUNET_free (pkd.sign_sigs);
+  GNUNET_free (pkd.denom_sigs);
+}
+
+
+/**
+ * Function called with information about the post upload extensions operation 
result.
+ *
+ * @param cls closure with a `struct UploadExtensionsRequest`
+ * @param er response data
+ */
+static void
+extensions_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *er)
+{
+  struct UploadExtensionsRequest *uer = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &er->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) uer->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (uer_head,
+                               uer_tail,
+                               uer);
+  GNUNET_free (uer);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload extension configuration
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for POSTing configurations of extensions
+ */
+static void
+upload_extensions (const char *exchange_url,
+                   size_t idx,
+                   const json_t *value)
+{
+  const json_t *extensions;
+  struct TALER_MasterSignatureP sig;
+  const char *err_name;
+  unsigned int err_line;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_object_const ("extensions",
+                                   &extensions),
+    GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+                                 &sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  /* 1. Parse the signed extensions */
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to set extensions: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+
+  /* 2. Verify the signature */
+  {
+    struct TALER_ExtensionManifestsHashP h_manifests;
+
+    if (GNUNET_OK !=
+        TALER_JSON_extensions_manifests_hash (extensions,
+                                              &h_manifests))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "couldn't hash extensions' manifests\n");
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+
+    if (GNUNET_OK !=
+        load_offline_key (GNUNET_NO))
+      return;
+
+    if (GNUNET_OK != TALER_exchange_offline_extension_manifests_hash_verify (
+          &h_manifests,
+          &master_pub,
+          &sig))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "invalid signature for extensions\n");
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+  }
+
+  /* 3. Upload the extensions */
+  {
+    struct TALER_EXCHANGE_ManagementPostExtensionsData ped = {
+      .extensions = extensions,
+      .extensions_sig = sig,
+    };
+    struct UploadExtensionsRequest *uer
+      = GNUNET_new (struct UploadExtensionsRequest);
+
+    uer->idx = idx;
+    uer->h = TALER_EXCHANGE_management_post_extensions (
+      ctx,
+      exchange_url,
+      &ped,
+      &extensions_cb,
+      uer);
+    GNUNET_CONTAINER_DLL_insert (uer_head,
+                                 uer_tail,
+                                 uer);
+  }
+}
+
+
+/**
+ * Function called with information about the add partner operation.
+ *
+ * @param cls closure with a `struct PartnerAddRequest`
+ * @param apr response data
+ */
+static void
+add_partner_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementAddPartnerResponse *apr)
+{
+  struct PartnerAddRequest *par = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &apr->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) par->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (par_head,
+                               par_tail,
+                               par);
+  GNUNET_free (par);
+  test_shutdown ();
+}
+
+
+/**
+ * Add partner action.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for add partner
+ */
+static void
+add_partner (const char *exchange_url,
+             size_t idx,
+             const json_t *value)
+{
+  struct TALER_MasterPublicKeyP partner_pub;
+  struct GNUNET_TIME_Timestamp start_date;
+  struct GNUNET_TIME_Timestamp end_date;
+  struct GNUNET_TIME_Relative wad_frequency;
+  struct TALER_Amount wad_fee;
+  const char *partner_base_url;
+  struct TALER_MasterSignatureP master_sig;
+  struct PartnerAddRequest *par;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("partner_pub",
+                                 &partner_pub),
+    TALER_JSON_spec_amount ("wad_fee",
+                            currency,
+                            &wad_fee),
+    GNUNET_JSON_spec_relative_time ("wad_frequency",
+                                    &wad_frequency),
+    GNUNET_JSON_spec_timestamp ("start_date",
+                                &start_date),
+    GNUNET_JSON_spec_timestamp ("end_date",
+                                &end_date),
+    GNUNET_JSON_spec_string ("partner_base_url",
+                             &partner_base_url),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *err_name;
+  unsigned int err_line;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to add partner: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  par = GNUNET_new (struct PartnerAddRequest);
+  par->idx = idx;
+  par->h =
+    TALER_EXCHANGE_management_add_partner (ctx,
+                                           exchange_url,
+                                           &partner_pub,
+                                           start_date,
+                                           end_date,
+                                           wad_frequency,
+                                           &wad_fee,
+                                           partner_base_url,
+                                           &master_sig,
+                                           &add_partner_cb,
+                                           par);
+  GNUNET_CONTAINER_DLL_insert (par_head,
+                               par_tail,
+                               par);
+}
+
+
+/**
+ * Function called with information about the AML officer update operation.
+ *
+ * @param cls closure with a `struct AmlStaffRequest`
+ * @param ar response data
+ */
+static void
+update_aml_officer_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *ar)
+{
+  struct AmlStaffRequest *asr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &ar->hr;
+
+  if (MHD_HTTP_NO_CONTENT != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Upload failed for command %u with status %u: %s (%s)\n",
+                (unsigned int) asr->idx,
+                hr->http_status,
+                TALER_ErrorCode_get_hint (hr->ec),
+                hr->hint);
+    global_ret = EXIT_FAILURE;
+  }
+  GNUNET_CONTAINER_DLL_remove (asr_head,
+                               asr_tail,
+                               asr);
+  GNUNET_free (asr);
+  test_shutdown ();
+}
+
+
+/**
+ * Upload AML staff action.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param idx index of the operation we are performing (for logging)
+ * @param value arguments for AML staff change
+ */
+static void
+update_aml_staff (const char *exchange_url,
+                  size_t idx,
+                  const json_t *value)
+{
+  struct TALER_AmlOfficerPublicKeyP officer_pub;
+  const char *officer_name;
+  struct GNUNET_TIME_Timestamp change_date;
+  bool is_active;
+  bool read_only;
+  struct TALER_MasterSignatureP master_sig;
+  struct AmlStaffRequest *asr;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("officer_pub",
+                                 &officer_pub),
+    GNUNET_JSON_spec_timestamp ("change_date",
+                                &change_date),
+    GNUNET_JSON_spec_bool ("is_active",
+                           &is_active),
+    GNUNET_JSON_spec_bool ("read_only",
+                           &read_only),
+    GNUNET_JSON_spec_string ("officer_name",
+                             &officer_name),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *err_name;
+  unsigned int err_line;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (value,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to AML staff update: %s#%u at %u (skipping)\n",
+                err_name,
+                err_line,
+                (unsigned int) idx);
+    json_dumpf (value,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return;
+  }
+  asr = GNUNET_new (struct AmlStaffRequest);
+  asr->idx = idx;
+  asr->h =
+    TALER_EXCHANGE_management_update_aml_officer (ctx,
+                                                  exchange_url,
+                                                  &officer_pub,
+                                                  officer_name,
+                                                  change_date,
+                                                  is_active,
+                                                  read_only,
+                                                  &master_sig,
+                                                  &update_aml_officer_cb,
+                                                  asr);
+  GNUNET_CONTAINER_DLL_insert (asr_head,
+                               asr_tail,
+                               asr);
+}
+
+
+/**
+ * Perform uploads based on the JSON in #out.
+ *
+ * @param exchange_url base URL of the exchange to use
+ */
+static void
+trigger_upload (const char *exchange_url)
+{
+  struct UploadHandler uhs[] = {
+    {
+      .key = OP_REVOKE_DENOMINATION,
+      .cb = &upload_denom_revocation
+    },
+    {
+      .key = OP_REVOKE_SIGNKEY,
+      .cb = &upload_signkey_revocation
+    },
+    {
+      .key = OP_ENABLE_AUDITOR,
+      .cb = &upload_auditor_add
+    },
+    {
+      .key = OP_DISABLE_AUDITOR,
+      .cb = &upload_auditor_del
+    },
+    {
+      .key = OP_ENABLE_WIRE,
+      .cb = &upload_wire_add
+    },
+    {
+      .key = OP_DISABLE_WIRE,
+      .cb = &upload_wire_del
+    },
+    {
+      .key = OP_SET_WIRE_FEE,
+      .cb = &upload_wire_fee
+    },
+    {
+      .key = OP_SET_GLOBAL_FEE,
+      .cb = &upload_global_fee
+    },
+    {
+      .key = OP_UPLOAD_SIGS,
+      .cb = &upload_keys
+    },
+    {
+      .key = OP_DRAIN_PROFITS,
+      .cb = &upload_drain
+    },
+    {
+      .key = OP_EXTENSIONS,
+      .cb = &upload_extensions
+    },
+    {
+      .key = OP_UPDATE_AML_STAFF,
+      .cb = &update_aml_staff
+    },
+    {
+      .key = OP_ADD_PARTNER,
+      .cb = &add_partner
+    },
+    /* array termination */
+    {
+      .key = NULL
+    }
+  };
+  size_t index;
+  json_t *obj;
+
+  json_array_foreach (out, index, obj) {
+    bool found = false;
+    const char *key;
+    const json_t *value;
+
+    key = json_string_value (json_object_get (obj, "operation"));
+    value = json_object_get (obj, "arguments");
+    if (NULL == key)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Malformed JSON input\n");
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+    /* block of code that uses key and value */
+    for (unsigned int i = 0; NULL != uhs[i].key; i++)
+    {
+      if (0 == strcasecmp (key,
+                           uhs[i].key))
+      {
+
+        found = true;
+        uhs[i].cb (exchange_url,
+                   index,
+                   value);
+        break;
+      }
+    }
+    if (! found)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Upload does not know how to handle `%s'\n",
+                  key);
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return;
+    }
+  }
+}
+
+
+/**
+ * Upload operation result (signatures) to exchange.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_upload (char *const *args)
+{
+  (void) args;
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, refusing upload\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if (NULL == out)
+  {
+    json_error_t err;
+
+    out = json_loadf (stdin,
+                      JSON_REJECT_DUPLICATES,
+                      &err);
+    if (NULL == out)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+                  err.text,
+                  err.line,
+                  err.source,
+                  err.position);
+      test_shutdown ();
+      global_ret = EXIT_FAILURE;
+      return;
+    }
+  }
+  if (! json_is_array (out))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Error: expected JSON array for `upload` command\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == CFG_exchange_url) &&
+       (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                               "exchange",
+                                               "BASE_URL",
+                                               &CFG_exchange_url)) )
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    global_ret = EXIT_NOTCONFIGURED;
+    test_shutdown ();
+    return;
+  }
+  trigger_upload (CFG_exchange_url);
+  json_decref (out);
+  out = NULL;
+}
+
+
+/**
+ * Revoke denomination key.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_revoke_denomination_key (char *const *args)
+{
+  struct TALER_DenominationHashP h_denom_pub;
+  struct TALER_MasterSignatureP master_sig;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, refusing revocation\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &h_denom_pub,
+                                       sizeof (h_denom_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify a denomination key with this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  TALER_exchange_offline_denomination_revoke_sign (&h_denom_pub,
+                                                   &master_priv,
+                                                   &master_sig);
+  output_operation (OP_REVOKE_DENOMINATION,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                                  &h_denom_pub),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 1);
+}
+
+
+/**
+ * Revoke signkey.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_revoke_signkey (char *const *args)
+{
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_MasterSignatureP master_sig;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, refusing revocation\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &exchange_pub,
+                                       sizeof (exchange_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify an exchange signing key with this 
subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  TALER_exchange_offline_signkey_revoke_sign (&exchange_pub,
+                                              &master_priv,
+                                              &master_sig);
+  output_operation (OP_REVOKE_SIGNKEY,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                                  &exchange_pub),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 1);
+}
+
+
+/**
+ * Add auditor.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the auditor's public key, args[1] the auditor's
+ *        API base URL, and args[2] the auditor's name.
+ */
+static void
+do_add_auditor (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct TALER_AuditorPublicKeyP auditor_pub;
+  struct GNUNET_TIME_Timestamp now;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not adding auditor\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &auditor_pub,
+                                       sizeof (auditor_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify an auditor public key as first argument for 
this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+
+  if ( (NULL == args[1]) ||
+       (0 != strncmp ("http",
+                      args[1],
+                      strlen ("http"))) ||
+       (NULL == args[2]) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify an auditor URI and auditor name as 2nd and 
3rd arguments to this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  now = GNUNET_TIME_timestamp_get ();
+  TALER_exchange_offline_auditor_add_sign (&auditor_pub,
+                                           args[1],
+                                           now,
+                                           &master_priv,
+                                           &master_sig);
+  output_operation (OP_ENABLE_AUDITOR,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("auditor_url",
+                                               args[1]),
+                      GNUNET_JSON_pack_string ("auditor_name",
+                                               args[2]),
+                      GNUNET_JSON_pack_timestamp ("validity_start",
+                                                  now),
+                      GNUNET_JSON_pack_data_auto ("auditor_pub",
+                                                  &auditor_pub),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 3);
+}
+
+
+/**
+ * Disable auditor account.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_del_auditor (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct TALER_AuditorPublicKeyP auditor_pub;
+  struct GNUNET_TIME_Timestamp now;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not deleting auditor 
account\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &auditor_pub,
+                                       sizeof (auditor_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify an auditor public key as first argument for 
this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  now = GNUNET_TIME_timestamp_get ();
+  TALER_exchange_offline_auditor_del_sign (&auditor_pub,
+                                           now,
+                                           &master_priv,
+                                           &master_sig);
+  output_operation (OP_DISABLE_AUDITOR,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_data_auto ("auditor_pub",
+                                                  &auditor_pub),
+                      GNUNET_JSON_pack_timestamp ("validity_end",
+                                                  now),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 1);
+}
+
+
+/**
+ * Parse account restriction.
+ *
+ * @param args the array of command-line arguments to process next
+ * @param[in,out] restrictions JSON array to update
+ * @return -1 on error, otherwise number of arguments from @a args that were 
used
+ */
+static int
+parse_restriction (char *const *args,
+                   json_t *restrictions)
+{
+  if (NULL == args[0])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Restriction TYPE argument missing\n");
+    return -1;
+  }
+  if (0 == strcmp (args[0],
+                   "deny"))
+  {
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     restrictions,
+                     GNUNET_JSON_PACK (
+                       GNUNET_JSON_pack_string ("type",
+                                                "deny"))));
+    return 1;
+  }
+  if (0 == strcmp (args[0],
+                   "regex"))
+  {
+    json_t *i18n;
+    json_error_t err;
+
+    if ( (NULL == args[1]) ||
+         (NULL == args[2]) ||
+         (NULL == args[3]) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Mandatory arguments for restriction of type `regex' missing 
(REGEX, HINT, HINT-I18 required)\n");
+      return -1;
+    }
+    {
+      regex_t ex;
+
+      if (0 != regcomp (&ex,
+                        args[1],
+                        REG_NOSUB | REG_EXTENDED))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Invalid regular expression `%s'\n",
+                    args[1]);
+        return -1;
+      }
+      regfree (&ex);
+    }
+
+    i18n = json_loads (args[3],
+                       JSON_REJECT_DUPLICATES,
+                       &err);
+    if (NULL == i18n)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid JSON for restriction of type `regex': `%s` at %d\n",
+                  args[3],
+                  err.position);
+      return -1;
+    }
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     restrictions,
+                     GNUNET_JSON_PACK (
+                       GNUNET_JSON_pack_string ("type",
+                                                "regex"),
+                       GNUNET_JSON_pack_string ("regex",
+                                                args[1]),
+                       GNUNET_JSON_pack_string ("human_hint",
+                                                args[2]),
+                       GNUNET_JSON_pack_object_steal ("human_hint_i18n",
+                                                      i18n)
+                       )));
+    return 4;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+              "Restriction TYPE `%s' unsupported\n",
+              args[0]);
+  return -1;
+}
+
+
+/**
+ * Add wire account.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_add_wire (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig_add;
+  struct TALER_MasterSignatureP master_sig_wire;
+  struct GNUNET_TIME_Timestamp now;
+  const char *conversion_url = NULL;
+  json_t *debit_restrictions;
+  json_t *credit_restrictions;
+  unsigned int num_args = 1;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not adding wire account\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if (NULL == args[0])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify a payto://-URI with this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  {
+    char *msg = TALER_payto_validate (args[0]);
+
+    if (NULL != msg)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "payto URI is malformed: %s\n",
+                  msg);
+      GNUNET_free (msg);
+      test_shutdown ();
+      global_ret = EXIT_INVALIDARGUMENT;
+      return;
+    }
+  }
+  now = GNUNET_TIME_timestamp_get ();
+  {
+    char *wire_method;
+
+    wire_method = TALER_payto_get_method (args[0]);
+    if (NULL == wire_method)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "payto:// URI `%s' is malformed\n",
+                  args[0]);
+      global_ret = EXIT_INVALIDARGUMENT;
+      test_shutdown ();
+      return;
+    }
+    GNUNET_free (wire_method);
+  }
+  debit_restrictions = json_array ();
+  GNUNET_assert (NULL != debit_restrictions);
+  credit_restrictions = json_array ();
+  GNUNET_assert (NULL != credit_restrictions);
+  while (NULL != args[num_args])
+  {
+    if (0 == strcmp (args[num_args],
+                     "conversion-url"))
+    {
+      num_args++;
+      conversion_url = args[num_args];
+      if (NULL == conversion_url)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "'conversion-url' requires an argument\n");
+        global_ret = EXIT_INVALIDARGUMENT;
+        test_shutdown ();
+        json_decref (debit_restrictions);
+        json_decref (credit_restrictions);
+        return;
+      }
+      num_args++;
+      continue;
+    }
+    if (0 == strcmp (args[num_args],
+                     "credit-restriction"))
+    {
+      int iret;
+
+      num_args++;
+      iret = parse_restriction (&args[num_args],
+                                credit_restrictions);
+      if (iret <= 0)
+      {
+        global_ret = EXIT_INVALIDARGUMENT;
+        test_shutdown ();
+        json_decref (debit_restrictions);
+        json_decref (credit_restrictions);
+        return;
+      }
+      num_args += iret;
+      continue;
+    }
+    if (0 == strcmp (args[num_args],
+                     "debit-restriction"))
+    {
+      int iret;
+
+      num_args++;
+      iret = parse_restriction (&args[num_args],
+                                debit_restrictions);
+      if (iret <= 0)
+      {
+        global_ret = EXIT_INVALIDARGUMENT;
+        test_shutdown ();
+        json_decref (debit_restrictions);
+        json_decref (credit_restrictions);
+        return;
+      }
+      num_args += iret;
+      continue;
+    }
+    break;
+  }
+  TALER_exchange_offline_wire_add_sign (args[0],
+                                        conversion_url,
+                                        debit_restrictions,
+                                        credit_restrictions,
+                                        now,
+                                        &master_priv,
+                                        &master_sig_add);
+  TALER_exchange_wire_signature_make (args[0],
+                                      conversion_url,
+                                      debit_restrictions,
+                                      credit_restrictions,
+                                      &master_priv,
+                                      &master_sig_wire);
+  output_operation (OP_ENABLE_WIRE,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("payto_uri",
+                                               args[0]),
+                      GNUNET_JSON_pack_array_steal ("debit_restrictions",
+                                                    debit_restrictions),
+                      GNUNET_JSON_pack_array_steal ("credit_restrictions",
+                                                    credit_restrictions),
+                      GNUNET_JSON_pack_allow_null (
+                        GNUNET_JSON_pack_string ("conversion_url",
+                                                 conversion_url)),
+                      GNUNET_JSON_pack_timestamp ("validity_start",
+                                                  now),
+                      GNUNET_JSON_pack_data_auto ("master_sig_add",
+                                                  &master_sig_add),
+                      GNUNET_JSON_pack_data_auto ("master_sig_wire",
+                                                  &master_sig_wire)));
+  next (args + num_args);
+}
+
+
+/**
+ * Disable wire account.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the hash of the denomination key to revoke
+ */
+static void
+do_del_wire (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig;
+  struct GNUNET_TIME_Timestamp now;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not deleting wire 
account\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if (NULL == args[0])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify a payto://-URI with this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  now = GNUNET_TIME_timestamp_get ();
+  TALER_exchange_offline_wire_del_sign (args[0],
+                                        now,
+                                        &master_priv,
+                                        &master_sig);
+  output_operation (OP_DISABLE_WIRE,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("payto_uri",
+                                               args[0]),
+                      GNUNET_JSON_pack_timestamp ("validity_end",
+                                                  now),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 1);
+}
+
+
+/**
+ * Set wire fees for the given year.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the year, args[1] the wire method, args[2] the wire 
fee and args[3]
+ *        the closing fee.
+ */
+static void
+do_set_wire_fee (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig;
+  char dummy;
+  unsigned int year;
+  struct TALER_WireFeeSet fees;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct GNUNET_TIME_Timestamp end_time;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not setting wire fee\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (NULL == args[1]) ||
+       (NULL == args[2]) ||
+       (NULL == args[3]) ||
+       ( (1 != sscanf (args[0],
+                       "%u%c",
+                       &year,
+                       &dummy)) &&
+         (0 != strcasecmp ("now",
+                           args[0])) ) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[2],
+                                &fees.wire)) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[3],
+                                &fees.closing)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must use YEAR, METHOD, WIRE-FEE, and CLOSING-FEE as 
arguments for this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (0 == strcasecmp ("now",
+                       args[0]))
+    year = GNUNET_TIME_get_current_year ();
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  start_time = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year));
+  end_time = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year + 1));
+
+  TALER_exchange_offline_wire_fee_sign (args[1],
+                                        start_time,
+                                        end_time,
+                                        &fees,
+                                        &master_priv,
+                                        &master_sig);
+  output_operation (OP_SET_WIRE_FEE,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("wire_method",
+                                               args[1]),
+                      GNUNET_JSON_pack_timestamp ("start_time",
+                                                  start_time),
+                      GNUNET_JSON_pack_timestamp ("end_time",
+                                                  end_time),
+                      TALER_JSON_pack_amount ("wire_fee",
+                                              &fees.wire),
+                      TALER_JSON_pack_amount ("closing_fee",
+                                              &fees.closing),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 4);
+}
+
+
+/**
+ * Set global fees for the given year.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the year, args[1] the history fee, args[2]
+ *        the account fee and args[3] the purse fee. These are followed by 
args[4] purse timeout,
+ *        args[5] history expiration. Last is args[6] the (free) purse account 
limit.
+ */
+static void
+do_set_global_fee (char *const *args)
+{
+  struct TALER_MasterSignatureP master_sig;
+  char dummy;
+  unsigned int year;
+  struct TALER_GlobalFeeSet fees;
+  struct GNUNET_TIME_Relative purse_timeout;
+  struct GNUNET_TIME_Relative history_expiration;
+  unsigned int purse_account_limit;
+  struct GNUNET_TIME_Timestamp start_time;
+  struct GNUNET_TIME_Timestamp end_time;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not setting global fee\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (NULL == args[1]) ||
+       (NULL == args[2]) ||
+       (NULL == args[3]) ||
+       (NULL == args[4]) ||
+       (NULL == args[5]) ||
+       (NULL == args[6]) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must use YEAR, HISTORY-FEE, ACCOUNT-FEE, PURSE-FEE, 
PURSE-TIMEOUT, HISTORY-EXPIRATION and PURSE-ACCOUNT-LIMIT as arguments for this 
subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (1 != sscanf (args[0],
+                     "%u%c",
+                     &year,
+                     &dummy)) &&
+       (0 != strcasecmp ("now",
+                         args[0])) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid YEAR given for 'global-fee' subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (GNUNET_OK !=
+        TALER_string_to_amount (args[1],
+                                &fees.history)) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[2],
+                                &fees.account)) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[3],
+                                &fees.purse)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid amount given for 'global-fee' subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (GNUNET_OK !=
+        GNUNET_STRINGS_fancy_time_to_relative (args[4],
+                                               &purse_timeout)) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_fancy_time_to_relative (args[5],
+                                               &history_expiration)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid delay given for 'global-fee' subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (1 != sscanf (args[6],
+                   "%u%c",
+                   &purse_account_limit,
+                   &dummy))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid purse account limit given for 'global-fee' 
subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (0 == strcasecmp ("now",
+                       args[0]))
+    year = GNUNET_TIME_get_current_year ();
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  start_time = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year));
+  end_time = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year + 1));
+
+  TALER_exchange_offline_global_fee_sign (start_time,
+                                          end_time,
+                                          &fees,
+                                          purse_timeout,
+                                          history_expiration,
+                                          (uint32_t) purse_account_limit,
+                                          &master_priv,
+                                          &master_sig);
+  output_operation (OP_SET_GLOBAL_FEE,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_timestamp ("start_time",
+                                                  start_time),
+                      GNUNET_JSON_pack_timestamp ("end_time",
+                                                  end_time),
+                      TALER_JSON_pack_amount ("history_fee",
+                                              &fees.history),
+                      TALER_JSON_pack_amount ("account_fee",
+                                              &fees.account),
+                      TALER_JSON_pack_amount ("purse_fee",
+                                              &fees.purse),
+                      GNUNET_JSON_pack_time_rel ("purse_timeout",
+                                                 purse_timeout),
+                      GNUNET_JSON_pack_time_rel ("history_expiration",
+                                                 history_expiration),
+                      GNUNET_JSON_pack_uint64 ("purse_account_limit",
+                                               (uint32_t) purse_account_limit),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 7);
+}
+
+
+/**
+ * Drain profits from exchange's escrow account to
+ * regular exchange account.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the amount,
+ *        args[1] must be the section of the escrow account to drain
+ *        args[2] must be the payto://-URI of the target account
+ */
+static void
+do_drain (char *const *args)
+{
+  struct TALER_WireTransferIdentifierRawP wtid;
+  struct GNUNET_TIME_Timestamp date;
+  struct TALER_Amount amount;
+  const char *account_section;
+  const char *payto_uri;
+  struct TALER_MasterSignatureP master_sig;
+  char *err;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, refusing drain\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (NULL == args[1]) ||
+       (NULL == args[2]) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[0],
+                                &amount)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Drain requires an amount, section name and target 
payto://-URI as arguments\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (NULL == args[1]) ||
+       (NULL == args[2]) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Drain requires an amount, section name and target 
payto://-URI as arguments\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_string_to_amount (args[0],
+                              &amount))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid amount `%s' specified for drain\n",
+                args[0]);
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  account_section = args[1];
+  payto_uri = args[2];
+  err = TALER_payto_validate (payto_uri);
+  if (NULL != err)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid payto://-URI `%s' specified for drain: %s\n",
+                payto_uri,
+                err);
+    GNUNET_free (err);
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &wtid,
+                              sizeof (wtid));
+  date = GNUNET_TIME_timestamp_get ();
+  TALER_exchange_offline_profit_drain_sign (&wtid,
+                                            date,
+                                            &amount,
+                                            account_section,
+                                            payto_uri,
+                                            &master_priv,
+                                            &master_sig);
+  output_operation (OP_DRAIN_PROFITS,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_data_auto ("wtid",
+                                                  &wtid),
+                      GNUNET_JSON_pack_string ("account_section",
+                                               account_section),
+                      GNUNET_JSON_pack_string ("payto_uri",
+                                               payto_uri),
+                      TALER_JSON_pack_amount ("amount",
+                                              &amount),
+                      GNUNET_JSON_pack_timestamp ("date",
+                                                  date),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 3);
+}
+
+
+/**
+ * Add partner.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the partner's master public key, args[1] the 
partner's
+ *        API base URL, args[2] the wad fee, args[3] the wad frequency, and
+ *        args[4] the year (including possibly 'now')
+ */
+static void
+do_add_partner (char *const *args)
+{
+  struct TALER_MasterPublicKeyP partner_pub;
+  struct GNUNET_TIME_Timestamp start_date;
+  struct GNUNET_TIME_Timestamp end_date;
+  struct GNUNET_TIME_Relative wad_frequency;
+  struct TALER_Amount wad_fee;
+  const char *partner_base_url;
+  struct TALER_MasterSignatureP master_sig;
+  char dummy;
+  unsigned int year;
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not adding partner\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &partner_pub,
+                                       sizeof (partner_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify the partner master public key as first 
argument for this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (NULL == args[1]) ||
+       (0 != strncmp ("http",
+                      args[1],
+                      strlen ("http"))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify the partner's base URL as the 2nd argument 
to this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  partner_base_url = args[1];
+  if ( (NULL == args[2]) ||
+       (GNUNET_OK !=
+        TALER_string_to_amount (args[2],
+                                &wad_fee)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid amount `%s' specified for wad fee of partner\n",
+                args[2]);
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (NULL == args[3]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_fancy_time_to_relative (args[3],
+                                               &wad_frequency)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid wad frequency `%s' specified for add partner\n",
+                args[3]);
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if ( (NULL == args[4]) ||
+       ( (1 != sscanf (args[4],
+                       "%u%c",
+                       &year,
+                       &dummy)) &&
+         (0 != strcasecmp ("now",
+                           args[4])) ) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid year `%s' specified for add partner\n",
+                args[4]);
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (0 == strcasecmp ("now",
+                       args[4]))
+    year = GNUNET_TIME_get_current_year ();
+  start_date = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year));
+  end_date = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_year_to_time (year + 1));
+
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  TALER_exchange_offline_partner_details_sign (&partner_pub,
+                                               start_date,
+                                               end_date,
+                                               wad_frequency,
+                                               &wad_fee,
+                                               partner_base_url,
+                                               &master_priv,
+                                               &master_sig);
+  output_operation (OP_ADD_PARTNER,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("partner_base_url",
+                                               partner_base_url),
+                      GNUNET_JSON_pack_time_rel ("wad_frequency",
+                                                 wad_frequency),
+                      GNUNET_JSON_pack_timestamp ("start_date",
+                                                  start_date),
+                      GNUNET_JSON_pack_timestamp ("end_date",
+                                                  end_date),
+                      GNUNET_JSON_pack_data_auto ("partner_pub",
+                                                  &partner_pub),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + 5);
+}
+
+
+/**
+ * Enable or disable AML staff.
+ *
+ * @param is_active true to enable, false to disable
+ * @param args the array of command-line arguments to process next; args[0] 
must be the AML staff's public key, args[1] the AML staff's legal name, and if 
@a is_active then args[2] rw (read write) or ro (read only)
+ */
+static void
+do_set_aml_staff (bool is_active,
+                  char *const *args)
+{
+  struct TALER_AmlOfficerPublicKeyP officer_pub;
+  const char *officer_name;
+  bool read_only;
+  struct TALER_MasterSignatureP master_sig;
+  struct GNUNET_TIME_Timestamp now
+    = GNUNET_TIME_timestamp_get ();
+
+  if (NULL != in)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Downloaded data was not consumed, not updating AML staff 
status\n");
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  if ( (NULL == args[0]) ||
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &officer_pub,
+                                       sizeof (officer_pub))) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify the AML officer's public key as first 
argument for this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  if (NULL == args[1])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must specify the officer's legal name as the 2nd argument 
to this subcommand\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+  officer_name = args[1];
+  if (is_active)
+  {
+    if ( (NULL == args[2]) ||
+         ( (0 != strcmp (args[2],
+                         "ro")) &&
+           (0 != strcmp (args[2],
+                         "rw")) ) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "You must specify 'ro' or 'rw' (and not `%s') for the access 
level\n",
+                  args[2]);
+      test_shutdown ();
+      global_ret = EXIT_INVALIDARGUMENT;
+      return;
+    }
+    read_only = (0 == strcmp (args[2],
+                              "ro"));
+  }
+  else
+  {
+    read_only = true;
+  }
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  TALER_exchange_offline_aml_officer_status_sign (&officer_pub,
+                                                  officer_name,
+                                                  now,
+                                                  is_active,
+                                                  read_only,
+                                                  &master_priv,
+                                                  &master_sig);
+  output_operation (OP_UPDATE_AML_STAFF,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_string ("officer_name",
+                                               officer_name),
+                      GNUNET_JSON_pack_timestamp ("change_date",
+                                                  now),
+                      GNUNET_JSON_pack_bool ("is_active",
+                                             is_active),
+                      GNUNET_JSON_pack_bool ("read_only",
+                                             read_only),
+                      GNUNET_JSON_pack_data_auto ("officer_pub",
+                                                  &officer_pub),
+                      GNUNET_JSON_pack_data_auto ("master_sig",
+                                                  &master_sig)));
+  next (args + (is_active ? 3 : 2));
+}
+
+
+/**
+ * Disable AML staff.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the AML staff's public key, args[1] the AML staff's 
legal name, args[2] rw (read write) or ro (read only)
+ */
+static void
+disable_aml_staff (char *const *args)
+{
+  do_set_aml_staff (false,
+                    args);
+}
+
+
+/**
+ * Enable AML staff.
+ *
+ * @param args the array of command-line arguments to process next;
+ *        args[0] must be the AML staff's public key, args[1] the AML staff's 
legal name, args[2] rw (read write) or ro (read only)
+ */
+static void
+enable_aml_staff (char *const *args)
+{
+  do_set_aml_staff (true,
+                    args);
+}
+
+
+/**
+ * Function called with information about future keys.  Dumps the JSON output
+ * (on success), either into an internal buffer or to stdout (depending on
+ * whether there are subsequent commands).
+ *
+ * @param cls closure with the `char **` remaining args
+ * @param mgr response data
+ */
+static void
+download_cb (void *cls,
+             const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr)
+{
+  char *const *args = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &mgr->hr;
+
+  mgkh = NULL;
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  default:
+    if (0 != hr->http_status)
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to download keys from `%s': %s (HTTP status: 
%u/%u)\n",
+                  CFG_exchange_url,
+                  hr->hint,
+                  hr->http_status,
+                  (unsigned int) hr->ec);
+    else
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to download keys from `%s' (no HTTP response)\n",
+                  CFG_exchange_url);
+    test_shutdown ();
+    global_ret = EXIT_FAILURE;
+    return;
+  }
+  in = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("operation",
+                             OP_INPUT_KEYS),
+    GNUNET_JSON_pack_object_incref ("arguments",
+                                    (json_t *) hr->reply));
+  if (NULL == args[0])
+  {
+    json_dumpf (in,
+                stdout,
+                JSON_INDENT (2));
+    json_decref (in);
+    in = NULL;
+  }
+  next (args);
+}
+
+
+/**
+ * Download future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_download (char *const *args)
+{
+  if ( (NULL == CFG_exchange_url) &&
+       (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                               "exchange",
+                                               "BASE_URL",
+                                               &CFG_exchange_url)) )
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    test_shutdown ();
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+  mgkh = TALER_EXCHANGE_get_management_keys (ctx,
+                                             CFG_exchange_url,
+                                             &download_cb,
+                                             (void *) args);
+}
+
+
+/**
+ * Check that the security module keys are the same as before.  If we had no
+ * keys in store before, remember them (Trust On First Use).
+ *
+ * @param secmset security module keys
+ * @return #GNUNET_OK if keys match with what we have in store
+ *         #GNUNET_NO if we had nothing in store but now do
+ *         #GNUNET_SYSERR if keys changed from what we remember or other error
+ */
+static enum GNUNET_GenericReturnValue
+tofu_check (const struct TALER_SecurityModulePublicKeySetP *secmset)
+{
+  char *fn;
+  struct TALER_SecurityModulePublicKeySetP oldset;
+  ssize_t ret;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (kcfg,
+                                               "exchange-offline",
+                                               "SECM_TOFU_FILE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "SECM_TOFU_FILE");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK ==
+      GNUNET_DISK_file_test (fn))
+  {
+    ret = GNUNET_DISK_fn_read (fn,
+                               &oldset,
+                               sizeof (oldset));
+    if (GNUNET_SYSERR != ret)
+    {
+      if (ret != sizeof (oldset))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "File `%s' corrupt\n",
+                    fn);
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+      /* TOFU check */
+      if (0 != memcmp (&oldset,
+                       secmset,
+                       sizeof (*secmset)))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Fatal: security module keys changed (file `%s')!\n",
+                    fn);
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+      GNUNET_free (fn);
+      return GNUNET_OK;
+    }
+  }
+
+  {
+    char *key;
+
+    /* check against SECMOD-keys pinned in configuration */
+    if (GNUNET_OK ==
+        GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                               "exchange-offline",
+                                               "SECM_ESIGN_PUBKEY",
+                                               &key))
+    {
+      struct TALER_SecurityModulePublicKeyP k;
+
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_string_to_data (key,
+                                         strlen (key),
+                                         &k,
+                                         sizeof (k)))
+      {
+        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                   "exchange-offline",
+                                   "SECM_ESIGN_PUBKEY",
+                                   "key malformed");
+        GNUNET_free (key);
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+      GNUNET_free (key);
+      if (0 !=
+          GNUNET_memcmp (&k,
+                         &secmset->eddsa))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "ESIGN security module key does not match 
SECM_ESIGN_PUBKEY in configuration\n");
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+    }
+    if (GNUNET_OK ==
+        GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                               "exchange-offline",
+                                               "SECM_DENOM_PUBKEY",
+                                               &key))
+    {
+      struct TALER_SecurityModulePublicKeyP k;
+
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_string_to_data (key,
+                                         strlen (key),
+                                         &k,
+                                         sizeof (k)))
+      {
+        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                   "exchange-offline",
+                                   "SECM_DENOM_PUBKEY",
+                                   "key malformed");
+        GNUNET_free (key);
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+      GNUNET_free (key);
+      if (0 !=
+          GNUNET_memcmp (&k,
+                         &secmset->rsa))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "DENOM security module key does not match 
SECM_DENOM_PUBKEY in configuration\n");
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+    }
+    if (GNUNET_OK ==
+        GNUNET_CONFIGURATION_get_value_string (kcfg,
+                                               "exchange-offline",
+                                               "SECM_DENOM_CS_PUBKEY",
+                                               &key))
+    {
+      struct TALER_SecurityModulePublicKeyP k;
+
+      if (GNUNET_OK !=
+          GNUNET_STRINGS_string_to_data (key,
+                                         strlen (key),
+                                         &k,
+                                         sizeof (k)))
+      {
+        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                   "exchange-offline",
+                                   "SECM_DENOM_CS_PUBKEY",
+                                   "key malformed");
+        GNUNET_free (key);
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+      GNUNET_free (key);
+      if (0 !=
+          GNUNET_memcmp (&k,
+                         &secmset->cs))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "DENOM security module key does not match 
SECM_DENOM_CS_PUBKEY in configuration\n");
+        GNUNET_free (fn);
+        return GNUNET_SYSERR;
+      }
+    }
+  }
+  if (GNUNET_OK !=
+      GNUNET_DISK_directory_create_for_file (fn))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed create directory to store key material in file `%s'\n",
+                fn);
+    GNUNET_free (fn);
+    return GNUNET_SYSERR;
+  }
+  /* persist keys for future runs */
+  if (GNUNET_OK !=
+      GNUNET_DISK_fn_write (fn,
+                            secmset,
+                            sizeof (oldset),
+                            GNUNET_DISK_PERM_USER_READ))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to store key material in file `%s'\n",
+                fn);
+    GNUNET_free (fn);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (fn);
+  return GNUNET_NO;
+}
+
+
+/**
+ * Output @a signkeys for human consumption.
+ *
+ * @param secm_pub security module public key used to sign the denominations
+ * @param signkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+show_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
+               const json_t *signkeys)
+{
+  size_t index;
+  json_t *value;
+
+  json_array_foreach (signkeys, index, value) {
+    const char *err_name;
+    unsigned int err_line;
+    struct TALER_ExchangePublicKeyP exchange_pub;
+    struct TALER_SecurityModuleSignatureP secm_sig;
+    struct GNUNET_TIME_Timestamp start_time;
+    struct GNUNET_TIME_Timestamp sign_end;
+    struct GNUNET_TIME_Timestamp legal_end;
+    struct GNUNET_TIME_Relative duration;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_timestamp ("stamp_start",
+                                  &start_time),
+      GNUNET_JSON_spec_timestamp ("stamp_expire",
+                                  &sign_end),
+      GNUNET_JSON_spec_timestamp ("stamp_end",
+                                  &legal_end),
+      GNUNET_JSON_spec_fixed_auto ("key",
+                                   &exchange_pub),
+      GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+                                   &secm_sig),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for signing key to 'show': %s#%u at %u 
(skipping)\n",
+                  err_name,
+                  err_line,
+                  (unsigned int) index);
+      json_dumpf (value,
+                  stderr,
+                  JSON_INDENT (2));
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+    duration = GNUNET_TIME_absolute_get_difference (start_time.abs_time,
+                                                    sign_end.abs_time);
+    if (GNUNET_OK !=
+        TALER_exchange_secmod_eddsa_verify (&exchange_pub,
+                                            start_time,
+                                            duration,
+                                            secm_pub,
+                                            &secm_sig))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid security module signature for signing key %s 
(aborting)\n",
+                  TALER_B2S (&exchange_pub));
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+    {
+      char *legal_end_s;
+
+      legal_end_s = GNUNET_strdup (
+        GNUNET_TIME_timestamp2s (legal_end));
+      printf ("EXCHANGE-KEY %s starting at %s (used for: %s, legal end: %s)\n",
+              TALER_B2S (&exchange_pub),
+              GNUNET_TIME_timestamp2s (start_time),
+              GNUNET_TIME_relative2s (duration,
+                                      false),
+              legal_end_s);
+      GNUNET_free (legal_end_s);
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Output @a denomkeys for human consumption.
+ *
+ * @param secm_pub_rsa security module public key used to sign the RSA 
denominations
+ * @param secm_pub_cs security module public key used to sign the CS 
denominations
+ * @param denomkeys keys to output
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+show_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
+                const struct TALER_SecurityModulePublicKeyP *secm_pub_cs,
+                const json_t *denomkeys)
+{
+  size_t index;
+  json_t *value;
+
+  json_array_foreach (denomkeys, index, value) {
+    const char *err_name;
+    unsigned int err_line;
+    const char *section_name;
+    struct TALER_DenominationPublicKey denom_pub;
+    struct GNUNET_TIME_Timestamp stamp_start;
+    struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+    struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+    struct GNUNET_TIME_Timestamp stamp_expire_legal;
+    struct TALER_Amount coin_value;
+    struct TALER_DenomFeeSet fees;
+    struct TALER_SecurityModuleSignatureP secm_sig;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("section_name",
+                               &section_name),
+      TALER_JSON_spec_denom_pub ("denom_pub",
+                                 &denom_pub),
+      TALER_JSON_spec_amount ("value",
+                              currency,
+                              &coin_value),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
+      GNUNET_JSON_spec_timestamp ("stamp_start",
+                                  &stamp_start),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+                                  &stamp_expire_withdraw),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+                                  &stamp_expire_deposit),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+                                  &stamp_expire_legal),
+      GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+                                   &secm_sig),
+      GNUNET_JSON_spec_end ()
+    };
+    struct GNUNET_TIME_Relative duration;
+    struct TALER_DenominationHashP h_denom_pub;
+    enum GNUNET_GenericReturnValue ok;
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for denomination key to 'show': %s#%u at %u 
(skipping)\n",
+                  err_name,
+                  err_line,
+                  (unsigned int) index);
+      json_dumpf (value,
+                  stderr,
+                  JSON_INDENT (2));
+      GNUNET_JSON_parse_free (spec);
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+    duration = GNUNET_TIME_absolute_get_difference (
+      stamp_start.abs_time,
+      stamp_expire_withdraw.abs_time);
+    TALER_denom_pub_hash (&denom_pub,
+                          &h_denom_pub);
+    switch (denom_pub.cipher)
+    {
+    case TALER_DENOMINATION_RSA:
+      {
+        struct TALER_RsaPubHashP h_rsa;
+
+        TALER_rsa_pub_hash (denom_pub.details.rsa_public_key,
+                            &h_rsa);
+        ok = TALER_exchange_secmod_rsa_verify (&h_rsa,
+                                               section_name,
+                                               stamp_start,
+                                               duration,
+                                               secm_pub_rsa,
+                                               &secm_sig);
+      }
+      break;
+    case TALER_DENOMINATION_CS:
+      {
+        struct TALER_CsPubHashP h_cs;
+
+        TALER_cs_pub_hash (&denom_pub.details.cs_public_key,
+                           &h_cs);
+        ok = TALER_exchange_secmod_cs_verify (&h_cs,
+                                              section_name,
+                                              stamp_start,
+                                              duration,
+                                              secm_pub_cs,
+                                              &secm_sig);
+      }
+      break;
+    default:
+      GNUNET_break (0);
+      ok = GNUNET_SYSERR;
+      break;
+    }
+    if (GNUNET_OK != ok)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid security module signature for denomination key %s 
(aborting)\n",
+                  GNUNET_h2s (&h_denom_pub.hash));
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+
+    {
+      char *withdraw_fee_s;
+      char *deposit_fee_s;
+      char *refresh_fee_s;
+      char *refund_fee_s;
+      char *deposit_s;
+      char *legal_s;
+
+      withdraw_fee_s = TALER_amount_to_string (&fees.withdraw);
+      deposit_fee_s = TALER_amount_to_string (&fees.deposit);
+      refresh_fee_s = TALER_amount_to_string (&fees.refresh);
+      refund_fee_s = TALER_amount_to_string (&fees.refund);
+      deposit_s = GNUNET_strdup (
+        GNUNET_TIME_timestamp2s (stamp_expire_deposit));
+      legal_s = GNUNET_strdup (
+        GNUNET_TIME_timestamp2s (stamp_expire_legal));
+
+      printf (
+        "DENOMINATION-KEY(%s) %s of value %s starting at %s "
+        "(used for: %s, deposit until: %s legal end: %s) with fees 
%s/%s/%s/%s\n",
+        section_name,
+        TALER_B2S (&h_denom_pub),
+        TALER_amount2s (&coin_value),
+        GNUNET_TIME_timestamp2s (stamp_start),
+        GNUNET_TIME_relative2s (duration,
+                                false),
+        deposit_s,
+        legal_s,
+        withdraw_fee_s,
+        deposit_fee_s,
+        refresh_fee_s,
+        refund_fee_s);
+      GNUNET_free (withdraw_fee_s);
+      GNUNET_free (deposit_fee_s);
+      GNUNET_free (refresh_fee_s);
+      GNUNET_free (refund_fee_s);
+      GNUNET_free (deposit_s);
+      GNUNET_free (legal_s);
+    }
+
+    GNUNET_JSON_parse_free (spec);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse the input of exchange keys for the 'show' and 'sign' commands.
+ *
+ * @param command_name name of the command, for logging
+ * @return NULL on error, otherwise the keys details to be free'd by caller
+ */
+static json_t *
+parse_keys_input (const char *command_name)
+{
+  const char *op_str;
+  json_t *keys;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("arguments",
+                           &keys),
+    GNUNET_JSON_spec_string ("operation",
+                             &op_str),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *err_name;
+  unsigned int err_line;
+
+  if (NULL == in)
+  {
+    json_error_t err;
+
+    in = json_loadf (stdin,
+                     JSON_REJECT_DUPLICATES,
+                     &err);
+    if (NULL == in)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
+                  err.text,
+                  err.line,
+                  err.source,
+                  err.position);
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return NULL;
+    }
+  }
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (in,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to '%s': %s#%u (skipping)\n",
+                command_name,
+                err_name,
+                err_line);
+    json_dumpf (in,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    return NULL;
+  }
+  if (0 != strcmp (op_str,
+                   OP_INPUT_KEYS))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to '%s' : operation is `%s', expected `%s'\n",
+                command_name,
+                op_str,
+                OP_INPUT_KEYS);
+    GNUNET_JSON_parse_free (spec);
+    return NULL;
+  }
+  json_decref (in);
+  in = NULL;
+  return keys;
+}
+
+
+/**
+ * Show future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_show (char *const *args)
+{
+  json_t *keys;
+  const char *err_name;
+  unsigned int err_line;
+  const json_t *denomkeys;
+  const json_t *signkeys;
+  struct TALER_MasterPublicKeyP mpub;
+  struct TALER_SecurityModulePublicKeySetP secmset;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_array_const ("future_denoms",
+                                  &denomkeys),
+    GNUNET_JSON_spec_array_const ("future_signkeys",
+                                  &signkeys),
+    GNUNET_JSON_spec_fixed_auto ("master_pub",
+                                 &mpub),
+    GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+                                 &secmset.rsa),
+    GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+                                 &secmset.cs),
+    GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+                                 &secmset.eddsa),
+    GNUNET_JSON_spec_end ()
+  };
+
+  keys = parse_keys_input ("show");
+  if (NULL == keys)
+    return;
+
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+    return;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (keys,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to 'show': %s #%u (skipping)\n",
+                err_name,
+                err_line);
+    json_dumpf (in,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  if (0 !=
+      GNUNET_memcmp (&master_pub,
+                     &mpub))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fatal: exchange uses different master key!\n");
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  if (GNUNET_SYSERR ==
+      tofu_check (&secmset))
+  {
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  if ( (GNUNET_OK !=
+        show_signkeys (&secmset.eddsa,
+                       signkeys)) ||
+       (GNUNET_OK !=
+        show_denomkeys (&secmset.rsa,
+                        &secmset.cs,
+                        denomkeys)) )
+  {
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  json_decref (keys);
+  next (args);
+}
+
+
+/**
+ * Sign @a signkeys with offline key.
+ *
+ * @param secm_pub security module public key used to sign the denominations
+ * @param signkeys keys to output
+ * @param[in,out] result array where to output the signatures
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+sign_signkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub,
+               const json_t *signkeys,
+               json_t *result)
+{
+  size_t index;
+  json_t *value;
+
+  json_array_foreach (signkeys, index, value) {
+    const char *err_name;
+    unsigned int err_line;
+    struct TALER_ExchangePublicKeyP exchange_pub;
+    struct TALER_SecurityModuleSignatureP secm_sig;
+    struct GNUNET_TIME_Timestamp start_time;
+    struct GNUNET_TIME_Timestamp sign_end;
+    struct GNUNET_TIME_Timestamp legal_end;
+    struct GNUNET_TIME_Relative duration;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_timestamp ("stamp_start",
+                                  &start_time),
+      GNUNET_JSON_spec_timestamp ("stamp_expire",
+                                  &sign_end),
+      GNUNET_JSON_spec_timestamp ("stamp_end",
+                                  &legal_end),
+      GNUNET_JSON_spec_fixed_auto ("key",
+                                   &exchange_pub),
+      GNUNET_JSON_spec_fixed_auto ("signkey_secmod_sig",
+                                   &secm_sig),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for signing key to 'show': %s #%u at %u 
(skipping)\n",
+                  err_name,
+                  err_line,
+                  (unsigned int) index);
+      json_dumpf (value,
+                  stderr,
+                  JSON_INDENT (2));
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+
+    duration = GNUNET_TIME_absolute_get_difference (start_time.abs_time,
+                                                    sign_end.abs_time);
+    if (GNUNET_OK !=
+        TALER_exchange_secmod_eddsa_verify (&exchange_pub,
+                                            start_time,
+                                            duration,
+                                            secm_pub,
+                                            &secm_sig))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid security module signature for signing key %s 
(aborting)\n",
+                  TALER_B2S (&exchange_pub));
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+    {
+      struct TALER_MasterSignatureP master_sig;
+
+      TALER_exchange_offline_signkey_validity_sign (&exchange_pub,
+                                                    start_time,
+                                                    sign_end,
+                                                    legal_end,
+                                                    &master_priv,
+                                                    &master_sig);
+      GNUNET_assert (0 ==
+                     json_array_append_new (
+                       result,
+                       GNUNET_JSON_PACK (
+                         GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                                     &exchange_pub),
+                         GNUNET_JSON_pack_data_auto ("master_sig",
+                                                     &master_sig))));
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ *    denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char*section_name)
+{
+  static const struct TALER_AgeMask null_mask = {0};
+  enum GNUNET_GenericReturnValue ret;
+
+  if (! ar_enabled)
+    return null_mask;
+
+  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+                      kcfg,
+                      section_name,
+                      "AGE_RESTRICTED")))
+    return null_mask;
+
+  ret = GNUNET_CONFIGURATION_get_value_yesno (kcfg,
+                                              section_name,
+                                              "AGE_RESTRICTED");
+  if (GNUNET_SYSERR == ret)
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               section_name,
+                               "AGE_RESTRICTED",
+                               "Value must be YES or NO\n");
+  if (GNUNET_YES == ret)
+    return ar_config.mask;
+
+  return null_mask;
+}
+
+
+/**
+ * Sign @a denomkeys with offline key.
+ *
+ * @param secm_pub_rsa security module public key used to sign the RSA 
denominations
+ * @param secm_pub_cs security module public key used to sign the CS 
denominations
+ * @param denomkeys keys to output
+ * @param[in,out] result array where to output the signatures
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+sign_denomkeys (const struct TALER_SecurityModulePublicKeyP *secm_pub_rsa,
+                const struct TALER_SecurityModulePublicKeyP *secm_pub_cs,
+                const json_t *denomkeys,
+                json_t *result)
+{
+  size_t index;
+  json_t *value;
+
+  json_array_foreach (denomkeys, index, value) {
+    const char *err_name;
+    unsigned int err_line;
+    const char *section_name;
+    struct TALER_DenominationPublicKey denom_pub;
+    struct GNUNET_TIME_Timestamp stamp_start;
+    struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
+    struct GNUNET_TIME_Timestamp stamp_expire_deposit;
+    struct GNUNET_TIME_Timestamp stamp_expire_legal;
+    struct TALER_Amount coin_value;
+    struct TALER_DenomFeeSet fees;
+    struct TALER_SecurityModuleSignatureP secm_sig;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("section_name",
+                               &section_name),
+      TALER_JSON_spec_denom_pub ("denom_pub",
+                                 &denom_pub),
+      TALER_JSON_spec_amount ("value",
+                              currency,
+                              &coin_value),
+      TALER_JSON_SPEC_DENOM_FEES ("fee",
+                                  currency,
+                                  &fees),
+      GNUNET_JSON_spec_timestamp ("stamp_start",
+                                  &stamp_start),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+                                  &stamp_expire_withdraw),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+                                  &stamp_expire_deposit),
+      GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+                                  &stamp_expire_legal),
+      GNUNET_JSON_spec_fixed_auto ("denom_secmod_sig",
+                                   &secm_sig),
+      GNUNET_JSON_spec_end ()
+    };
+    struct GNUNET_TIME_Relative duration;
+    struct TALER_DenominationHashP h_denom_pub;
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (value,
+                           spec,
+                           &err_name,
+                           &err_line))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Invalid input for denomination key to 'sign': %s #%u at %u 
(skipping)\n",
+                  err_name,
+                  err_line,
+                  (unsigned int) index);
+      json_dumpf (value,
+                  stderr,
+                  JSON_INDENT (2));
+      GNUNET_JSON_parse_free (spec);
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      return GNUNET_SYSERR;
+    }
+    duration = GNUNET_TIME_absolute_get_difference (
+      stamp_start.abs_time,
+      stamp_expire_withdraw.abs_time);
+
+    /* Load the age mask, if applicable to this denomination */
+    denom_pub.age_mask = load_age_mask (section_name);
+
+    TALER_denom_pub_hash (&denom_pub,
+                          &h_denom_pub);
+    switch (denom_pub.cipher)
+    {
+    case TALER_DENOMINATION_RSA:
+      {
+        struct TALER_RsaPubHashP h_rsa;
+
+        TALER_rsa_pub_hash (denom_pub.details.rsa_public_key,
+                            &h_rsa);
+        if (GNUNET_OK !=
+            TALER_exchange_secmod_rsa_verify (&h_rsa,
+                                              section_name,
+                                              stamp_start,
+                                              duration,
+                                              secm_pub_rsa,
+                                              &secm_sig))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Invalid security module signature for denomination key 
%s (aborting)\n",
+                      GNUNET_h2s (&h_denom_pub.hash));
+          global_ret = EXIT_FAILURE;
+          test_shutdown ();
+          GNUNET_JSON_parse_free (spec);
+          return GNUNET_SYSERR;
+        }
+      }
+      break;
+    case TALER_DENOMINATION_CS:
+      {
+        struct TALER_CsPubHashP h_cs;
+
+        TALER_cs_pub_hash (&denom_pub.details.cs_public_key,
+                           &h_cs);
+        if (GNUNET_OK !=
+            TALER_exchange_secmod_cs_verify (&h_cs,
+                                             section_name,
+                                             stamp_start,
+                                             duration,
+                                             secm_pub_cs,
+                                             &secm_sig))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Invalid security module signature for denomination key 
%s (aborting)\n",
+                      GNUNET_h2s (&h_denom_pub.hash));
+          global_ret = EXIT_FAILURE;
+          test_shutdown ();
+          GNUNET_JSON_parse_free (spec);
+          return GNUNET_SYSERR;
+        }
+      }
+      break;
+    default:
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+
+    {
+      struct TALER_MasterSignatureP master_sig;
+
+      TALER_exchange_offline_denom_validity_sign (&h_denom_pub,
+                                                  stamp_start,
+                                                  stamp_expire_withdraw,
+                                                  stamp_expire_deposit,
+                                                  stamp_expire_legal,
+                                                  &coin_value,
+                                                  &fees,
+                                                  &master_priv,
+                                                  &master_sig);
+      GNUNET_assert (0 ==
+                     json_array_append_new (
+                       result,
+                       GNUNET_JSON_PACK (
+                         GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                                     &h_denom_pub),
+                         GNUNET_JSON_pack_data_auto ("master_sig",
+                                                     &master_sig))));
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Sign future keys.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_sign (char *const *args)
+{
+  json_t *keys;
+  const char *err_name;
+  unsigned int err_line;
+  const json_t *denomkeys;
+  const json_t *signkeys;
+  struct TALER_MasterPublicKeyP mpub;
+  struct TALER_SecurityModulePublicKeySetP secmset;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_array_const ("future_denoms",
+                                  &denomkeys),
+    GNUNET_JSON_spec_array_const ("future_signkeys",
+                                  &signkeys),
+    GNUNET_JSON_spec_fixed_auto ("master_pub",
+                                 &mpub),
+    GNUNET_JSON_spec_fixed_auto ("denom_secmod_public_key",
+                                 &secmset.rsa),
+    GNUNET_JSON_spec_fixed_auto ("denom_secmod_cs_public_key",
+                                 &secmset.cs),
+    GNUNET_JSON_spec_fixed_auto ("signkey_secmod_public_key",
+                                 &secmset.eddsa),
+    GNUNET_JSON_spec_end ()
+  };
+
+  keys = parse_keys_input ("sign");
+  if (NULL == keys)
+    return;
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+  {
+    json_decref (keys);
+    return;
+  }
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (keys,
+                         spec,
+                         &err_name,
+                         &err_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Invalid input to 'sign' : %s #%u (skipping)\n",
+                err_name,
+                err_line);
+    json_dumpf (in,
+                stderr,
+                JSON_INDENT (2));
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  if (0 !=
+      GNUNET_memcmp (&master_pub,
+                     &mpub))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fatal: exchange uses different master key!\n");
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  if (GNUNET_SYSERR ==
+      tofu_check (&secmset))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Fatal: security module keys changed!\n");
+    global_ret = EXIT_FAILURE;
+    test_shutdown ();
+    json_decref (keys);
+    return;
+  }
+  {
+    json_t *signkey_sig_array = json_array ();
+    json_t *denomkey_sig_array = json_array ();
+
+    GNUNET_assert (NULL != signkey_sig_array);
+    GNUNET_assert (NULL != denomkey_sig_array);
+    if ( (GNUNET_OK !=
+          sign_signkeys (&secmset.eddsa,
+                         signkeys,
+                         signkey_sig_array)) ||
+         (GNUNET_OK !=
+          sign_denomkeys (&secmset.rsa,
+                          &secmset.cs,
+                          denomkeys,
+                          denomkey_sig_array)) )
+    {
+      global_ret = EXIT_FAILURE;
+      test_shutdown ();
+      json_decref (signkey_sig_array);
+      json_decref (denomkey_sig_array);
+      json_decref (keys);
+      return;
+    }
+
+    output_operation (OP_UPLOAD_SIGS,
+                      GNUNET_JSON_PACK (
+                        GNUNET_JSON_pack_array_steal ("denom_sigs",
+                                                      denomkey_sig_array),
+                        GNUNET_JSON_pack_array_steal ("signkey_sigs",
+                                                      signkey_sig_array)));
+  }
+  json_decref (keys);
+  next (args);
+}
+
+
+/**
+ * Setup and output offline signing key.
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_setup (char *const *args)
+{
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_YES))
+  {
+    global_ret = EXIT_NOPERMISSION;
+    return;
+  }
+  if (NULL != *args)
+  {
+    output_operation (OP_SETUP,
+                      GNUNET_JSON_PACK (
+                        GNUNET_JSON_pack_data_auto ("exchange_offline_pub",
+                                                    &master_pub)));
+  }
+
+  else
+  {
+    char *pub_s;
+
+    pub_s = GNUNET_STRINGS_data_to_string_alloc (&master_pub,
+                                                 sizeof (master_pub));
+    fprintf (stdout,
+             "%s\n",
+             pub_s);
+    GNUNET_free (pub_s);
+  }
+  if ( (NULL != *args) &&
+       (0 == strcmp (*args,
+                     "-")) )
+    args++;
+  next (args);
+}
+
+
+/**
+ * Print the current extensions as configured
+ *
+ * @param args the array of command-line arguments to process next
+ */
+static void
+do_extensions_show (char *const *args)
+{
+  const struct TALER_Extensions *it;
+  json_t *exts = json_object ();
+  json_t *obj;
+
+  GNUNET_assert (NULL != exts);
+  for (it = TALER_extensions_get_head ();
+       NULL != it && NULL != it->extension;
+       it = it->next)
+  {
+    const struct TALER_Extension *extension = it->extension;
+    int ret;
+
+    ret = json_object_set_new (exts,
+                               extension->name,
+                               extension->manifest (extension));
+    GNUNET_assert (-1 != ret);
+  }
+
+  obj = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_object_steal ("extensions",
+                                   exts));
+  GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+              "%s\n",
+              json_dumps (obj,
+                          JSON_INDENT (2)));
+  json_decref (obj);
+  next (args);
+}
+
+
+/**
+ * Sign the configurations of the enabled extensions
+ */
+static void
+do_extensions_sign (char *const *args)
+{
+  json_t *extensions = json_object ();
+  struct TALER_ExtensionManifestsHashP h_manifests;
+  struct TALER_MasterSignatureP sig;
+  const struct TALER_Extensions *it;
+  bool found = false;
+  json_t *obj;
+
+  GNUNET_assert (NULL != extensions);
+  for (it = TALER_extensions_get_head ();
+       NULL != it && NULL != it->extension;
+       it = it->next)
+  {
+    const struct TALER_Extension *ext = it->extension;
+    GNUNET_assert (ext);
+
+    found = true;
+
+    GNUNET_assert (0 ==
+                   json_object_set_new (extensions,
+                                        ext->name,
+                                        ext->manifest (ext)));
+  }
+
+  if (! found)
+    return;
+
+  if (GNUNET_OK !=
+      TALER_JSON_extensions_manifests_hash (extensions,
+                                            &h_manifests))
+  {
+    json_decref (extensions);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "error while hashing manifest for extensions\n");
+    return;
+  }
+
+  if (GNUNET_OK !=
+      load_offline_key (GNUNET_NO))
+  {
+    json_decref (extensions);
+    return;
+  }
+
+  TALER_exchange_offline_extension_manifests_hash_sign (&h_manifests,
+                                                        &master_priv,
+                                                        &sig);
+  obj = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_object_steal ("extensions",
+                                   extensions),
+    GNUNET_JSON_pack_data_auto (
+      "extensions_sig",
+      &sig));
+
+  output_operation (OP_EXTENSIONS,
+                    obj);
+  next (args);
+}
+
+
+/**
+ * Dispatch @a args in the @a cmds array.
+ *
+ * @param args arguments with subcommand to dispatch
+ * @param cmds array of possible subcommands to call
+ */
+static void
+cmd_handler (char *const *args,
+             const struct SubCommand *cmds)
+{
+  nxt = NULL;
+  for (unsigned int i = 0; NULL != cmds[i].name; i++)
+  {
+    if (0 == strcasecmp (cmds[i].name,
+                         args[0]))
+    {
+      cmds[i].cb (&args[1]);
+      return;
+    }
+  }
+
+  if (0 != strcasecmp ("help",
+                       args[0]))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+                "Unexpected command `%s'\n",
+                args[0]);
+    global_ret = EXIT_INVALIDARGUMENT;
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+              "Supported subcommands:\n");
+  for (unsigned int i = 0; NULL != cmds[i].name; i++)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+                "- %s: %s\n",
+                cmds[i].name,
+                cmds[i].help);
+  }
+  json_decref (out);
+  out = NULL;
+  GNUNET_SCHEDULER_shutdown ();
+}
+
+
+static void
+do_work_extensions (char *const *args)
+{
+  struct SubCommand cmds[] = {
+    {
+      .name = "show",
+      .help =
+        "show the extensions in the Taler-config and their configured 
parameters",
+      .cb = &do_extensions_show
+    },
+    {
+      .name = "sign",
+      .help =
+        "sign the configuration of the extensions and publish it with the 
exchange",
+      .cb = &do_extensions_sign
+    },
+    {
+      .name = NULL,
+    }
+  };
+
+  if (NULL == args[0])
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "You must provide a subcommand: `show` or `sign`.\n");
+    test_shutdown ();
+    global_ret = EXIT_INVALIDARGUMENT;
+    return;
+  }
+
+  cmd_handler (args, cmds);
+}
+
+
+static void
+work (void *cls)
+{
+  char *const *args = cls;
+  struct SubCommand cmds[] = {
+    {
+      .name = "setup",
+      .help =
+        "initialize offline key signing material and display public offline 
key",
+      .cb = &do_setup
+    },
+    {
+      .name = "download",
+      .help =
+        "obtain future public keys from exchange (to be performed online!)",
+      .cb = &do_download
+    },
+    {
+      .name = "show",
+      .help =
+        "display future public keys from exchange for human review (pass '-' 
as argument to disable consuming input)",
+      .cb = &do_show
+    },
+    {
+      .name = "sign",
+      .help = "sign all future public keys from the input",
+      .cb = &do_sign
+    },
+    {
+      .name = "revoke-denomination",
+      .help =
+        "revoke denomination key (hash of public key must be given as 
argument)",
+      .cb = &do_revoke_denomination_key
+    },
+    {
+      .name = "revoke-signkey",
+      .help =
+        "revoke exchange online signing key (public key must be given as 
argument)",
+      .cb = &do_revoke_signkey
+    },
+    {
+      .name = "enable-auditor",
+      .help =
+        "enable auditor for the exchange (auditor-public key, auditor-URI and 
auditor-name must be given as arguments)",
+      .cb = &do_add_auditor
+    },
+    {
+      .name = "disable-auditor",
+      .help =
+        "disable auditor at the exchange (auditor-public key must be given as 
argument)",
+      .cb = &do_del_auditor
+    },
+    {
+      .name = "enable-account",
+      .help =
+        "enable wire account of the exchange (payto-URI must be given as 
argument; for optional argument see man page)",
+      .cb = &do_add_wire
+    },
+    {
+      .name = "disable-account",
+      .help =
+        "disable wire account of the exchange (payto-URI must be given as 
argument)",
+      .cb = &do_del_wire
+    },
+    {
+      .name = "wire-fee",
+      .help =
+        "sign wire fees for the given year (year, wire method, wire fee, and 
closing fee must be given as arguments)",
+      .cb = &do_set_wire_fee
+    },
+    {
+      .name = "global-fee",
+      .help =
+        "sign global fees for the given year (year, history fee, account fee, 
purse fee, purse timeout, history expiration and the maximum number of free 
purses per account must be given as arguments)",
+      .cb = &do_set_global_fee
+    },
+    {
+      .name = "drain",
+      .help =
+        "drain profits from exchange escrow account to regular exchange 
operator account (amount, debit account configuration section and credit 
account payto://-URI must be given as arguments)",
+      .cb = &do_drain
+    },
+    {
+      .name = "add-partner",
+      .help =
+        "add partner exchange for P2P wad transfers (partner master public 
key, partner base URL, wad fee, wad frequency and validity year must be given 
as arguments)",
+      .cb = &do_add_partner
+    },
+    {
+      .name = "aml-enable",
+      .help =
+        "enable AML staff member (staff member public key, legal name and rw 
(read write) or ro (read only) must be given as arguments)",
+      .cb = &enable_aml_staff
+    },
+    {
+      .name = "aml-disable",
+      .help =
+        "disable AML staff member (staff member public key and legal name must 
be given as arguments)",
+      .cb = &disable_aml_staff
+    },
+    {
+      .name = "upload",
+      .help =
+        "upload operation result to exchange (to be performed online!)",
+      .cb = &do_upload
+    },
+    {
+      .name = "extensions",
+      .help = "subcommands for extension handling",
+      .cb = &do_work_extensions
+    },
+    /* list terminator */
+    {
+      .name = NULL,
+    }
+  };
+  (void) cls;
+
+  cmd_handler (args, cmds);
+}
+
+
+/**
+ * Main function that will be run.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be 
NULL!)
+ * @param cfg configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  (void) cls;
+  (void) cfgfile;
+  kcfg = cfg;
+
+  /* load extensions */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_extensions_init (kcfg));
+
+  /* setup age restriction, if applicable */
+  {
+    const struct TALER_AgeRestrictionConfig *arc;
+
+    if (NULL !=
+        (arc = TALER_extensions_get_age_restriction_config ()))
+    {
+      ar_config  = *arc;
+      ar_enabled = true;
+    }
+  }
+
+
+  if (GNUNET_OK !=
+      TALER_config_get_currency (kcfg,
+                                 &currency))
+  {
+    global_ret = EXIT_NOTCONFIGURED;
+    return;
+  }
+
+  ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+                          &rc);
+  rc = GNUNET_CURL_gnunet_rc_create (ctx);
+  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+                                 NULL);
+  next (args);
+}
+
+
+/**
+ * The main function of the taler-exchange-offline tool.  This tool is used to
+ * create the signing and denomination keys for the exchange.  It uses the
+ * long-term offline private key and generates signatures with it. It also
+ * supports online operations with the exchange to download its input data and
+ * to upload its results. Those online operations should be performed on
+ * another machine in production!
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_OPTION_END
+  };
+  enum GNUNET_GenericReturnValue ret;
+
+  /* force linker to link against libtalerutil; if we do
+     not do this, the linker may "optimize" libtalerutil
+     away and skip #TALER_OS_init(), which we do need */
+  (void) TALER_project_data_default ();
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_get_utf8_args (argc, argv,
+                                    &argc, &argv))
+    return EXIT_INVALIDARGUMENT;
+  TALER_OS_init ();
+  ret = GNUNET_PROGRAM_run (
+    argc, argv,
+    "taler-exchange-offline",
+    gettext_noop ("Operations for offline signing for a Taler exchange"),
+    options,
+    &run, NULL);
+  GNUNET_free_nz ((void *) argv);
+  if (GNUNET_SYSERR == ret)
+    return EXIT_INVALIDARGUMENT;
+  if (GNUNET_NO == ret)
+    return EXIT_SUCCESS;
+  return global_ret;
+}
+
+
+/* end of taler-exchange-offline.c */
diff --git a/src/exchange/.gitignore b/src/exchange/.gitignore
new file mode 100644
index 0000000..bcfdb7e
--- /dev/null
+++ b/src/exchange/.gitignore
@@ -0,0 +1,13 @@
+taler-exchange-dbinit
+taler-exchange-keycheck
+taler-exchange-keyup
+taler-exchange-pursemod
+taler-exchange-reservemod
+taler-exchange-httpd
+taler-exchange-wirewatch
+test_taler_exchange_wirewatch-postgres
+test_taler_exchange_httpd_home/.config/taler/account-1.json
+taler-exchange-closer
+taler-exchange-transfer
+taler-exchange-router
+taler-exchange-expire
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
new file mode 100644
index 0000000..607ea91
--- /dev/null
+++ b/src/exchange/Makefile.am
@@ -0,0 +1,232 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/include \
+ $(LIBGCRYPT_CFLAGS) \
+ $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+  exchange.conf
+
+# Programs
+bin_SCRIPTS = \
+  taler-exchange-kyc-aml-pep-trigger.sh
+
+bin_PROGRAMS = \
+  taler-exchange-aggregator \
+  taler-exchange-closer \
+  taler-exchange-drain \
+  taler-exchange-expire \
+  taler-exchange-httpd \
+  taler-exchange-router \
+  taler-exchange-transfer \
+  taler-exchange-wirewatch
+
+taler_exchange_aggregator_SOURCES = \
+  taler-exchange-aggregator.c
+taler_exchange_aggregator_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+
+taler_exchange_closer_SOURCES = \
+  taler-exchange-closer.c
+taler_exchange_closer_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_drain_SOURCES = \
+  taler-exchange-drain.c
+taler_exchange_drain_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_expire_SOURCES = \
+  taler-exchange-expire.c
+taler_exchange_expire_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_router_SOURCES = \
+  taler-exchange-router.c
+taler_exchange_router_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_transfer_SOURCES = \
+  taler-exchange-transfer.c
+taler_exchange_transfer_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+taler_exchange_wirewatch_SOURCES = \
+  taler-exchange-wirewatch.c
+taler_exchange_wirewatch_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  -ljansson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  $(XLIB)
+
+
+taler_exchange_httpd_SOURCES = \
+  taler-exchange-httpd.c taler-exchange-httpd.h \
+  taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \
+  taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \
+  taler-exchange-httpd_aml-decision-get.c \
+  taler-exchange-httpd_aml-decisions-get.c \
+  taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \
+  taler-exchange-httpd_batch-withdraw.c taler-exchange-httpd_batch-withdraw.h \
+  taler-exchange-httpd_age-withdraw.c taler-exchange-httpd_age-withdraw.h \
+  taler-exchange-httpd_age-withdraw_reveal.c 
taler-exchange-httpd_age-withdraw_reveal.h \
+  taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
+  taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.h \
+  taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
+  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_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 \
+  taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \
+  taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \
+  taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \
+  taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
+  taler-exchange-httpd_link.c taler-exchange-httpd_link.h \
+  taler-exchange-httpd_management.h \
+  taler-exchange-httpd_management_aml-officers.c \
+  taler-exchange-httpd_management_auditors.c \
+  taler-exchange-httpd_management_auditors_AP_disable.c \
+  taler-exchange-httpd_management_denominations_HDP_revoke.c \
+  taler-exchange-httpd_management_drain.c \
+  taler-exchange-httpd_management_extensions.c \
+  taler-exchange-httpd_management_global_fees.c \
+  taler-exchange-httpd_management_partners.c \
+  taler-exchange-httpd_management_post_keys.c \
+  taler-exchange-httpd_management_signkey_EP_revoke.c \
+  taler-exchange-httpd_management_wire_enable.c \
+  taler-exchange-httpd_management_wire_disable.c \
+  taler-exchange-httpd_management_wire_fees.c \
+  taler-exchange-httpd_melt.c taler-exchange-httpd_melt.h \
+  taler-exchange-httpd_metrics.c taler-exchange-httpd_metrics.h \
+  taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
+  taler-exchange-httpd_purses_create.c taler-exchange-httpd_purses_create.h \
+  taler-exchange-httpd_purses_deposit.c taler-exchange-httpd_purses_deposit.h \
+  taler-exchange-httpd_purses_delete.c taler-exchange-httpd_purses_delete.h \
+  taler-exchange-httpd_purses_get.c taler-exchange-httpd_purses_get.h \
+  taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \
+  taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \
+  taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \
+  taler-exchange-httpd_refreshes_reveal.c 
taler-exchange-httpd_refreshes_reveal.h \
+  taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
+  taler-exchange-httpd_reserves_attest.c 
taler-exchange-httpd_reserves_attest.h \
+  taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \
+  taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \
+  taler-exchange-httpd_reserves_get_attest.c 
taler-exchange-httpd_reserves_get_attest.h \
+  taler-exchange-httpd_reserves_history.c 
taler-exchange-httpd_reserves_history.h \
+  taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \
+  taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \
+  taler-exchange-httpd_reserves_status.c 
taler-exchange-httpd_reserves_status.h \
+  taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
+  taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \
+  taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \
+  taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h
+
+taler_exchange_httpd_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/mhd/libtalermhd.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  $(top_builddir)/src/templating/libtalertemplating.la \
+  $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lmicrohttpd \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -lgnunetjson \
+  -ljansson \
+  -lcurl \
+  -lz \
+  $(XLIB)
+
+# Testcases
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+check_SCRIPTS = \
+  test_taler_exchange_httpd.sh
+if HAVE_EXPENSIVE_TESTS
+check_SCRIPTS += \
+  test_taler_exchange_httpd_afl.sh
+endif
+
+.NOTPARALLEL:
+TESTS = \
+  $(check_SCRIPTS)
+
+# Distribution
+
+EXTRA_DIST = \
+  
test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
 \
+  test_taler_exchange_httpd.conf \
+  test_taler_exchange_unix.conf \
+  test_taler_exchange_httpd.get \
+  test_taler_exchange_httpd.post \
+  exchange.conf \
+  $(bin_SCRIPTS) \
+  $(check_SCRIPTS)
diff --git a/src/exchange/exchange.conf b/src/exchange/exchange.conf
new file mode 100644
index 0000000..f7387ce
--- /dev/null
+++ b/src/exchange/exchange.conf
@@ -0,0 +1,138 @@
+# This file is in the public domain.
+#
+[exchange]
+
+# Master public key used to sign the exchange's various keys
+# This must be adjusted to your actual installation.
+# MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# Must be set to the threshold above which transactions
+# are flagged for AML review.
+# AML_THRESHOLD =
+
+# How many digits does the currency use by default on displays.
+# Hint provided to wallets. Should be 2 for EUR/USD/CHF,
+# and 0 for JPY. Default is 2 as that is most common.
+# Maximum value is 8. Note that this is the number of
+# fractions shown in the wallet by default, it is still
+# possible to configure denominations with more digits
+# and those will then be rendered using 'tiny' fraction
+# capitals (like at gas stations) when present.
+CURRENCY_FRACTION_DIGITS = 2
+
+# Specifies a program (binary) to run on KYC attribute data to decide
+# whether we should immediately flag an account for AML review.
+# The KYC attribute data will be passed on standard-input.
+# Return non-zero to trigger AML review of the new user.
+KYC_AML_TRIGGER = true
+
+# Attribute encryption key for storing attributes encrypted
+# in the database. Should be a high-entropy nonce.
+ATTRIBUTE_ENCRYPTION_KEY = SET_ME_PLEASE
+
+# Set to NO to disable rewards.
+ENABLE_REWARDS = YES
+
+# How long do we allow /keys to be cached at most? The actual
+# limit is the minimum of this value and the first expected
+# significant change in /keys based on the expiration times.
+# Used to artificially reduce caching (addresses #5747).
+MAX_KEYS_CACHING = forever
+
+# After how many requests should the exchange auto-restart
+# (to address potential issues with memory fragmentation)?
+# If this option is not specified, auto-restarting is disabled.
+# MAX_REQUESTS = 100000
+
+# How to access our database
+DB = postgres
+
+# Network configuration for the normal API/service HTTP server
+# serve via tcp socket (on PORT)
+SERVE = tcp
+
+# Unix domain socket to listen on,
+# only effective with "SERVE = unix"
+UNIXPATH = ${TALER_RUNTIME_DIR}/exchange-httpd/exchange-http.sock
+UNIXPATH_MODE = 660
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Base URL of the exchange (public-facing).  Due to reverse proxies,
+# this may or may not match our port or hostname at all and can thus
+# not be determined automatically. Note that a globally reachable name
+# is required, so 'localhost' will not work except for testing.
+# Required for wire transfers as we need to include it in the wire
+# transfers to enable tracking.
+BASE_URL = http://localhost:8081/
+
+# How long should the aggregator sleep if it has nothing to do?
+AGGREGATOR_IDLE_SLEEP_INTERVAL = 60 s
+
+# What type of asset is the exchange managing? Used to adjust
+# the user-interface of the wallet.
+# Possibilities include: "fiat", "regional" and "crypto".
+# In the future (and already permitted but not yet supported by wallets)
+# we also expect to have "stock" and "future" (and more).
+# Default is "fiat".
+ASSET_TYPE = "fiat"
+
+# FIXME: document!
+ROUTER_IDLE_SLEEP_INTERVAL = 60 s
+
+# How big is an individual shard to be processed
+# by taler-exchange-expire (in time).  It may take
+# this much time for an expired purse to be really
+# cleaned up and the coins refunded.
+EXPIRE_SHARD_SIZE = 1 h
+
+# How long should the transfer tool
+# sleep if it has nothing to do?
+TRANSFER_IDLE_SLEEP_INTERVAL = 60 s
+
+# How long should the closer tool
+# sleep if it has nothing to do?
+CLOSER_IDLE_SLEEP_INTERVAL = 60 s
+
+# Values of 0 or above 2^31 disable sharding, which
+# is a sane default for most use-cases.
+# When changing this value, you MUST stop all
+# aggregators and manually run
+#
+# $ taler-exchange-dbinit -s
+#
+# against the exchange's database. Otherwise, the
+# aggregation logic will break badly!
+AGGREGATOR_SHARD_SIZE = 2147483648
+
+# Values of 0 or above 2^31 disable sharding, which
+# is a sane default for most use-cases.
+# When changing this value, you MUST stop all
+# aggregators and manually run
+#
+# $ taler-exchange-dbinit -s
+#
+# against the exchange's database. Otherwise, the
+# aggregation logic will break badly!
+ROUTER_SHARD_SIZE = 2147483648
+
+# How long should wirewatch sleep if it has nothing to do?
+# (Set very aggressively here for the demonstrators to be
+# super fast.)
+WIREWATCH_IDLE_SLEEP_INTERVAL = 1 s
+
+# how long are the signatures with the signkey valid?
+SIGNKEY_LEGAL_DURATION = 2 years
+
+# Directory with our terms of service.
+TERMS_DIR = $DATADIR/terms/
+
+# Etag / filename for the terms of service.
+TERMS_ETAG = exchange-tos-v0
+
+# Directory with our privacy policy.
+PRIVACY_DIR = $DATADIR/terms/
+
+# Etag / filename for the privacy policy.
+PRIVACY_ETAG = exchange-pp-v0
diff --git a/src/exchange/taler-exchange-httpd.c 
b/src/exchange/taler-exchange-httpd.c
new file mode 100644
index 0000000..149c60c
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd.c
@@ -0,0 +1,2568 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2014-2023 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Affero General Public License as published by the Free 
Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+   You should have received a copy of the GNU Affero General Public License 
along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file taler-exchange-httpd.c
+ * @brief Serve the HTTP interface of the exchange
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <limits.h>
+#include "taler_kyclogic_lib.h"
+#include "taler_templating_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_age-withdraw.h"
+#include "taler-exchange-httpd_age-withdraw_reveal.h"
+#include "taler-exchange-httpd_aml-decision.h"
+#include "taler-exchange-httpd_auditors.h"
+#include "taler-exchange-httpd_batch-deposit.h"
+#include "taler-exchange-httpd_batch-withdraw.h"
+#include "taler-exchange-httpd_config.h"
+#include "taler-exchange-httpd_contract.h"
+#include "taler-exchange-httpd_csr.h"
+#include "taler-exchange-httpd_deposits_get.h"
+#include "taler-exchange-httpd_extensions.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_kyc-check.h"
+#include "taler-exchange-httpd_kyc-proof.h"
+#include "taler-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_link.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_melt.h"
+#include "taler-exchange-httpd_metrics.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_purses_create.h"
+#include "taler-exchange-httpd_purses_deposit.h"
+#include "taler-exchange-httpd_purses_get.h"
+#include "taler-exchange-httpd_purses_delete.h"
+#include "taler-exchange-httpd_purses_merge.h"
+#include "taler-exchange-httpd_recoup.h"
+#include "taler-exchange-httpd_recoup-refresh.h"
+#include "taler-exchange-httpd_refreshes_reveal.h"
+#include "taler-exchange-httpd_refund.h"
+#include "taler-exchange-httpd_reserves_attest.h"
+#include "taler-exchange-httpd_reserves_close.h"
+#include "taler-exchange-httpd_reserves_get.h"
+#include "taler-exchange-httpd_reserves_get_attest.h"
+#include "taler-exchange-httpd_reserves_history.h"
+#include "taler-exchange-httpd_reserves_open.h"
+#include "taler-exchange-httpd_reserves_purse.h"
+#include "taler-exchange-httpd_reserves_status.h"
+#include "taler-exchange-httpd_terms.h"
+#include "taler-exchange-httpd_transfers_get.h"
+#include "taler-exchange-httpd_withdraw.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_extensions.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+/**
+ * Backlog for listen operation on unix domain sockets.
+ */
+#define UNIX_BACKLOG 50
+
+/**
+ * How often will we try to connect to the database before giving up?
+ */
+#define MAX_DB_RETRIES 5
+
+/**
+ * Above what request latency do we start to log?
+ */
+#define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MILLISECONDS, 500)
+
+/**
+ * Are clients allowed to request /keys for times other than the
+ * current time? Allowing this could be abused in a DoS-attack
+ * as building new /keys responses is expensive. Should only be
+ * enabled for testcases, development and test systems.
+ */
+int TEH_allow_keys_timetravel;
+
+/**
+ * Should we allow two HTTPDs to bind to the same port?
+ */
+static int allow_address_reuse;
+
+/**
+ * The exchange's configuration (global)
+ */
+const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
+
+/**
+ * Configuration of age restriction
+ *
+ * Set after loading the library, enabled in database event handler.
+ */
+bool TEH_age_restriction_enabled = false;
+struct TALER_AgeRestrictionConfig TEH_age_restriction_config = {0};
+
+/**
+ * Handle to the HTTP server.
+ */
+static struct MHD_Daemon *mhd;
+
+/**
+ * How long is caching /keys allowed at most? (global)
+ */
+struct GNUNET_TIME_Relative TEH_max_keys_caching;
+
+/**
+ * How long is the delay before we close reserves?
+ */
+struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
+
+/**
+ * Master public key (according to the
+ * configuration in the exchange directory).  (global)
+ */
+struct TALER_MasterPublicKeyP TEH_master_public_key;
+
+/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
+ * Our DB plugin.  (global)
+ */
+struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
+
+/**
+ * Absolute STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+struct TALER_Amount TEH_stefan_lin;
+
+/**
+ * Default number of fractional digits to render
+ * amounts with.
+ */
+unsigned int TEH_currency_fraction_digits;
+
+/**
+ * Our currency.
+ */
+char *TEH_currency;
+
+/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+char *TEH_kyc_aml_trigger;
+
+/**
+ * Option set to #GNUNET_YES if rewards are enabled.
+ */
+int TEH_enable_rewards;
+
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+struct TALER_Amount TEH_aml_threshold;
+
+/**
+ * Our base URL.
+ */
+char *TEH_base_url;
+
+/**
+ * Default timeout in seconds for HTTP requests.
+ */
+static unsigned int connection_timeout = 30;
+
+/**
+ * -C command-line flag given?
+ */
+static int connection_close;
+
+/**
+ * -I command-line flag given?
+ */
+int TEH_check_invariants_flag;
+
+/**
+ * True if we should commit suicide once all active
+ * connections are finished.
+ */
+bool TEH_suicide;
+
+/**
+ * Signature of the configuration of all enabled extensions,
+ * signed by the exchange's offline master key with purpose
+ * TALER_SIGNATURE_MASTER_EXTENSION.
+ */
+struct TALER_MasterSignatureP TEH_extensions_sig;
+bool TEH_extensions_signed = false;
+
+/**
+ * Value to return from main()
+ */
+static int global_ret;
+
+/**
+ * Port to run the daemon on.
+ */
+static uint16_t serve_port;
+
+/**
+ * Counter for the number of requests this HTTP has processed so far.
+ */
+static unsigned long long req_count;
+
+/**
+ * Counter for the number of open connections.
+ */
+static unsigned long long active_connections;
+
+/**
+ * Limit for the number of requests this HTTP may process before restarting.
+ * (This was added as one way of dealing with unavoidable memory fragmentation
+ * happening slowly over time.)
+ */
+static unsigned long long req_max;
+
+/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+struct GNUNET_CURL_Context *TEH_curl_ctx;
+
+/**
+ * Context for integrating #TEH_curl_ctx with the
+ * GNUnet event loop.
+ */
+static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
+
+/**
+ * Signature of functions that handle operations on coins.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub the public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*CoinOpHandler)(struct MHD_Connection *connection,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                 const json_t *root);
+
+
+/**
+ * Generate a 404 "not found" reply on @a connection with
+ * the hint @a details.
+ *
+ * @param connection where to send the reply on
+ * @param details details for the error message, can be NULL
+ */
+static MHD_RESULT
+r404 (struct MHD_Connection *connection,
+      const char *details)
+{
+  return TALER_MHD_reply_with_error (connection,
+                                     MHD_HTTP_NOT_FOUND,
+                                     
TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
+                                     details);
+}
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/$OP" POST request.  Parses the "coin_pub"
+ * EdDSA key of the coin and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_coins (struct TEH_RequestContext *rc,
+                   const json_t *root,
+                   const char *const args[2])
+{
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    CoinOpHandler handler;
+
+  } h[] = {
+    {
+      .op = "melt",
+      .handler = &TEH_handler_melt
+    },
+    {
+      .op = "recoup",
+      .handler = &TEH_handler_recoup
+    },
+    {
+      .op = "recoup-refresh",
+      .handler = &TEH_handler_recoup_refresh
+    },
+    {
+      .op = "refund",
+      .handler = &TEH_handler_refund
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &coin_pub,
+                                     sizeof (coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
+                                       args[0]);
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (rc->connection,
+                           &coin_pub,
+                           root);
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
+/**
+ * Signature of functions that handle operations
+ * authorized by AML officers.
+ *
+ * @param rc request context
+ * @param officer_pub the public key of the AML officer
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*AmlOpPostHandler)(struct TEH_RequestContext *rc,
+                    const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+                    const json_t *root);
+
+
+/**
+ * Handle a "/aml/$OFFICER_PUB/$OP" POST request.  Parses the "officer_pub"
+ * EdDSA key of the officer and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_aml (struct TEH_RequestContext *rc,
+                 const json_t *root,
+                 const char *const args[2])
+{
+  struct TALER_AmlOfficerPublicKeyP officer_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    AmlOpPostHandler handler;
+
+  } h[] = {
+    {
+      .op = "decision",
+      .handler = &TEH_handler_post_aml_decision
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &officer_pub,
+                                     sizeof (officer_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+                                       args[0]);
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (rc,
+                           &officer_pub,
+                           root);
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
+/**
+ * Signature of functions that handle operations
+ * authorized by AML officers.
+ *
+ * @param rc request context
+ * @param officer_pub the public key of the AML officer
+ * @param args remaining arguments
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*AmlOpGetHandler)(struct TEH_RequestContext *rc,
+                   const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+                   const char *const args[]);
+
+
+/**
+ * Handle a "/aml/$OFFICER_PUB/$OP" GET request.  Parses the "officer_pub"
+ * EdDSA key of the officer, checks the authentication signature, and
+ * demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_aml (struct TEH_RequestContext *rc,
+                const char *const args[])
+{
+  struct TALER_AmlOfficerPublicKeyP officer_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    AmlOpGetHandler handler;
+
+  } h[] = {
+    {
+      .op = "decisions",
+      .handler = &TEH_handler_aml_decisions_get
+    },
+    {
+      .op = "decision",
+      .handler = &TEH_handler_aml_decision_get
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if (NULL == args[0])
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+                                       "argument missing");
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &officer_pub,
+                                     sizeof (officer_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_PUB_MALFORMED,
+                                       args[0]);
+  }
+  if (NULL == args[1])
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
+                                       "AML GET operations must specify an 
operation identifier");
+  }
+  {
+    const char *sig_hdr;
+    struct TALER_AmlOfficerSignatureP officer_sig;
+
+    sig_hdr = MHD_lookup_connection_value (rc->connection,
+                                           MHD_HEADER_KIND,
+                                           TALER_AML_OFFICER_SIGNATURE_HEADER);
+    if ( (NULL == sig_hdr) ||
+         (GNUNET_OK !=
+          GNUNET_STRINGS_string_to_data (sig_hdr,
+                                         strlen (sig_hdr),
+                                         &officer_sig,
+                                         sizeof (officer_sig))) ||
+         (GNUNET_OK !=
+          TALER_officer_aml_query_verify (&officer_pub,
+                                          &officer_sig)) )
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_GET_SIGNATURE_INVALID,
+                                         sig_hdr);
+    }
+    TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+  }
+
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TEH_plugin->test_aml_officer (TEH_plugin->cls,
+                                       &officer_pub);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_HARD_ERROR:
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         
TALER_EC_EXCHANGE_GENERIC_AML_OFFICER_ACCESS_DENIED,
+                                         NULL);
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      break;
+    }
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (rc,
+                           &officer_pub,
+                           &args[2]);
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
+/**
+ * Handle a "/age-withdraw/$ACH/reveal" POST request.  Parses the "ACH"
+ * hash of the commitment from a previous call to
+ * /reserves/$reserve_pub/age-withdraw
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_age_withdraw (struct TEH_RequestContext *rc,
+                          const json_t *root,
+                          const char *const args[2])
+{
+  struct TALER_AgeWithdrawCommitmentHashP ach;
+
+  if (0 != strcmp ("reveal", args[1]))
+    return r404 (rc->connection,
+                 args[1]);
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &ach,
+                                     sizeof (ach)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+                                       args[0]);
+  }
+
+  return TEH_handler_age_withdraw_reveal (rc,
+                                          &ach,
+                                          root);
+}
+
+
+/**
+ * Signature of functions that handle operations on reserves.
+ *
+ * @param rc request context
+ * @param reserve_pub the public key of the reserve
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*ReserveOpHandler)(struct TEH_RequestContext *rc,
+                    const struct TALER_ReservePublicKeyP *reserve_pub,
+                    const json_t *root);
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/$OP" POST request.  Parses the 
"reserve_pub"
+ * EdDSA key of the reserve and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_reserves (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[2])
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    ReserveOpHandler handler;
+
+  } h[] = {
+    {
+      .op = "batch-withdraw",
+      .handler = &TEH_handler_batch_withdraw
+    },
+    {
+      .op = "age-withdraw",
+      .handler = &TEH_handler_age_withdraw
+    },
+    {
+      .op = "withdraw",
+      .handler = &TEH_handler_withdraw
+    },
+    {
+      .op = "status",
+      .handler = &TEH_handler_reserves_status
+    },
+    {
+      .op = "history",
+      .handler = &TEH_handler_reserves_history
+    },
+    {
+      .op = "purse",
+      .handler = &TEH_handler_reserves_purse
+    },
+    {
+      .op = "open",
+      .handler = &TEH_handler_reserves_open
+    },
+    {
+      .op = "close",
+      .handler = &TEH_handler_reserves_close
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &reserve_pub,
+                                     sizeof (reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+                                       args[0]);
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (rc,
+                           &reserve_pub,
+                           root);
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
+/**
+ * Signature of functions that handle operations on purses.
+ *
+ * @param connection HTTP request handle
+ * @param purse_pub the public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+typedef MHD_RESULT
+(*PurseOpHandler)(struct MHD_Connection *connection,
+                  const struct TALER_PurseContractPublicKeyP *purse_pub,
+                  const json_t *root);
+
+
+/**
+ * Handle a "/purses/$RESERVE_PUB/$OP" POST request.  Parses the "purse_pub"
+ * EdDSA key of the purse and demultiplexes based on $OP.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_purses (struct TEH_RequestContext *rc,
+                    const json_t *root,
+                    const char *const args[2])
+{
+  struct TALER_PurseContractPublicKeyP purse_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     */
+    PurseOpHandler handler;
+
+  } h[] = {
+    {
+      .op = "create",
+      .handler = &TEH_handler_purses_create
+    },
+    {
+      .op = "deposit",
+      .handler = &TEH_handler_purses_deposit
+    },
+    {
+      .op = "merge",
+      .handler = &TEH_handler_purses_merge
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &purse_pub,
+                                     sizeof (purse_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+                                       args[0]);
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (rc->connection,
+                           &purse_pub,
+                           root);
+  return r404 (rc->connection,
+               args[1]);
+}
+
+
+/**
+ * Increments our request counter and checks if this
+ * process should commit suicide.
+ */
+static void
+check_suicide (void)
+{
+  int fd;
+  pid_t chld;
+  unsigned long long cnt;
+
+  cnt = req_count++;
+  if (req_max != cnt)
+    return;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Restarting exchange service after %llu requests\n",
+              cnt);
+  /* Stop accepting new connections */
+  fd = MHD_quiesce_daemon (mhd);
+  GNUNET_break (0 == close (fd));
+  /* Continue handling existing connections in child,
+     so that this process can die and be replaced by
+     systemd with a fresh one */
+  chld = fork ();
+  if (-1 == chld)
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "fork");
+    _exit (1);
+  }
+  if (0 != chld)
+  {
+    /* We are the parent, instant-suicide! */
+    _exit (0);
+  }
+  TEH_suicide = true;
+}
+
+
+/**
+ * Function called whenever MHD is done with a request.  If the
+ * request was a POST, we may have stored a `struct Buffer *` in the
+ * @a con_cls that might still need to be cleaned up.  Call the
+ * respective function to free the memory.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ *        the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
+ */
+static void
+handle_mhd_completion_callback (void *cls,
+                                struct MHD_Connection *connection,
+                                void **con_cls,
+                                enum MHD_RequestTerminationCode toe)
+{
+  struct TEH_RequestContext *rc = *con_cls;
+  struct GNUNET_AsyncScopeSave old_scope;
+
+  (void) cls;
+  if (NULL == rc)
+    return;
+  GNUNET_async_scope_enter (&rc->async_scope_id,
+                            &old_scope);
+  check_suicide ();
+  TEH_check_invariants ();
+  if (NULL != rc->rh_cleaner)
+    rc->rh_cleaner (rc);
+  TEH_check_invariants ();
+  {
+#if MHD_VERSION >= 0x00097304
+    const union MHD_ConnectionInfo *ci;
+    unsigned int http_status = 0;
+
+    ci = MHD_get_connection_info (connection,
+                                  MHD_CONNECTION_INFO_HTTP_STATUS);
+    if (NULL != ci)
+      http_status = ci->http_status;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Request for `%s' completed with HTTP status %u (%d)\n",
+                rc->url,
+                http_status,
+                toe);
+#else
+    (void) connection;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Request for `%s' completed (%d)\n",
+                rc->url,
+                toe);
+#endif
+  }
+
+  TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
+  /* Sanity-check that we didn't leave any transactions hanging */
+  GNUNET_break (GNUNET_OK ==
+                TEH_plugin->preflight (TEH_plugin->cls));
+  {
+    struct GNUNET_TIME_Relative latency;
+
+    latency = GNUNET_TIME_absolute_get_duration (rc->start_time);
+    if (latency.rel_value_us >
+        WARN_LATENCY.rel_value_us)
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Request for `%s' took %s\n",
+                  rc->url,
+                  GNUNET_STRINGS_relative_time_to_string (latency,
+                                                          GNUNET_YES));
+  }
+  GNUNET_free (rc);
+  *con_cls = NULL;
+  GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * We found a request handler responsible for handling a request. Parse the
+ * @a upload_data (if applicable) and the @a url and call the
+ * handler.
+ *
+ * @param rc request context
+ * @param url rest of the URL to parse
+ * @param upload_data upload data to parse (if available)
+ * @param[in,out] upload_data_size number of bytes in @a upload_data
+ * @return MHD result code
+ */
+static MHD_RESULT
+proceed_with_handler (struct TEH_RequestContext *rc,
+                      const char *url,
+                      const char *upload_data,
+                      size_t *upload_data_size)
+{
+  const struct TEH_RequestHandler *rh = rc->rh;
+  const char *args[rh->nargs + 2];
+  size_t ulen = strlen (url) + 1;
+  json_t *root = NULL;
+  MHD_RESULT ret;
+
+  /* We do check for "ulen" here, because we'll later stack-allocate a buffer
+     of that size and don't want to enable malicious clients to cause us
+     huge stack allocations. */
+  if (ulen > 512)
+  {
+    /* 512 is simply "big enough", as it is bigger than "6 * 54",
+       which is the longest URL format we ever get (for
+       /deposits/).  The value should be adjusted if we ever define protocol
+       endpoints with plausibly longer inputs.  */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_URI_TOO_LONG,
+                                       TALER_EC_GENERIC_URI_TOO_LONG,
+                                       url);
+  }
+
+  /* All POST endpoints come with a body in JSON format. So we parse
+     the JSON here. */
+  if (0 == strcasecmp (rh->method,
+                       MHD_HTTP_METHOD_POST))
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_post_json (rc->connection,
+                                     &rc->opaque_post_parsing_context,
+                                     upload_data,
+                                     upload_data_size,
+                                     &root);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_assert (NULL == root);
+      return MHD_NO; /* bad upload, could not even generate error */
+    }
+    if ( (GNUNET_NO == res) ||
+         (NULL == root) )
+    {
+      GNUNET_assert (NULL == root);
+      return MHD_YES; /* so far incomplete upload or parser error */
+    }
+  }
+
+  {
+    char d[ulen];
+    unsigned int i;
+    char *sp;
+
+    /* Parse command-line arguments */
+    /* make a copy of 'url' because 'strtok_r()' will modify */
+    GNUNET_memcpy (d,
+                   url,
+                   ulen);
+    i = 0;
+    args[i++] = strtok_r (d, "/", &sp);
+    while ( (NULL != args[i - 1]) &&
+            (i <= rh->nargs + 1) )
+      args[i++] = strtok_r (NULL, "/", &sp);
+    /* make sure above loop ran nicely until completion, and also
+       that there is no excess data in 'd' afterwards */
+    if ( ( (rh->nargs_is_upper_bound) &&
+           (i - 1 > rh->nargs) ) ||
+         ( (! rh->nargs_is_upper_bound) &&
+           (i - 1 != rh->nargs) ) )
+    {
+      char emsg[128 + 512];
+
+      GNUNET_snprintf (emsg,
+                       sizeof (emsg),
+                       "Got %u+/%u segments for `%s' request (`%s')",
+                       i - 1,
+                       rh->nargs,
+                       rh->url,
+                       url);
+      GNUNET_break_op (0);
+      json_decref (root);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
+                                         emsg);
+    }
+    GNUNET_assert (NULL == args[i - 1]);
+
+    /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
+       so we test for 'root' to decide which handler to invoke. */
+    if (0 == strcasecmp (rh->method,
+                         MHD_HTTP_METHOD_POST))
+      ret = rh->handler.post (rc,
+                              root,
+                              args);
+    else if (0 == strcasecmp (rh->method,
+                              MHD_HTTP_METHOD_DELETE))
+      ret = rh->handler.delete (rc,
+                                args);
+    else /* Only GET left */
+      ret = rh->handler.get (rc,
+                             args);
+  }
+  json_decref (root);
+  return ret;
+}
+
+
+/**
+ * Handle a "/seed" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_seed (struct TEH_RequestContext *rc,
+              const char *const args[])
+{
+#define SEED_SIZE 32
+  char *body;
+  MHD_RESULT ret;
+  struct MHD_Response *resp;
+
+  (void) args;
+  body = malloc (SEED_SIZE); /* must use malloc(), because MHD will use free() 
*/
+  if (NULL == body)
+    return MHD_NO;
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              body,
+                              SEED_SIZE);
+  resp = MHD_create_response_from_buffer (SEED_SIZE,
+                                          body,
+                                          MHD_RESPMEM_MUST_FREE);
+  TALER_MHD_add_global_headers (resp);
+  ret = MHD_queue_response (rc->connection,
+                            MHD_HTTP_OK,
+                            resp);
+  GNUNET_break (MHD_YES == ret);
+  MHD_destroy_response (resp);
+  return ret;
+#undef SEED_SIZE
+}
+
+
+/**
+ * Handle POST "/management/..." requests.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_management (struct TEH_RequestContext *rc,
+                        const json_t *root,
+                        const char *const args[])
+{
+  if (NULL == args[0])
+  {
+    GNUNET_break_op (0);
+    return r404 (rc->connection,
+                 "/management");
+  }
+  if (0 == strcmp (args[0],
+                   "auditors"))
+  {
+    struct TALER_AuditorPublicKeyP auditor_pub;
+
+    if (NULL == args[1])
+      return TEH_handler_management_auditors (rc->connection,
+                                              root);
+    if ( (NULL == args[1]) ||
+         (NULL == args[2]) ||
+         (0 != strcmp (args[2],
+                       "disable")) ||
+         (NULL != args[3]) )
+      return r404 (rc->connection,
+                   "/management/auditors/$AUDITOR_PUB/disable");
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[1],
+                                       strlen (args[1]),
+                                       &auditor_pub,
+                                       sizeof (auditor_pub)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                         args[1]);
+    }
+    return TEH_handler_management_auditors_AP_disable (rc->connection,
+                                                       &auditor_pub,
+                                                       root);
+  }
+  if (0 == strcmp (args[0],
+                   "denominations"))
+  {
+    struct TALER_DenominationHashP h_denom_pub;
+
+    if ( (NULL == args[0]) ||
+         (NULL == args[1]) ||
+         (NULL == args[2]) ||
+         (0 != strcmp (args[2],
+                       "revoke")) ||
+         (NULL != args[3]) )
+      return r404 (rc->connection,
+                   "/management/denominations/$HDP/revoke");
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[1],
+                                       strlen (args[1]),
+                                       &h_denom_pub,
+                                       sizeof (h_denom_pub)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                         args[1]);
+    }
+    return TEH_handler_management_denominations_HDP_revoke (rc->connection,
+                                                            &h_denom_pub,
+                                                            root);
+  }
+  if (0 == strcmp (args[0],
+                   "signkeys"))
+  {
+    struct TALER_ExchangePublicKeyP exchange_pub;
+
+    if ( (NULL == args[0]) ||
+         (NULL == args[1]) ||
+         (NULL == args[2]) ||
+         (0 != strcmp (args[2],
+                       "revoke")) ||
+         (NULL != args[3]) )
+      return r404 (rc->connection,
+                   "/management/signkeys/$HDP/revoke");
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[1],
+                                       strlen (args[1]),
+                                       &exchange_pub,
+                                       sizeof (exchange_pub)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                         args[1]);
+    }
+    return TEH_handler_management_signkeys_EP_revoke (rc->connection,
+                                                      &exchange_pub,
+                                                      root);
+  }
+  /* FIXME-STYLE: all of the following can likely be nicely combined
+     into an array-based dispatcher to deduplicate the logic... */
+  if (0 == strcmp (args[0],
+                   "keys"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/keys/*");
+    }
+    return TEH_handler_management_post_keys (rc->connection,
+                                             root);
+  }
+  if (0 == strcmp (args[0],
+                   "wire"))
+  {
+    if (NULL == args[1])
+      return TEH_handler_management_post_wire (rc->connection,
+                                               root);
+    if ( (0 != strcmp (args[1],
+                       "disable")) ||
+         (NULL != args[2]) )
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/wire/disable");
+    }
+    return TEH_handler_management_post_wire_disable (rc->connection,
+                                                     root);
+  }
+  if (0 == strcmp (args[0],
+                   "wire-fee"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/wire-fee/*");
+    }
+    return TEH_handler_management_post_wire_fees (rc->connection,
+                                                  root);
+  }
+  if (0 == strcmp (args[0],
+                   "global-fee"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/global-fee/*");
+    }
+    return TEH_handler_management_post_global_fees (rc->connection,
+                                                    root);
+  }
+  if (0 == strcmp (args[0],
+                   "extensions"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/extensions/*");
+    }
+    return TEH_handler_management_post_extensions (rc->connection,
+                                                   root);
+  }
+  if (0 == strcmp (args[0],
+                   "drain"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/drain/*");
+    }
+    return TEH_handler_management_post_drain (rc->connection,
+                                              root);
+  }
+  if (0 == strcmp (args[0],
+                   "aml-officers"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/aml-officers/*");
+    }
+    return TEH_handler_management_aml_officers (rc->connection,
+                                                root);
+  }
+  if (0 == strcmp (args[0],
+                   "partners"))
+  {
+    if (NULL != args[1])
+    {
+      GNUNET_break_op (0);
+      return r404 (rc->connection,
+                   "/management/partners/*");
+    }
+    return TEH_handler_management_partners (rc->connection,
+                                            root);
+  }
+  GNUNET_break_op (0);
+  return r404 (rc->connection,
+               "/management/*");
+}
+
+
+/**
+ * Handle a GET "/management" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be [0] == "keys")
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_get_management (struct TEH_RequestContext *rc,
+                       const char *const args[2])
+{
+  if ( (NULL != args[0]) &&
+       (0 == strcmp (args[0],
+                     "keys")) &&
+       (NULL == args[1]) )
+  {
+    return TEH_keys_management_get_keys_handler (rc->rh,
+                                                 rc->connection);
+  }
+  GNUNET_break_op (0);
+  return r404 (rc->connection,
+               "/management/*");
+}
+
+
+/**
+ * Handle POST "/auditors/..." requests.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_post_auditors (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[])
+{
+  struct TALER_AuditorPublicKeyP auditor_pub;
+  struct TALER_DenominationHashP h_denom_pub;
+
+  if ( (NULL == args[0]) ||
+       (NULL == args[1]) ||
+       (NULL != args[2]) )
+  {
+    GNUNET_break_op (0);
+    return r404 (rc->connection,
+                 "/auditors/$AUDITOR_PUB/$H_DENOM_PUB");
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &auditor_pub,
+                                     sizeof (auditor_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       args[0]);
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[1],
+                                     strlen (args[1]),
+                                     &h_denom_pub,
+                                     sizeof (h_denom_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       args[1]);
+  }
+  return TEH_handler_auditors (rc->connection,
+                               &auditor_pub,
+                               &h_denom_pub,
+                               root);
+}
+
+
+/**
+ * Handle incoming HTTP request.
+ *
+ * @param cls closure for MHD daemon (unused)
+ * @param connection the connection
+ * @param url the requested url
+ * @param method the method (POST, GET, ...)
+ * @param version HTTP version (ignored)
+ * @param upload_data request data
+ * @param upload_data_size size of @a upload_data in bytes
+ * @param con_cls closure for request (a `struct TEH_RequestContext *`)
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_mhd_request (void *cls,
+                    struct MHD_Connection *connection,
+                    const char *url,
+                    const char *method,
+                    const char *version,
+                    const char *upload_data,
+                    size_t *upload_data_size,
+                    void **con_cls)
+{
+  static struct TEH_RequestHandler handlers[] = {
+    /* /robots.txt: disallow everything */
+    {
+      .url = "robots.txt",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_static_response,
+      .mime_type = "text/plain",
+      .data = "User-agent: *\nDisallow: /\n",
+      .response_code = MHD_HTTP_OK
+    },
+    /* Landing page, tell humans to go away. */
+    {
+      .url = "",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_handler_static_response,
+      .mime_type = "text/plain",
+      .data =
+        "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
+      .response_code = MHD_HTTP_OK
+    },
+    /* AGPL licensing page, redirect to source. As per the AGPL-license, every
+       deployment is required to offer the user a download of the source of
+       the actual deployment. We make this easy by including a redirect to the
+       source here. */
+    {
+      .url = "agpl",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_agpl_redirect
+    },
+    {
+      .url = "seed",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &handler_seed
+    },
+    /* Configuration */
+    {
+      .url = "config",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_config
+    },
+    /* Performance metrics */
+    {
+      .url = "metrics",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_metrics
+    },
+    /* Terms of service */
+    {
+      .url = "terms",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_terms
+    },
+    /* Privacy policy */
+    {
+      .url = "privacy",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_privacy
+    },
+    /* Return key material and fundamental properties for this exchange */
+    {
+      .url = "keys",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_keys_get_handler,
+    },
+    {
+      .url = "batch-deposit",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_batch_deposit,
+      .nargs = 0
+    },
+    /* request R, used in clause schnorr withdraw and refresh */
+    {
+      .url = "csr-melt",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_csr_melt,
+      .nargs = 0
+    },
+    {
+      .url = "csr-withdraw",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_csr_withdraw,
+      .nargs = 0
+    },
+    /* Withdrawing coins / interaction with reserves */
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_reserves_get,
+      .nargs = 1
+    },
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_reserves,
+      .nargs = 2
+    },
+    {
+      .url = "age-withdraw",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_age_withdraw,
+      .nargs = 2
+    },
+    {
+      .url = "reserves-attest",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_reserves_get_attest,
+      .nargs = 1
+    },
+    {
+      .url = "reserves-attest",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_reserves_attest,
+      .nargs = 1
+    },
+    /* coins */
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_coins,
+      .nargs = 2
+    },
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_handler_link,
+      .nargs = 2,
+    },
+    /* refreshes/$RCH/reveal */
+    {
+      .url = "refreshes",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_reveal,
+      .nargs = 2
+    },
+    /* tracking transfers */
+    {
+      .url = "transfers",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_transfers_get,
+      .nargs = 1
+    },
+    /* tracking deposits */
+    {
+      .url = "deposits",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_deposits_get,
+      .nargs = 4
+    },
+    /* Operating on purses */
+    {
+      .url = "purses",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_purses,
+      .nargs = 2
+    },
+    /* Getting purse status */
+    {
+      .url = "purses",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_purses_get,
+      .nargs = 2
+    },
+    /* Deleting purse */
+    {
+      .url = "purses",
+      .method = MHD_HTTP_METHOD_DELETE,
+      .handler.delete = &TEH_handler_purses_delete,
+      .nargs = 1
+    },
+    /* Getting contracts */
+    {
+      .url = "contracts",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_contracts_get,
+      .nargs = 1
+    },
+    /* KYC endpoints */
+    {
+      .url = "kyc-check",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_kyc_check,
+      .nargs = 3
+    },
+    {
+      .url = "kyc-proof",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_kyc_proof,
+      .nargs = 1
+    },
+    {
+      .url = "kyc-wallet",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_handler_kyc_wallet,
+      .nargs = 0
+    },
+    /* POST management endpoints */
+    {
+      .url = "management",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_management,
+      .nargs = 4,
+      .nargs_is_upper_bound = true
+    },
+    /* GET management endpoints (we only really have "/management/keys") */
+    {
+      .url = "management",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &handle_get_management,
+      .nargs = 1
+    },
+    /* auditor endpoints */
+    {
+      .url = "auditors",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_auditors,
+      .nargs = 4,
+      .nargs_is_upper_bound = true
+    },
+    /* AML endpoints */
+    {
+      .url = "aml",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &handle_get_aml,
+      .nargs = 4,
+      .nargs_is_upper_bound = true
+    },
+    {
+      .url = "aml",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_aml,
+      .nargs = 2
+    },
+
+
+    /* mark end of list */
+    {
+      .url = NULL
+    }
+  };
+  struct TEH_RequestContext *rc = *con_cls;
+  struct GNUNET_AsyncScopeSave old_scope;
+  const char *correlation_id = NULL;
+
+  (void) cls;
+  (void) version;
+  if (NULL == rc)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling new request\n");
+
+    /* We're in a new async scope! */
+    rc = *con_cls = GNUNET_new (struct TEH_RequestContext);
+    rc->start_time = GNUNET_TIME_absolute_get ();
+    GNUNET_async_scope_fresh (&rc->async_scope_id);
+    TEH_check_invariants ();
+    rc->url = url;
+    rc->connection = connection;
+    /* We only read the correlation ID on the first callback for every client 
*/
+    correlation_id = MHD_lookup_connection_value (connection,
+                                                  MHD_HEADER_KIND,
+                                                  "Taler-Correlation-Id");
+    if ( (NULL != correlation_id) &&
+         (GNUNET_YES !=
+          GNUNET_CURL_is_valid_scope_id (correlation_id)) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "illegal incoming correlation ID\n");
+      correlation_id = NULL;
+    }
+
+    /* Check if upload is in bounds */
+    if (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_POST))
+    {
+      TALER_MHD_check_content_length (connection,
+                                      TALER_MHD_REQUEST_BUFFER_MAX);
+    }
+  }
+
+  GNUNET_async_scope_enter (&rc->async_scope_id,
+                            &old_scope);
+  TEH_check_invariants ();
+  if (NULL != correlation_id)
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling request (%s) for URL '%s', correlation_id=%s\n",
+                method,
+                url,
+                correlation_id);
+  else
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling request (%s) for URL '%s'\n",
+                method,
+                url);
+  /* on repeated requests, check our cache first */
+  if (NULL != rc->rh)
+  {
+    MHD_RESULT ret;
+    const char *start;
+
+    if ('\0' == url[0])
+      /* strange, should start with '/', treat as just "/" */
+      url = "/";
+    start = strchr (url + 1, '/');
+    if (NULL == start)
+      start = "";
+    ret = proceed_with_handler (rc,
+                                start,
+                                upload_data,
+                                upload_data_size);
+    GNUNET_async_scope_restore (&old_scope);
+    return ret;
+  }
+
+  if ( (0 == strcasecmp (method,
+                         MHD_HTTP_METHOD_OPTIONS)) &&
+       (0 == strcmp ("*",
+                     url)) )
+    return TALER_MHD_reply_cors_preflight (connection);
+
+  if (0 == strcasecmp (method,
+                       MHD_HTTP_METHOD_HEAD))
+    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the 
rest */
+
+  /* parse first part of URL */
+  {
+    bool found = false;
+    size_t tok_size;
+    const char *tok;
+    const char *rest;
+
+    if ('\0' == url[0])
+      /* strange, should start with '/', treat as just "/" */
+      url = "/";
+    tok = url + 1;
+    rest = strchr (tok, '/');
+    if (NULL == rest)
+    {
+      tok_size = strlen (tok);
+    }
+    else
+    {
+      tok_size = rest - tok;
+      rest++; /* skip over '/' */
+    }
+    for (unsigned int i = 0; NULL != handlers[i].url; i++)
+    {
+      struct TEH_RequestHandler *rh = &handlers[i];
+
+      if ( (0 != strncmp (tok,
+                          rh->url,
+                          tok_size)) ||
+           (tok_size != strlen (rh->url) ) )
+        continue;
+      found = true;
+      /* The URL is a match!  What we now do depends on the method. */
+      if (0 == strcasecmp (method,
+                           MHD_HTTP_METHOD_OPTIONS))
+      {
+        GNUNET_async_scope_restore (&old_scope);
+        return TALER_MHD_reply_cors_preflight (connection);
+      }
+      GNUNET_assert (NULL != rh->method);
+      if (0 == strcasecmp (method,
+                           rh->method))
+      {
+        MHD_RESULT ret;
+
+        /* cache to avoid the loop next time */
+        rc->rh = rh;
+        /* run handler */
+        ret = proceed_with_handler (rc,
+                                    url + tok_size + 1,
+                                    upload_data,
+                                    upload_data_size);
+        GNUNET_async_scope_restore (&old_scope);
+        return ret;
+      }
+    }
+
+    if (found)
+    {
+      /* we found a matching address, but the method is wrong */
+      struct MHD_Response *reply;
+      MHD_RESULT ret;
+      char *allowed = NULL;
+
+      GNUNET_break_op (0);
+      for (unsigned int i = 0; NULL != handlers[i].url; i++)
+      {
+        struct TEH_RequestHandler *rh = &handlers[i];
+
+        if ( (0 != strncmp (tok,
+                            rh->url,
+                            tok_size)) ||
+             (tok_size != strlen (rh->url) ) )
+          continue;
+        if (NULL == allowed)
+        {
+          allowed = GNUNET_strdup (rh->method);
+        }
+        else
+        {
+          char *tmp;
+
+          GNUNET_asprintf (&tmp,
+                           "%s, %s",
+                           allowed,
+                           rh->method);
+          GNUNET_free (allowed);
+          allowed = tmp;
+        }
+        if (0 == strcasecmp (rh->method,
+                             MHD_HTTP_METHOD_GET))
+        {
+          char *tmp;
+
+          GNUNET_asprintf (&tmp,
+                           "%s, %s",
+                           allowed,
+                           MHD_HTTP_METHOD_HEAD);
+          GNUNET_free (allowed);
+          allowed = tmp;
+        }
+      }
+      reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
+                                    method);
+      GNUNET_break (MHD_YES ==
+                    MHD_add_response_header (reply,
+                                             MHD_HTTP_HEADER_ALLOW,
+                                             allowed));
+      GNUNET_free (allowed);
+      ret = MHD_queue_response (connection,
+                                MHD_HTTP_METHOD_NOT_ALLOWED,
+                                reply);
+      MHD_destroy_response (reply);
+      return ret;
+    }
+  }
+
+  /* No handler matches, generate not found */
+  {
+    MHD_RESULT ret;
+
+    ret = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_NOT_FOUND,
+                                      TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+                                      url);
+    GNUNET_async_scope_restore (&old_scope);
+    return ret;
+  }
+}
+
+
+/**
+ * Load configuration parameters for the exchange
+ * server into the corresponding global variables.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+exchange_serve_process_config (void)
+{
+  if (GNUNET_OK !=
+      TALER_KYCLOGIC_kyc_init (TEH_cfg))
+  {
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
+                                             "exchange",
+                                             "MAX_REQUESTS",
+                                             &req_max))
+  {
+    req_max = ULLONG_MAX;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+                                           "exchangedb",
+                                           "IDLE_RESERVE_EXPIRATION_TIME",
+                                           &TEH_reserve_closing_delay))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchangedb",
+                               "IDLE_RESERVE_EXPIRATION_TIME");
+    /* use default */
+    TEH_reserve_closing_delay
+      = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
+                                       4);
+  }
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+                                           "exchange",
+                                           "MAX_KEYS_CACHING",
+                                           &TEH_max_keys_caching))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "MAX_KEYS_CACHING",
+                               "valid relative time expected");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+                                             "exchange",
+                                             "KYC_AML_TRIGGER",
+                                             &TEH_kyc_aml_trigger))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "KYC_AML_TRIGGER");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_currency (TEH_cfg,
+                                 &TEH_currency))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "taler",
+                               "CURRENCY");
+    return GNUNET_SYSERR;
+  }
+  {
+    unsigned long long cfd;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
+                                               "exchange",
+                                               "CURRENCY_FRACTION_DIGITS",
+                                               &cfd))
+      cfd = 0;
+    if (cfd > 8)
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 "taler",
+                                 "CURRENCY_FRACTION_DIGITS",
+                                 "Value must be below 8");
+      return GNUNET_SYSERR;
+    }
+    TEH_currency_fraction_digits = (unsigned int) cfd;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               "exchange",
+                               "AML_THRESHOLD",
+                               &TEH_aml_threshold))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Need amount in section `exchange' under `AML_THRESHOLD'\n");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               "exchange",
+                               "STEFAN_ABS",
+                               &TEH_stefan_abs))
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &TEH_stefan_abs));
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               "exchange",
+                               "STEFAN_LOG",
+                               &TEH_stefan_log))
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &TEH_stefan_log));
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               "exchange",
+                               "STEFAN_LIN",
+                               &TEH_stefan_lin))
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &TEH_stefan_lin));
+  }
+
+  if (0 != strcmp (TEH_currency,
+                   TEH_aml_threshold.currency))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Amount in section `exchange' under `AML_THRESHOLD' uses the 
wrong currency!\n");
+    return GNUNET_SYSERR;
+  }
+  TEH_enable_rewards
+    = GNUNET_CONFIGURATION_get_value_yesno (
+        TEH_cfg,
+        "exchange",
+        "ENABLE_REWARDS");
+  if (GNUNET_SYSERR == TEH_enable_rewards)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Need YES or NO in section `exchange' under 
`ENABLE_REWARDS'\n");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &TEH_base_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    return GNUNET_SYSERR;
+  }
+  if (! TALER_url_valid_charset (TEH_base_url))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL",
+                               "invalid URL");
+    return GNUNET_SYSERR;
+  }
+
+  {
+    char *master_public_key_str;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+                                               "exchange",
+                                               "MASTER_PUBLIC_KEY",
+                                               &master_public_key_str))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 "exchange",
+                                 "MASTER_PUBLIC_KEY");
+      return GNUNET_SYSERR;
+    }
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
+                                                    strlen (
+                                                      master_public_key_str),
+                                                    &TEH_master_public_key.
+                                                    eddsa_pub))
+    {
+      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                                 "exchange",
+                                 "MASTER_PUBLIC_KEY",
+                                 "invalid base32 encoding for a master public 
key");
+      GNUNET_free (master_public_key_str);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Launching exchange with public key `%s'...\n",
+                master_public_key_str);
+    GNUNET_free (master_public_key_str);
+  }
+
+  {
+    char *attr_enc_key_str;
+
+    if (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+                                               "exchange",
+                                               "ATTRIBUTE_ENCRYPTION_KEY",
+                                               &attr_enc_key_str))
+    {
+      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                                 "exchange",
+                                 "ATTRIBUTE_ENCRYPTION_KEY");
+      return GNUNET_SYSERR;
+    }
+    GNUNET_CRYPTO_hash (attr_enc_key_str,
+                        strlen (attr_enc_key_str),
+                        &TEH_attribute_key.hash);
+    GNUNET_free (attr_enc_key_str);
+  }
+
+  for (unsigned int i = 0; i<MAX_DB_RETRIES; i++)
+  {
+    TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg);
+    if (NULL != TEH_plugin)
+      break;
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to connect to DB, will try again %u times\n",
+                MAX_DB_RETRIES - i);
+    sleep (1);
+  }
+  if (NULL == TEH_plugin)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to initialize DB subsystem. Giving up.\n");
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Called when the main thread exits, writes out performance
+ * stats if requested.
+ */
+static void
+write_stats (void)
+{
+  struct GNUNET_DISK_FileHandle *fh;
+  pid_t pid = getpid ();
+  char *benchmark_dir;
+  char *s;
+  struct rusage usage;
+
+  benchmark_dir = getenv ("GNUNET_BENCHMARK_DIR");
+  if (NULL == benchmark_dir)
+    return;
+  GNUNET_asprintf (&s,
+                   "%s/taler-exchange-%llu.txt",
+                   benchmark_dir,
+                   (unsigned long long) pid);
+  fh = GNUNET_DISK_file_open (s,
+                              (GNUNET_DISK_OPEN_WRITE
+                               | GNUNET_DISK_OPEN_TRUNCATE
+                               | GNUNET_DISK_OPEN_CREATE),
+                              (GNUNET_DISK_PERM_USER_READ
+                               | GNUNET_DISK_PERM_USER_WRITE));
+  GNUNET_free (s);
+  if (NULL == fh)
+    return; /* permission denied? */
+
+  /* Collect stats, summed up for all threads */
+  GNUNET_assert (0 ==
+                 getrusage (RUSAGE_SELF,
+                            &usage));
+  GNUNET_asprintf (&s,
+                   "time_exchange sys %llu user %llu\n",
+                   (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
+                                         + usage.ru_stime.tv_usec),
+                   (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
+                                         + usage.ru_utime.tv_usec));
+  GNUNET_assert (GNUNET_SYSERR !=
+                 GNUNET_DISK_file_write_blocking (fh,
+                                                  s,
+                                                  strlen (s)));
+  GNUNET_free (s);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_DISK_file_close (fh));
+}
+
+
+/* Developer logic for supporting the `-f' option. */
+#if HAVE_DEVELOPER
+
+/**
+ * Option `-f' (specifies an input file to give to the HTTP server).
+ */
+static char *input_filename;
+
+
+/**
+ * Run 'nc' or 'ncat' as a fake HTTP client using #input_filename
+ * as the input for the request.  If launching the client worked,
+ * run the #TEH_KS_loop() event loop as usual.
+ *
+ * @return child pid
+ */
+static pid_t
+run_fake_client (void)
+{
+  pid_t cld;
+  char ports[6];
+  int fd;
+
+  if (0 == strcmp (input_filename,
+                   "-"))
+    fd = STDIN_FILENO;
+  else
+    fd = open (input_filename,
+               O_RDONLY);
+  if (-1 == fd)
+  {
+    fprintf (stderr,
+             "Failed to open `%s': %s\n",
+             input_filename,
+             strerror (errno));
+    return -1;
+  }
+  /* Fake HTTP client request with #input_filename as input.
+     We do this using the nc tool. */
+  GNUNET_snprintf (ports,
+                   sizeof (ports),
+                   "%u",
+                   serve_port);
+  if (0 == (cld = fork ()))
+  {
+    GNUNET_break (0 == close (0));
+    GNUNET_break (0 == dup2 (fd, 0));
+    GNUNET_break (0 == close (fd));
+    if ( (0 != execlp ("nc",
+                       "nc",
+                       "localhost",
+                       ports,
+                       "-w", "30",
+                       NULL)) &&
+         (0 != execlp ("ncat",
+                       "ncat",
+                       "localhost",
+                       ports,
+                       "-i", "30",
+                       NULL)) )
+    {
+      fprintf (stderr,
+               "Failed to run both `nc' and `ncat': %s\n",
+               strerror (errno));
+    }
+    _exit (1);
+  }
+  /* parent process */
+  if (0 != strcmp (input_filename,
+                   "-"))
+    GNUNET_break (0 == close (fd));
+  return cld;
+}
+
+
+/**
+ * Run the exchange to serve a single request only, without threads.
+ *
+ * @return #GNUNET_OK on success
+ */
+static void
+run_single_request (void)
+{
+  pid_t xfork;
+
+  xfork = fork ();
+  if (-1 == xfork)
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (0 == xfork)
+  {
+    pid_t cld;
+
+    cld = run_fake_client ();
+    if (-1 == cld)
+      _exit (EXIT_FAILURE);
+    _exit (EXIT_SUCCESS);
+  }
+
+  {
+    int status;
+
+    if (xfork != waitpid (xfork,
+                          &status,
+                          0))
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Waiting for `nc' child failed: %s\n",
+                  strerror (errno));
+  }
+}
+
+
+/* end of HAVE_DEVELOPER */
+#endif
+
+
+/**
+ * Signature of the callback used by MHD to notify the application
+ * about completed connections.  If we are running in test-mode with
+ * an input_filename, this function is used to terminate the HTTPD
+ * after the first request has been processed.
+ *
+ * @param cls client-defined closure, NULL
+ * @param connection connection handle (ignored)
+ * @param socket_context socket-specific pointer (ignored)
+ * @param toe reason for connection notification
+ */
+static void
+connection_done (void *cls,
+                 struct MHD_Connection *connection,
+                 void **socket_context,
+                 enum MHD_ConnectionNotificationCode toe)
+{
+  (void) cls;
+  (void) connection;
+  (void) socket_context;
+
+  switch (toe)
+  {
+  case MHD_CONNECTION_NOTIFY_STARTED:
+    active_connections++;
+    break;
+  case MHD_CONNECTION_NOTIFY_CLOSED:
+    active_connections--;
+    if (TEH_suicide &&
+        (0 == active_connections) )
+      GNUNET_SCHEDULER_shutdown ();
+    break;
+  }
+#if HAVE_DEVELOPER
+  /* We only act if the connection is closed. */
+  if (MHD_CONNECTION_NOTIFY_CLOSED != toe)
+    return;
+  if (NULL != input_filename)
+    GNUNET_SCHEDULER_shutdown ();
+#endif
+}
+
+
+/**
+ * Function run on shutdown.
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+  struct MHD_Daemon *mhd;
+  (void) cls;
+
+  mhd = TALER_MHD_daemon_stop ();
+  TEH_resume_keys_requests (true);
+  TEH_deposits_get_cleanup ();
+  TEH_reserves_get_cleanup ();
+  TEH_purses_get_cleanup ();
+  TEH_kyc_check_cleanup ();
+  TEH_kyc_proof_cleanup ();
+  TALER_KYCLOGIC_kyc_done ();
+  if (NULL != mhd)
+  {
+    MHD_stop_daemon (mhd);
+    mhd = NULL;
+  }
+  TEH_wire_done ();
+  TEH_extensions_done ();
+  TEH_keys_finished ();
+  if (NULL != TEH_plugin)
+  {
+    TALER_EXCHANGEDB_plugin_unload (TEH_plugin);
+    TEH_plugin = NULL;
+  }
+  if (NULL != TEH_curl_ctx)
+  {
+    GNUNET_CURL_fini (TEH_curl_ctx);
+    TEH_curl_ctx = NULL;
+  }
+  if (NULL != exchange_curl_rc)
+  {
+    GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
+    exchange_curl_rc = NULL;
+  }
+  TALER_TEMPLATING_done ();
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ *        NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+     char *const *args,
+     const char *cfgfile,
+     const struct GNUNET_CONFIGURATION_Handle *config)
+{
+  enum TALER_MHD_GlobalOptions go;
+  int fh;
+
+  (void) cls;
+  (void) args;
+  (void ) cfgfile;
+  go = TALER_MHD_GO_NONE;
+  if (connection_close)
+    go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+  TALER_MHD_setup (go);
+  TEH_cfg = config;
+
+  if (GNUNET_OK !=
+      exchange_serve_process_config ())
+  {
+    global_ret = EXIT_NOTCONFIGURED;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TEMPLATING_init ("exchange"))
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (GNUNET_SYSERR ==
+      TEH_plugin->preflight (TEH_plugin->cls))
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (GNUNET_OK !=
+      TEH_extensions_init ())
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (GNUNET_OK !=
+      TEH_keys_init ())
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  if (GNUNET_OK !=
+      TEH_wire_init ())
+  {
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+
+  TEH_load_terms (TEH_cfg);
+  TEH_curl_ctx
+    = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+                        &exchange_curl_rc);
+  if (NULL == TEH_curl_ctx)
+  {
+    GNUNET_break (0);
+    global_ret = EXIT_FAILURE;
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEH_curl_ctx);
+  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+                                 NULL);
+  fh = TALER_MHD_bind (TEH_cfg,
+                       "exchange",
+                       &serve_port);
+  if ( (0 == serve_port) &&
+       (-1 == fh) )
+  {
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
+                          | MHD_USE_PIPE_FOR_SHUTDOWN
+                          | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
+                          | MHD_USE_TCP_FASTOPEN,
+                          (-1 == fh) ? serve_port : 0,
+                          NULL, NULL,
+                          &handle_mhd_request, NULL,
+                          MHD_OPTION_LISTEN_BACKLOG_SIZE,
+                          (unsigned int) 1024,
+                          MHD_OPTION_LISTEN_SOCKET,
+                          fh,
+                          MHD_OPTION_EXTERNAL_LOGGER,
+                          &TALER_MHD_handle_logs,
+                          NULL,
+                          MHD_OPTION_NOTIFY_COMPLETED,
+                          &handle_mhd_completion_callback,
+                          NULL,
+                          MHD_OPTION_NOTIFY_CONNECTION,
+                          &connection_done,
+                          NULL,
+                          MHD_OPTION_CONNECTION_TIMEOUT,
+                          connection_timeout,
+                          (0 == allow_address_reuse)
+                          ? MHD_OPTION_END
+                          : MHD_OPTION_LISTENING_ADDRESS_REUSE,
+                          (unsigned int) allow_address_reuse,
+                          MHD_OPTION_END);
+  if (NULL == mhd)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to launch HTTP service. Is the port in use?\n");
+    GNUNET_SCHEDULER_shutdown ();
+    return;
+  }
+  global_ret = EXIT_SUCCESS;
+  TALER_MHD_daemon_start (mhd);
+  atexit (&write_stats);
+
+#if HAVE_DEVELOPER
+  if (NULL != input_filename)
+    run_single_request ();
+#endif
+}
+
+
+/**
+ * The main function of the taler-exchange-httpd server ("the exchange").
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  const struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_option_flag ('a',
+                               "allow-timetravel",
+                               "allow clients to request /keys for arbitrary 
timestamps (for testing and development only)",
+                               &TEH_allow_keys_timetravel),
+    GNUNET_GETOPT_option_flag ('C',
+                               "connection-close",
+                               "force HTTP connections to be closed after each 
request",
+                               &connection_close),
+    GNUNET_GETOPT_option_flag ('I',
+                               "check-invariants",
+                               "enable expensive invariant checks",
+                               &TEH_check_invariants_flag),
+    GNUNET_GETOPT_option_flag ('r',
+                               "allow-reuse-address",
+                               "allow multiple HTTPDs to listen to the same 
port",
+                               &allow_address_reuse),
+    GNUNET_GETOPT_option_uint ('t',
+                               "timeout",
+                               "SECONDS",
+                               "after how long do connections timeout by 
default (in seconds)",
+                               &connection_timeout),
+    GNUNET_GETOPT_option_timetravel ('T',
+                                     "timetravel"),
+#if HAVE_DEVELOPER
+    GNUNET_GETOPT_option_filename ('f',
+                                   "file-input",
+                                   "FILENAME",
+                                   "run in test-mode using FILENAME as the 
HTTP request to process, use '-' to read from stdin",
+                                   &input_filename),
+#endif
+    GNUNET_GETOPT_option_help (
+      "HTTP server providing a RESTful API to access a Taler exchange"),
+    GNUNET_GETOPT_OPTION_END
+  };
+  enum GNUNET_GenericReturnValue ret;
+
+  TALER_OS_init ();
+  ret = GNUNET_PROGRAM_run (argc, argv,
+                            "taler-exchange-httpd",
+                            "Taler exchange HTTP service",
+                            options,
+                            &run, NULL);
+  if (GNUNET_SYSERR == ret)
+    return EXIT_INVALIDARGUMENT;
+  if (GNUNET_NO == ret)
+    return EXIT_SUCCESS;
+  return global_ret;
+}
+
+
+/* end of taler-exchange-httpd.c */
diff --git a/src/exchange/taler-exchange-httpd.h 
b/src/exchange/taler-exchange-httpd.h
new file mode 100644
index 0000000..9e18f9e
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd.h
@@ -0,0 +1,317 @@
+/*
+  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.h
+ * @brief Global declarations for the exchange
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_H
+#define TALER_EXCHANGE_HTTPD_H
+
+#include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_util.h"
+#include "taler_kyclogic_plugin.h"
+#include "taler_extensions.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * How long is caching /keys allowed at most?
+ */
+extern struct GNUNET_TIME_Relative TEH_max_keys_caching;
+
+/**
+ * How long is the delay before we close reserves?
+ */
+extern struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
+
+/**
+ * The exchange's configuration.
+ */
+extern const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
+
+/**
+ * Main directory with exchange data.
+ */
+extern char *TEH_exchange_directory;
+
+/**
+ * -I command-line flag given?
+ */
+extern int TEH_check_invariants_flag;
+
+/**
+ * Are clients allowed to request /keys for times other than the
+ * current time? Allowing this could be abused in a DoS-attack
+ * as building new /keys responses is expensive. Should only be
+ * enabled for testcases, development and test systems.
+ */
+extern int TEH_allow_keys_timetravel;
+
+/**
+ * Option set to #GNUNET_YES if rewards are allowed.
+ */
+extern int TEH_enable_rewards;
+
+/**
+ * Main directory with revocation data.
+ */
+extern char *TEH_revocation_directory;
+
+/**
+ * True if we should commit suicide once all active
+ * connections are finished. Also forces /keys requests
+ * to terminate if they are long-polling.
+ */
+extern bool TEH_suicide;
+
+/**
+ * Master public key (according to the
+ * configuration in the exchange directory).
+ */
+extern struct TALER_MasterPublicKeyP TEH_master_public_key;
+
+/**
+ * Key used to encrypt KYC attribute data in our database.
+ */
+extern struct TALER_AttributeEncryptionKeyP TEH_attribute_key;
+
+/**
+ * Our DB plugin.
+ */
+extern struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
+
+/**
+ * Absolute STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_abs;
+
+/**
+ * Logarithmic STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_log;
+
+/**
+ * Linear STEFAN parameter.
+ */
+extern struct TALER_Amount TEH_stefan_lin;
+
+/**
+ * Default number of fractional digits to render
+ * amounts with.
+ */
+extern unsigned int TEH_currency_fraction_digits;
+
+/**
+ * Our currency.
+ */
+extern char *TEH_currency;
+
+/**
+ * Name of the KYC-AML-trigger evaluation binary.
+ */
+extern char *TEH_kyc_aml_trigger;
+
+/**
+ * What is the largest amount we allow a peer to
+ * merge into a reserve before always triggering
+ * an AML check?
+ */
+extern struct TALER_Amount TEH_aml_threshold;
+
+/**
+ * Our (externally visible) base URL.
+ */
+extern char *TEH_base_url;
+
+/**
+ * Are we shutting down?
+ */
+extern volatile bool MHD_terminating;
+
+/**
+ * Context for all CURL operations (useful to the event loop)
+ */
+extern struct GNUNET_CURL_Context *TEH_curl_ctx;
+
+/*
+ * Signature of the offline master key of all enabled extensions' configuration
+ */
+extern struct TALER_MasterSignatureP TEH_extensions_sig;
+extern bool TEH_extensions_signed;
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct TEH_RequestHandler;
+
+
+/**
+ * @brief Context in which the exchange is processing
+ *        all requests
+ */
+struct TEH_RequestContext
+{
+
+  /**
+   * Async Scope ID associated with this request.
+   */
+  struct GNUNET_AsyncScopeId async_scope_id;
+
+  /**
+   * When was this request started?
+   */
+  struct GNUNET_TIME_Absolute start_time;
+
+  /**
+   * Opaque parsing context.
+   */
+  void *opaque_post_parsing_context;
+
+  /**
+   * Request handler responsible for this request.
+   */
+  const struct TEH_RequestHandler *rh;
+
+  /**
+   * Request URL (for logging).
+   */
+  const char *url;
+
+  /**
+   * Connection we are processing.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * @e rh-specific cleanup routine. Function called
+   * upon completion of the request that should
+   * clean up @a rh_ctx. Can be NULL.
+   */
+  void
+  (*rh_cleaner)(struct TEH_RequestContext *rc);
+
+  /**
+   * @e rh-specific context. Place where the request
+   * handler can associate state with this request.
+   * Can be NULL.
+   */
+  void *rh_ctx;
+};
+
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct TEH_RequestHandler
+{
+
+  /**
+   * URL the handler is for (first part only).
+   */
+  const char *url;
+
+  /**
+   * Method the handler is for.
+   */
+  const char *method;
+
+  /**
+   * Callbacks for handling of the request. Which one is used
+   * depends on @e method.
+   */
+  union
+  {
+    /**
+     * Function to call to handle GET requests (and those
+     * with @e method NULL).
+     *
+     * @param rc context for the request
+     * @param args array of arguments, needs to be of length @e args_expected
+     * @return MHD result code
+     */
+    MHD_RESULT
+    (*get)(struct TEH_RequestContext *rc,
+           const char *const args[]);
+
+
+    /**
+     * Function to call to handle POST requests.
+     *
+     * @param rc context for the request
+     * @param json uploaded JSON data
+     * @param args array of arguments, needs to be of length @e nargs
+     * @return MHD result code
+     */
+    MHD_RESULT
+    (*post)(struct TEH_RequestContext *rc,
+            const json_t *root,
+            const char *const args[]);
+
+    /**
+     * Function to call to handle DELETE requests.
+     *
+     * @param rc context for the request
+     * @param args array of arguments, needs to be of length @e nargs
+     * @return MHD result code
+     */
+    MHD_RESULT
+      (*delete)(struct TEH_RequestContext *rc,
+                const char *const args[]);
+
+  } handler;
+
+  /**
+   * Mime type to use in reply (hint, can be NULL).
+   */
+  const char *mime_type;
+
+  /**
+   * Raw data for the @e handler, can be NULL for none provided.
+   */
+  const void *data;
+
+  /**
+   * Number of bytes in @e data, 0 for data is 0-terminated (!).
+   */
+  size_t data_size;
+
+  /**
+   * Default response code. 0 for none provided.
+   */
+  unsigned int response_code;
+
+  /**
+   * Number of arguments this handler expects in the @a args array.
+   */
+  unsigned int nargs;
+
+  /**
+   * Is the number of arguments given in @e nargs only an upper bound,
+   * and calling with fewer arguments could be OK?
+   */
+  bool nargs_is_upper_bound;
+};
+
+
+/* Age restriction configuration */
+extern bool TEH_age_restriction_enabled;
+extern struct TALER_AgeRestrictionConfig TEH_age_restriction_config;
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c 
b/src/exchange/taler-exchange-httpd_batch-deposit.c
new file mode 100644
index 0000000..6bf70ef
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -0,0 +1,729 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_batch-deposit.c
+ * @brief Handle /batch-deposit requests; parses the POST and JSON and
+ *        verifies the coin signatures before handing things off
+ *        to the database.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#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_batch-deposit.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Closure for #batch_deposit_transaction.
+ */
+struct BatchDepositContext
+{
+
+  /**
+   * Array with the individual coin deposit fees.
+   */
+  struct TALER_Amount *deposit_fees;
+
+  /**
+   * 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;
+
+  /**
+   * Details about the batch deposit operation.
+   */
+  struct TALER_EXCHANGEDB_BatchDeposit bd;
+
+  /**
+   * Additional details for policy extension relevant for this
+   * deposit operation, possibly NULL!
+   */
+  json_t *policy_json;
+
+  /**
+   * Hash over the merchant's payto://-URI with the wire salt.
+   */
+  struct TALER_MerchantWireHashP h_wire;
+
+  /**
+   * If @e policy_json was present, the corresponding policy extension
+   * calculates these details.  These will be persisted in the policy_details
+   * table.
+   */
+  struct TALER_PolicyDetails policy_details;
+
+  /**
+   * Hash over @e policy_details, might be all zero
+   */
+  struct TALER_ExtensionPolicyHashP h_policy;
+
+  /**
+   * When @e policy_details are persisted, this contains the id of the record
+   * in the policy_details table.
+   */
+  uint64_t policy_details_serial_id;
+
+};
+
+
+/**
+ * Send confirmation of batch 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 coins had sufficient
+ * (residual) value for the specified transaction and that it will execute the
+ * requested batch deposit operation with the given wiring details.
+ *
+ * @param connection connection to the client
+ * @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 *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<bd->num_cdis; i++)
+  {
+    const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &bd->cdis[i];
+    struct TALER_ExchangePublicKeyP pubi;
+    struct TALER_ExchangeSignatureP sig;
+    enum TALER_ErrorCode ec;
+    struct TALER_Amount amount_without_fee;
+
+    GNUNET_assert (0 <=
+                   TALER_amount_subtract (&amount_without_fee,
+                                          &cdi->amount_with_fee,
+                                          &dc->deposit_fees[i]));
+    if (TALER_EC_NONE !=
+        (ec = TALER_exchange_online_deposit_confirmation_sign (
+           &TEH_keys_exchange_sign_,
+           &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,
+           &cdi->coin.coin_pub,
+           &dc->bd.merchant_pub,
+           &pubi,
+           &sig)))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_ec (connection,
+                                      ec,
+                                      NULL);
+    }
+    if (0 == i)
+      pub = pubi;
+    if (0 !=
+        GNUNET_memcmp (&pub,
+                       &pubi))
+    {
+      /* note: in the future, maybe have batch sign API to avoid having to
+         handle key rollover... */
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Exchange public key changed during batch deposit, trying 
again\n");
+      json_decref (arr);
+      goto again;
+    }
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (arr,
+                             GNUNET_JSON_PACK (
+                               GNUNET_JSON_pack_data_auto (
+                                 "exchange_sig",
+                                 &sig))));
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+                                dc->exchange_timestamp),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &pub),
+    GNUNET_JSON_pack_array_steal ("exchange_sigs",
+                                  arr));
+}
+
+
+/**
+ * Execute database transaction for /batch-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 BatchDepositContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (void *cls,
+                           struct MHD_Connection *connection,
+                           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 = UINT32_MAX;
+  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 (NULL != dc->policy_json)
+  {
+    qs = TEH_plugin->persist_policy_details (
+      TEH_plugin->cls,
+      &dc->policy_details,
+      &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!?*/
+  }
+
+  /* FIXME: replace by batch insert! */
+  for (unsigned int i = 0; i<bd->num_cdis; i++)
+  {
+    const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+      = &bd->cdis[i];
+    uint64_t known_coin_id;
+
+    qs = TEH_make_coin_known (&cdi->coin,
+                              connection,
+                              &known_coin_id,
+                              mhd_ret);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "make coin known (%s) returned %d\n",
+                TALER_B2S (&cdi->coin.coin_pub),
+                qs);
+    if (qs < 0)
+      return qs;
+  }
+
+  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;
+    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_DEBUG,
+              "do_deposit returned: %d / %s[%u] / %s\n",
+              qs,
+              balance_ok ? "balance ok" : "balance insufficient",
+              (unsigned int) 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);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "returning history of conflicting coin (%s)\n",
+                TALER_B2S (&bd->cdis[bad_balance_coin_index].coin.coin_pub));
+    *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;
+}
+
+
+/**
+ * Parse per-coin deposit information from @a jcoin
+ * into @a deposit. Fill in generic information from
+ * @a ctx.
+ *
+ * @param connection connection we are handling
+ * @param dc information about the overall batch
+ * @param jcoin coin data to parse
+ * @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,
+            const struct BatchDepositContext *dc,
+            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,
+                            &cdi->amount_with_fee),
+    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                 &cdi->coin.denom_pub_hash),
+    TALER_JSON_spec_denom_sig ("ub_sig",
+                               &cdi->coin.denom_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &cdi->coin.coin_pub),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &cdi->coin.h_age_commitment),
+      &cdi->coin.no_age_commitment),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &cdi->csig),
+    GNUNET_JSON_spec_end ()
+  };
+  enum GNUNET_GenericReturnValue res;
+
+  if (GNUNET_OK !=
+      (res = TALER_MHD_parse_json_data (connection,
+                                        jcoin,
+                                        spec)))
+    return res;
+  /* check denomination exists and is valid */
+  {
+    struct TEH_DenominationKey *dk;
+    MHD_RESULT mret;
+
+    dk = TEH_keys_denomination_by_hash (&cdi->coin.denom_pub_hash,
+                                        connection,
+                                        &mret);
+    if (NULL == dk)
+    {
+      GNUNET_JSON_parse_free (spec);
+      return mret;
+    }
+    if (0 > TALER_amount_cmp (&dk->meta.value,
+                              &cdi->amount_with_fee))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_BAD_REQUEST,
+                                          
TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+                                          NULL))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+    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 (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &cdi->coin.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+                "DEPOSIT"))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &cdi->coin.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+                "DEPOSIT"))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &cdi->coin.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+                "DEPOSIT"))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+    if (dk->denom_pub.cipher != cdi->coin.denom_sig.cipher)
+    {
+      /* denomination cipher and denomination signature cipher not the same */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_BAD_REQUEST,
+                                          
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+                                          NULL))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+
+    *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 (&cdi->coin,
+                               &dk->denom_pub))
+    {
+      TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_FORBIDDEN,
+                                          
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+                                          NULL))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+    }
+  }
+  if (0 < TALER_amount_cmp (deposit_fee,
+                            &cdi->amount_with_fee))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_BAD_REQUEST,
+                                        
TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+                                        NULL))
+        ? GNUNET_NO
+        : GNUNET_SYSERR;
+  }
+
+  TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+  if (GNUNET_OK !=
+      TALER_wallet_deposit_verify (
+        &cdi->amount_with_fee,
+        deposit_fee,
+        &dc->h_wire,
+        &bd->h_contract_terms,
+        &bd->wallet_data_hash,
+        NULL != cdi->coin.no_age_commitment
+        ? NULL
+        : &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);
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_FORBIDDEN,
+                                        
TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
+                                        TALER_B2S (&cdi->coin.coin_pub)))
+      ? GNUNET_NO
+      : GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+                           const json_t *root,
+                           const char *const args[])
+{
+  struct MHD_Connection *connection = rc->connection;
+  struct BatchDepositContext dc = { 0 };
+  struct TALER_EXCHANGEDB_BatchDeposit *bd = &dc.bd;
+  const json_t *coins;
+  bool no_refund_deadline = true;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("merchant_payto_uri",
+                             &bd->receiver_wire_account),
+    GNUNET_JSON_spec_fixed_auto ("wire_salt",
+                                 &bd->wire_salt),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &bd->merchant_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &bd->h_contract_terms),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+                                   &bd->wallet_data_hash),
+      &bd->no_wallet_data_hash),
+    GNUNET_JSON_spec_array_const ("coins",
+                                  &coins),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_json ("policy",
+                             &dc.policy_json),
+      NULL),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &bd->wallet_timestamp),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_timestamp ("refund_deadline",
+                                  &bd->refund_deadline),
+      &no_refund_deadline),
+    GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
+                                &bd->wire_deadline),
+    GNUNET_JSON_spec_end ()
+  };
+  enum GNUNET_GenericReturnValue res;
+
+  (void) args;
+  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 */
+  }
+
+  /* validate merchant's wire details (as far as we can) */
+  {
+    char *emsg;
+
+    emsg = TALER_payto_validate (bd->receiver_wire_account);
+    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 (bd->refund_deadline,
+                                 >,
+                                 bd->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 (bd->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);
+  }
+  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 (NULL != dc.policy_json)
+  {
+    const char *error_hint = NULL;
+
+    if (GNUNET_OK !=
+        TALER_extensions_create_policy_details (
+          dc.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_json,
+                               &dc.h_policy);
+  }
+
+  bd->num_cdis = json_array_size (coins);
+  if (0 == bd->num_cdis)
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "coins");
+  }
+  if (TALER_MAX_FRESH_COINS < bd->num_cdis)
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "coins");
+  }
+
+  {
+    struct TALER_EXCHANGEDB_CoinDepositInformation cdis[
+      GNUNET_NZL (bd->num_cdis)];
+    struct TALER_Amount deposit_fees[GNUNET_NZL (bd->num_cdis)];
+
+    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)
+      {
+        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;
+      }
+    }
+
+    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))
+      {
+        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;
+
+      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;
+    }
+  }
+}
+
+
+/* end of taler-exchange-httpd_batch-deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.h 
b/src/exchange/taler-exchange-httpd_batch-deposit.h
new file mode 100644
index 0000000..187fb9f
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-deposit.h
@@ -0,0 +1,49 @@
+/*
+  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_batch-deposit.h
+ * @brief Handle /batch-deposit requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/batch-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 "/batch-deposit" being executed, or
+ * rejected.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args arguments, empty in this case
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+                           const json_t *root,
+                           const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.c 
b/src/exchange/taler-exchange-httpd_batch-withdraw.c
new file mode 100644
index 0000000..38a7f43
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.c
@@ -0,0 +1,930 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as
+  published by the Free Software Foundation; either version 3,
+  or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty
+  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General
+  Public License along with TALER; see the file COPYING.  If not,
+  see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_batch-withdraw.c
+ * @brief Handle /reserves/$RESERVE_PUB/batch-withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_batch-withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler_util.h"
+
+
+/**
+ * Information per planchet in the batch.
+ */
+struct PlanchetContext
+{
+
+  /**
+   * Hash of the (blinded) message to be signed by the Exchange.
+   */
+  struct TALER_BlindedCoinHashP h_coin_envelope;
+
+  /**
+   * Value of the coin being exchanged (matching the denomination key)
+   * plus the transaction fee.  We include this in what is being
+   * signed so that we can verify a reserve's remaining total balance
+   * without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Blinded planchet.
+   */
+  struct TALER_BlindedPlanchet blinded_planchet;
+
+  /**
+   * Set to the resulting signed coin data to be returned to the client.
+   */
+  struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+
+};
+
+/**
+ * Context for #batch_withdraw_transaction.
+ */
+struct BatchWithdrawContext
+{
+
+  /**
+   * Public key of the reserv.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * request context
+   */
+  const struct TEH_RequestContext *rc;
+
+  /**
+   * KYC status of the reserve used for the operation.
+   */
+  struct TALER_EXCHANGEDB_KycStatus kyc;
+
+  /**
+   * Array of @e planchets_length planchets we are processing.
+   */
+  struct PlanchetContext *planchets;
+
+  /**
+   * Hash of the payto-URI representing the reserve
+   * from which we are withdrawing.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Current time for the DB transaction.
+   */
+  struct GNUNET_TIME_Timestamp now;
+
+  /**
+   * Total amount from all coins with fees.
+   */
+  struct TALER_Amount batch_total;
+
+  /**
+   * Length of the @e planchets array.
+   */
+  unsigned int planchets_length;
+
+  /**
+   * AML decision, #TALER_AML_NORMAL if we may proceed.
+   */
+  enum TALER_AmlDecisionState aml_decision;
+
+};
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and
+ *        account to iterate over events for
+ * @param limit maximum time-range for which events
+ *        should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ *        events must be returned in reverse chronological
+ *        order
+ * @param cb_cls closure for @a cb
+ */
+static void
+batch_withdraw_amount_cb (void *cls,
+                          struct GNUNET_TIME_Absolute limit,
+                          TALER_EXCHANGEDB_KycAmountCallback cb,
+                          void *cb_cls)
+{
+  struct BatchWithdrawContext *wc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (GNUNET_OK !=
+      cb (cb_cls,
+          &wc->batch_total,
+          wc->now.abs_time))
+    return;
+  qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
+    TEH_plugin->cls,
+    &wc->h_payto,
+    limit,
+    cb,
+    cb_cls);
+  GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for the AML check as it was merged into
+ * the reserve.
+ *
+ * @param cls `struct TALER_Amount *` to total up the amounts
+ * @param amount encountered transaction amount
+ * @param date when was the amount encountered
+ * @return #GNUNET_OK to continue to iterate,
+ *         #GNUNET_NO to abort iteration
+ *         #GNUNET_SYSERR on internal error (also abort itaration)
+ */
+static enum GNUNET_GenericReturnValue
+aml_amount_cb (
+  void *cls,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Absolute date)
+{
+  struct TALER_Amount *total = cls;
+
+  GNUNET_assert (0 <=
+                 TALER_amount_add (total,
+                                   total,
+                                   amount));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Generates our final (successful) response.
+ *
+ * @param rc request context
+ * @param wc operation context
+ * @return MHD queue status
+ */
+static MHD_RESULT
+generate_reply_success (const struct TEH_RequestContext *rc,
+                        const struct BatchWithdrawContext *wc)
+{
+  json_t *sigs;
+
+  if (! wc->kyc.ok)
+  {
+    /* KYC required */
+    return TEH_RESPONSE_reply_kyc_required (rc->connection,
+                                            &wc->h_payto,
+                                            &wc->kyc);
+  }
+  if (TALER_AML_NORMAL != wc->aml_decision)
+    return TEH_RESPONSE_reply_aml_blocked (rc->connection,
+                                           wc->aml_decision);
+
+  sigs = json_array ();
+  GNUNET_assert (NULL != sigs);
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        sigs,
+        GNUNET_JSON_PACK (
+          TALER_JSON_pack_blinded_denom_sig (
+            "ev_sig",
+            &pc->collectable.sig))));
+  }
+  TEH_METRICS_batch_withdraw_num_coins += wc->planchets_length;
+  return TALER_MHD_REPLY_JSON_PACK (
+    rc->connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_array_steal ("ev_sigs",
+                                  sigs));
+}
+
+
+/**
+ * Check if the @a wc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param wc parsed request data
+ * @param[out] mret HTTP status, set if we return true
+ * @return true if the request is idempotent with an existing request
+ *    false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+check_request_idempotent (const struct BatchWithdrawContext *wc,
+                          MHD_RESULT *mret)
+{
+  const struct TEH_RequestContext *rc = wc->rc;
+
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
+                                        &pc->h_coin_envelope,
+                                        &pc->collectable);
+    if (0 > qs)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mret = TALER_MHD_reply_with_error (rc->connection,
+                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                            TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                            "get_withdraw_info");
+      return true; /* well, kind-of */
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+      return false;
+  }
+  /* generate idempotent reply */
+  TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW]++;
+  *mret = generate_reply_success (rc,
+                                  wc);
+  return true;
+}
+
+
+/**
+ * Function implementing withdraw transaction.  Runs the
+ * transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response.  IF it returns an hard error,
+ * the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again
+ * to retry and MUST not queue a MHD response.
+ *
+ * Note that "wc->collectable.sig" is set before entering this function as we
+ * signed before entering the transaction.
+ *
+ * @param cls a `struct BatchWithdrawContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+batch_withdraw_transaction (void *cls,
+                            struct MHD_Connection *connection,
+                            MHD_RESULT *mhd_ret)
+{
+  struct BatchWithdrawContext *wc = cls;
+  uint64_t ruuid;
+  enum GNUNET_DB_QueryStatus qs;
+  bool found = false;
+  bool balance_ok = false;
+  bool age_ok = false;
+  uint16_t allowed_maximum_age = 0;
+  char *kyc_required;
+  struct TALER_PaytoHashP reserve_h_payto;
+
+  wc->now = GNUNET_TIME_timestamp_get ();
+  /* Do AML check: compute total merged amount and check
+     against applicable AML threshold */
+  {
+    char *reserve_payto;
+
+    reserve_payto = TALER_reserve_make_payto (TEH_base_url,
+                                              wc->reserve_pub);
+    TALER_payto_hash (reserve_payto,
+                      &reserve_h_payto);
+    GNUNET_free (reserve_payto);
+  }
+  {
+    struct TALER_Amount merge_amount;
+    struct TALER_Amount threshold;
+    struct GNUNET_TIME_Absolute now_minus_one_month;
+
+    now_minus_one_month
+      = GNUNET_TIME_absolute_subtract (wc->now.abs_time,
+                                       GNUNET_TIME_UNIT_MONTHS);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &merge_amount));
+    qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
+                                                         &reserve_h_payto,
+                                                         now_minus_one_month,
+                                                         &aml_amount_cb,
+                                                         &merge_amount);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               
"select_merge_amounts_for_kyc_check");
+      return qs;
+    }
+    qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
+                                           &reserve_h_payto,
+                                           &wc->aml_decision,
+                                           &wc->kyc,
+                                           &threshold);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "select_aml_threshold");
+      return qs;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      threshold = TEH_aml_threshold; /* use default */
+      wc->aml_decision = TALER_AML_NORMAL;
+    }
+
+    switch (wc->aml_decision)
+    {
+    case TALER_AML_NORMAL:
+      if (0 >= TALER_amount_cmp (&merge_amount,
+                                 &threshold))
+      {
+        /* merge_amount <= threshold, continue withdraw below */
+        break;
+      }
+      wc->aml_decision = TALER_AML_PENDING;
+      qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
+                                            &reserve_h_payto,
+                                            &merge_amount);
+      if (qs <= 0)
+      {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                                 "trigger_aml_process");
+        return qs;
+      }
+      return qs;
+    case TALER_AML_PENDING:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "AML already pending, doing nothing\n");
+      return qs;
+    case TALER_AML_FROZEN:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Account frozen, doing nothing\n");
+      return qs;
+    }
+  }
+
+  /* Check if the money came from a wire transfer */
+  qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
+                                        wc->reserve_pub,
+                                        &wc->h_payto);
+  if (qs < 0)
+  {
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                             "reserves_get_origin");
+    return qs;
+  }
+  /* If no results, reserve was created by merge, in which case no KYC check
+     is required as the merge already did that. */
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    qs = TALER_KYCLOGIC_kyc_test_required (
+      TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
+      &wc->h_payto,
+      TEH_plugin->select_satisfied_kyc_processes,
+      TEH_plugin->cls,
+      &batch_withdraw_amount_cb,
+      wc,
+      &kyc_required);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "kyc_test_required");
+      return qs;
+    }
+    if (NULL != kyc_required)
+    {
+      /* insert KYC requirement into DB! */
+      wc->kyc.ok = false;
+      qs = TEH_plugin->insert_kyc_requirement_for_account (
+        TEH_plugin->cls,
+        kyc_required,
+        &wc->h_payto,
+        wc->reserve_pub,
+        &wc->kyc.requirement_row);
+      GNUNET_free (kyc_required);
+      if (qs < 0)
+      {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                                 
"insert_kyc_requirement_for_account");
+      }
+      return qs;
+    }
+  }
+  wc->kyc.ok = true;
+
+  qs = TEH_plugin->do_batch_withdraw (TEH_plugin->cls,
+                                      wc->now,
+                                      wc->reserve_pub,
+                                      &wc->batch_total,
+                                      TEH_age_restriction_enabled,
+                                      &found,
+                                      &balance_ok,
+                                      &age_ok,
+                                      &allowed_maximum_age,
+                                      &ruuid);
+  if (0 > qs)
+  {
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                             "update_reserve_batch_withdraw");
+    }
+    return qs;
+  }
+  if (! found)
+  {
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+                                           NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  if (! age_ok)
+  {
+    /* We respond with the lowest age in the corresponding age group
+     * of the required age */
+    uint16_t lowest_age = TALER_get_lowest_age (
+      &TEH_age_restriction_config.mask,
+      allowed_maximum_age);
+
+    TEH_plugin->rollback (TEH_plugin->cls);
+    *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (
+      connection,
+      lowest_age);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  if (! balance_ok)
+  {
+    TEH_plugin->rollback (TEH_plugin->cls);
+    *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
+      connection,
+      TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
+      &wc->batch_total,
+      wc->reserve_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  /* Add information about each planchet in the batch */
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+    const struct TALER_BlindedPlanchet *bp = &pc->blinded_planchet;
+    const struct TALER_CsNonce *nonce;
+    bool denom_unknown = true;
+    bool conflict = true;
+    bool nonce_reuse = true;
+
+    nonce = (TALER_DENOMINATION_CS == bp->cipher)
+            ? &bp->details.cs_blinded_planchet.nonce
+            : NULL;
+    qs = TEH_plugin->do_batch_withdraw_insert (TEH_plugin->cls,
+                                               nonce,
+                                               &pc->collectable,
+                                               wc->now,
+                                               ruuid,
+                                               &denom_unknown,
+                                               &conflict,
+                                               &nonce_reuse);
+    if (0 > qs)
+    {
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "do_withdraw");
+      return qs;
+    }
+    if (denom_unknown)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             
TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+                                             NULL);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+    if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+         (conflict) )
+    {
+      if (! check_request_idempotent (wc,
+                                      mhd_ret))
+      {
+        /* We do not support *some* of the coins of the request being
+           idempotent while others being fresh. */
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Idempotent coin in batch, not allowed. Aborting.\n");
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_CONFLICT,
+                                               
TALER_EC_EXCHANGE_WITHDRAW_BATCH_IDEMPOTENT_PLANCHET,
+                                               NULL);
+      }
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+    if (nonce_reuse)
+    {
+      GNUNET_break_op (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_BAD_REQUEST,
+                                             
TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
+                                             NULL);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+  }
+  TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW]++;
+  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * The request was parsed successfully. Prepare
+ * our side for the main DB transaction.
+ *
+ * @param rc request details
+ * @param wc storage for request processing
+ * @return MHD result for the @a rc
+ */
+static MHD_RESULT
+prepare_transaction (const struct TEH_RequestContext *rc,
+                     struct BatchWithdrawContext *wc)
+{
+  struct TEH_CoinSignData csds[wc->planchets_length];
+  struct TALER_BlindedDenominationSignature bss[wc->planchets_length];
+
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+
+    csds[i].h_denom_pub = &pc->collectable.denom_pub_hash;
+    csds[i].bp = &pc->blinded_planchet;
+  }
+  {
+    enum TALER_ErrorCode ec;
+
+    ec = TEH_keys_denomination_batch_sign (
+      csds,
+      wc->planchets_length,
+      false,
+      bss);
+    if (TALER_EC_NONE != ec)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_ec (rc->connection,
+                                      ec,
+                                      NULL);
+    }
+  }
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+
+    pc->collectable.sig = bss[i];
+  }
+
+  /* run transaction */
+  {
+    MHD_RESULT mhd_ret;
+
+    if (GNUNET_OK !=
+        TEH_DB_run_transaction (rc->connection,
+                                "run batch withdraw",
+                                TEH_MT_REQUEST_WITHDRAW,
+                                &mhd_ret,
+                                &batch_withdraw_transaction,
+                                wc))
+    {
+      return mhd_ret;
+    }
+  }
+  /* return final positive response */
+  return generate_reply_success (rc,
+                                 wc);
+}
+
+
+/**
+ * Continue processing the request @a rc by parsing the
+ * @a planchets and then running the transaction.
+ *
+ * @param rc request details
+ * @param wc storage for request processing
+ * @param planchets array of planchets to parse
+ * @return MHD result for the @a rc
+ */
+static MHD_RESULT
+parse_planchets (const struct TEH_RequestContext *rc,
+                 struct BatchWithdrawContext *wc,
+                 const json_t *planchets)
+{
+  struct TEH_KeyStateHandle *ksh;
+  MHD_RESULT mret;
+
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+    struct GNUNET_JSON_Specification ispec[] = {
+      GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                   &pc->collectable.reserve_sig),
+      GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                   &pc->collectable.denom_pub_hash),
+      TALER_JSON_spec_blinded_planchet ("coin_ev",
+                                        &pc->blinded_planchet),
+      GNUNET_JSON_spec_end ()
+    };
+
+    {
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (rc->connection,
+                                       json_array_get (planchets,
+                                                       i),
+                                       ispec);
+      if (GNUNET_OK != res)
+        return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+    }
+    pc->collectable.reserve_pub = *wc->reserve_pub;
+    for (unsigned int k = 0; k<i; k++)
+    {
+      const struct PlanchetContext *kpc = &wc->planchets[k];
+
+      if (0 ==
+          TALER_blinded_planchet_cmp (&kpc->blinded_planchet,
+                                      &pc->blinded_planchet))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (rc->connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                           "duplicate planchet");
+      }
+    }
+  }
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    if (! check_request_idempotent (wc,
+                                    &mret))
+    {
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                         NULL);
+    }
+    return mret;
+  }
+  for (unsigned int i = 0; i<wc->planchets_length; i++)
+  {
+    struct PlanchetContext *pc = &wc->planchets[i];
+    struct TEH_DenominationKey *dk;
+
+    dk = TEH_keys_denomination_by_hash_from_state (
+      ksh,
+      &pc->collectable.denom_pub_hash,
+      NULL,
+      NULL);
+
+    if (NULL == dk)
+    {
+      if (! check_request_idempotent (wc,
+                                      &mret))
+      {
+        return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+          rc->connection,
+          &pc->collectable.denom_pub_hash);
+      }
+      return mret;
+    }
+    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+    {
+      /* This denomination is past the expiration time for withdraws */
+      if (! check_request_idempotent (wc,
+                                      &mret))
+      {
+        return TEH_RESPONSE_reply_expired_denom_pub_hash (
+          rc->connection,
+          &pc->collectable.denom_pub_hash,
+          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+          "WITHDRAW");
+      }
+      return mret;
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid, no need to check
+         for idempotency! */
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &pc->collectable.denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+        "WITHDRAW");
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      if (! check_request_idempotent (wc,
+                                      &mret))
+      {
+        return TEH_RESPONSE_reply_expired_denom_pub_hash (
+          rc->connection,
+          &pc->collectable.denom_pub_hash,
+          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+          "WITHDRAW");
+      }
+      return mret;
+    }
+    if (dk->denom_pub.cipher != pc->blinded_planchet.cipher)
+    {
+      /* denomination cipher and blinded planchet cipher not the same */
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+                                         NULL);
+    }
+    if (0 >
+        TALER_amount_add (&pc->collectable.amount_with_fee,
+                          &dk->meta.value,
+                          &dk->meta.fees.withdraw))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+                                         NULL);
+    }
+    if (0 >
+        TALER_amount_add (&wc->batch_total,
+                          &wc->batch_total,
+                          &pc->collectable.amount_with_fee))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+                                         NULL);
+    }
+
+    if (GNUNET_OK !=
+        TALER_coin_ev_hash (&pc->blinded_planchet,
+                            &pc->collectable.denom_pub_hash,
+                            &pc->collectable.h_coin_envelope))
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                         NULL);
+    }
+    TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+    if (GNUNET_OK !=
+        TALER_wallet_withdraw_verify (&pc->collectable.denom_pub_hash,
+                                      &pc->collectable.amount_with_fee,
+                                      &pc->collectable.h_coin_envelope,
+                                      &pc->collectable.reserve_pub,
+                                      &pc->collectable.reserve_sig))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_FORBIDDEN,
+                                         
TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+                                         NULL);
+    }
+  }
+  /* everything parsed */
+  return prepare_transaction (rc,
+                              wc);
+}
+
+
+MHD_RESULT
+TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            const json_t *root)
+{
+  struct BatchWithdrawContext wc = {
+    .reserve_pub = reserve_pub,
+    .rc = rc
+  };
+  const json_t *planchets;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_array_const ("planchets",
+                                  &planchets),
+    GNUNET_JSON_spec_end ()
+  };
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (TEH_currency,
+                                        &wc.batch_total));
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+  }
+  wc.planchets_length = json_array_size (planchets);
+  if (0 == wc.planchets_length)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "planchets");
+  }
+  if (wc.planchets_length > TALER_MAX_FRESH_COINS)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                       "too many planchets");
+  }
+  {
+    struct PlanchetContext splanchets[wc.planchets_length];
+    MHD_RESULT ret;
+
+    memset (splanchets,
+            0,
+            sizeof (splanchets));
+    wc.planchets = splanchets;
+    ret = parse_planchets (rc,
+                           &wc,
+                           planchets);
+    /* Clean up */
+    for (unsigned int i = 0; i<wc.planchets_length; i++)
+    {
+      struct PlanchetContext *pc = &wc.planchets[i];
+
+      TALER_blinded_planchet_free (&pc->blinded_planchet);
+      TALER_blinded_denom_sig_free (&pc->collectable.sig);
+    }
+    return ret;
+  }
+}
+
+
+/* end of taler-exchange-httpd_batch-withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_batch-withdraw.h 
b/src/exchange/taler-exchange-httpd_batch-withdraw.h
new file mode 100644
index 0000000..dfc6e5a
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_batch-withdraw.h
@@ -0,0 +1,48 @@
+/*
+  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_batch-withdraw.h
+ * @brief Handle /reserve/batch-withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_BATCH_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_BATCH_WITHDRAW_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/batch-withdraw" request.  Parses the batch 
of
+ * requested "denom_pub" which specifies the key/value of the coin to be
+ * withdrawn, and checks that the signature "reserve_sig" makes this a valid
+ * withdrawal request from the specified reserve.  If so, the envelope with
+ * the blinded coin "coin_ev" is passed down to execute the withdrawal
+ * operation.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param reserve_pub public key of the reserve
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_batch_withdraw (struct TEH_RequestContext *rc,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c 
b/src/exchange/taler-exchange-httpd_common_deposit.c
new file mode 100644
index 0000000..0c91f0b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_deposit.c
@@ -0,0 +1,267 @@
+/*
+  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 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_common_deposit.c
+ * @brief shared logic for handling deposited coins
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_common_purse_deposit_parse_coin (
+  struct MHD_Connection *connection,
+  struct TEH_PurseDepositedCoin *coin,
+  const json_t *jcoin)
+{
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount ("amount",
+                            TEH_currency,
+                            &coin->amount),
+    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                 &coin->cpi.denom_pub_hash),
+    TALER_JSON_spec_denom_sig ("ub_sig",
+                               &coin->cpi.denom_sig),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("attest",
+                                   &coin->attest),
+      &coin->no_attest),
+    GNUNET_JSON_spec_mark_optional (
+      TALER_JSON_spec_age_commitment ("age_commitment",
+                                      &coin->age_commitment),
+      &coin->cpi.no_age_commitment),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin->coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &coin->cpi.coin_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  memset (coin,
+          0,
+          sizeof (*coin));
+  coin->cpi.no_age_commitment = true;
+  coin->no_attest = true;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     jcoin,
+                                     spec);
+    if (GNUNET_OK != res)
+      return res;
+  }
+
+  /* check denomination exists and is valid */
+  {
+    struct TEH_DenominationKey *dk;
+    MHD_RESULT mret;
+
+    dk = TEH_keys_denomination_by_hash (&coin->cpi.denom_pub_hash,
+                                        connection,
+                                        &mret);
+    if (NULL == dk)
+    {
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES == mret) ? GNUNET_NO : GNUNET_SYSERR;
+    }
+    if (! coin->cpi.no_age_commitment)
+    {
+      coin->age_commitment.mask = dk->meta.age_mask;
+      TALER_age_commitment_hash (&coin->age_commitment,
+                                 &coin->cpi.h_age_commitment);
+    }
+    if (0 > TALER_amount_cmp (&dk->meta.value,
+                              &coin->amount))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_BAD_REQUEST,
+                                          
TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+                                          NULL))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+    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 (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &coin->cpi.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+                "PURSE CREATE"))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &coin->cpi.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+                "PURSE CREATE"))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TEH_RESPONSE_reply_expired_denom_pub_hash (
+                connection,
+                &coin->cpi.denom_pub_hash,
+                TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+                "PURSE CREATE"))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+    if (dk->denom_pub.cipher != coin->cpi.denom_sig.cipher)
+    {
+      /* denomination cipher and denomination signature cipher not the same */
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_BAD_REQUEST,
+                                          
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+                                          NULL))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+
+    coin->deposit_fee = dk->meta.fees.deposit;
+    if (0 < TALER_amount_cmp (&coin->deposit_fee,
+                              &coin->amount))
+    {
+      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);
+    }
+    GNUNET_assert (0 <=
+                   TALER_amount_subtract (&coin->amount_minus_fee,
+                                          &coin->amount,
+                                          &coin->deposit_fee));
+
+    /* 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 (&coin->cpi,
+                               &dk->denom_pub))
+    {
+      TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
+      GNUNET_JSON_parse_free (spec);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_FORBIDDEN,
+                                          
TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+                                          NULL))
+             ? GNUNET_NO : GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_common_deposit_check_purse_deposit (
+  struct MHD_Connection *connection,
+  const struct TEH_PurseDepositedCoin *coin,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  uint32_t min_age)
+{
+  if (GNUNET_OK !=
+      TALER_wallet_purse_deposit_verify (TEH_base_url,
+                                         purse_pub,
+                                         &coin->amount,
+                                         &coin->cpi.denom_pub_hash,
+                                         &coin->cpi.h_age_commitment,
+                                         &coin->cpi.coin_pub,
+                                         &coin->coin_sig))
+  {
+    TALER_LOG_WARNING (
+      "Invalid coin signature to deposit into purse\n");
+    return (MHD_YES ==
+            TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_FORBIDDEN,
+                                        
TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_SIGNATURE_INVALID,
+                                        TEH_base_url))
+           ? GNUNET_NO
+           : GNUNET_SYSERR;
+  }
+
+  if (0 == min_age)
+    return GNUNET_OK; /* no need to apply age checks */
+
+  /* Check and verify the age restriction. */
+  if (coin->no_attest != coin->cpi.no_age_commitment)
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_CONFLICTING_ATTEST_VS_AGE_COMMITMENT,
+                                       "mismatch of attest and 
age_commitment");
+  }
+
+  if (coin->cpi.no_age_commitment)
+    return GNUNET_OK; /* unrestricted coin */
+
+  /* age attestation must be valid */
+  if (GNUNET_OK !=
+      TALER_age_commitment_verify (&coin->age_commitment,
+                                   min_age,
+                                   &coin->attest))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       
TALER_EC_EXCHANGE_PURSE_DEPOSIT_COIN_AGE_ATTESTATION_FAILURE,
+                                       "invalid attest for minimum age");
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Release data structures of @a coin. Note that
+ * @a coin itself is NOT freed.
+ *
+ * @param[in] coin information to release
+ */
+void
+TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin)
+{
+  TALER_denom_sig_free (&coin->cpi.denom_sig);
+  if (! coin->cpi.no_age_commitment)
+    GNUNET_free (coin->age_commitment.keys); /* Only the keys have been 
allocated */
+}
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.h 
b/src/exchange/taler-exchange-httpd_common_deposit.h
new file mode 100644
index 0000000..10fd7e8
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_common_deposit.h
@@ -0,0 +1,130 @@
+/*
+  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 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_common_deposit.h
+ * @brief shared logic for handling deposited coins
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_COMMON_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+
+
+/**
+ * Information about an individual coin being deposited.
+ */
+struct TEH_PurseDepositedCoin
+{
+  /**
+   * Public information about the coin.
+   */
+  struct TALER_CoinPublicInfo cpi;
+
+  /**
+   * Signature affirming spending the coin.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Amount to be put into the purse from this coin.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Deposit fee applicable for this coin.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Amount to be put into the purse from this coin.
+   */
+  struct TALER_Amount amount_minus_fee;
+
+  /**
+   * Age attestation provided, set if @e no_attest is false.
+   */
+  struct TALER_AgeAttestation attest;
+
+  /**
+   * Age commitment provided, set if @e cpi.no_age_commitment is false.
+   */
+  struct TALER_AgeCommitment age_commitment;
+
+  /**
+   * ID of the coin in known_coins.
+   */
+  uint64_t known_coin_id;
+
+  /**
+   * True if @e attest was not provided.
+   */
+  bool no_attest;
+
+};
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ *         #GNUNET_SYSERR on failure and no error could be returned
+ */
+enum GNUNET_GenericReturnValue
+TEH_common_purse_deposit_parse_coin (
+  struct MHD_Connection *connection,
+  struct TEH_PurseDepositedCoin *coin,
+  const json_t *jcoin);
+
+
+/**
+ * Check that the deposited @a coin is valid for @a purse_pub
+ * and has a valid age commitment for @a min_age.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param coin the coin to evaluate
+ * @param purse_pub public key of the purse the coin was deposited into
+ * @param min_age minimum age restriction expected for this purse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ *         #GNUNET_SYSERR on failure and no error could be returned
+ */
+enum GNUNET_GenericReturnValue
+TEH_common_deposit_check_purse_deposit (
+  struct MHD_Connection *connection,
+  const struct TEH_PurseDepositedCoin *coin,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  uint32_t min_age);
+
+
+/**
+ * Release data structures of @a coin. Note that
+ * @a coin itself is NOT freed.
+ *
+ * @param[in] coin information to release
+ */
+void
+TEH_common_purse_deposit_free_coin (struct TEH_PurseDepositedCoin *coin);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_config.c 
b/src/exchange/taler-exchange-httpd_config.c
new file mode 100644
index 0000000..da5bf96
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.c
@@ -0,0 +1,55 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_config.c
+ * @brief Handle /config requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_config.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include <jansson.h>
+
+
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+                    const char *const args[])
+{
+  static struct MHD_Response *resp;
+
+  if (NULL == resp)
+  {
+    resp = TALER_MHD_MAKE_JSON_PACK (
+      GNUNET_JSON_pack_array_steal ("supported_kyc_requirements",
+                                    TALER_KYCLOGIC_get_satisfiable ()),
+      GNUNET_JSON_pack_string ("currency",
+                               TEH_currency),
+      GNUNET_JSON_pack_string ("name",
+                               "taler-exchange"),
+      GNUNET_JSON_pack_string ("version",
+                               EXCHANGE_PROTOCOL_VERSION));
+  }
+  return MHD_queue_response (rc->connection,
+                             MHD_HTTP_OK,
+                             resp);
+}
+
+
+/* end of taler-exchange-httpd_config.c */
diff --git a/src/exchange/taler-exchange-httpd_config.h 
b/src/exchange/taler-exchange-httpd_config.h
new file mode 100644
index 0000000..ea45517
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_config.h
@@ -0,0 +1,58 @@
+/*
+  This file is part of TALER
+  (C) 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 EXCHANGEABILITY 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 taler-exchange-httpd_config.h
+ * @brief headers for /config handler
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_CONFIG_H
+#define TALER_EXCHANGE_HTTPD_CONFIG_H
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Taler protocol version in the format CURRENT:REVISION:AGE
+ * as used by GNU libtool.  See
+ * 
https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ *
+ * Please be very careful when updating and follow
+ * 
https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
+ * precisely.  Note that this version has NOTHING to do with the
+ * release version, and the format is NOT the same that semantic
+ * versioning uses either.
+ *
+ * When changing this version, you likely want to also update
+ * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
+ * exchange_api_handle.c!
+ *
+ * Returned via both /config and /keys endpoints.
+ */
+#define EXCHANGE_PROTOCOL_VERSION "17:0:0"
+
+
+/**
+ * Manages a /config call.
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+                    const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_csr.c 
b/src/exchange/taler-exchange-httpd_csr.c
new file mode 100644
index 0000000..64892d3
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_csr.c
@@ -0,0 +1,340 @@
+/*
+  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_csr.c
+ * @brief Handle /csr requests
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmles
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_csr.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+MHD_RESULT
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[])
+{
+  struct TALER_RefreshMasterSecretP rms;
+  unsigned int csr_requests_num;
+  const json_t *csr_requests;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("rms",
+                                 &rms),
+    GNUNET_JSON_spec_array_const ("nks",
+                                  &csr_requests),
+    GNUNET_JSON_spec_end ()
+  };
+  enum TALER_ErrorCode ec;
+  struct TEH_DenominationKey *dk;
+
+  (void) args;
+  /* parse input */
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+  }
+  csr_requests_num = json_array_size (csr_requests);
+  if ( (TALER_MAX_FRESH_COINS <= csr_requests_num) ||
+       (0 == csr_requests_num) )
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (
+      rc->connection,
+      MHD_HTTP_BAD_REQUEST,
+      TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
+      NULL);
+  }
+
+  {
+    struct TALER_ExchangeWithdrawValues ewvs[csr_requests_num];
+    {
+      struct TALER_CsNonce nonces[csr_requests_num];
+      struct TALER_DenominationHashP denom_pub_hashes[csr_requests_num];
+      struct TEH_CsDeriveData cdds[csr_requests_num];
+      struct TALER_DenominationCSPublicRPairP r_pubs[csr_requests_num];
+
+      for (unsigned int i = 0; i < csr_requests_num; i++)
+      {
+        uint32_t coin_off;
+        struct TALER_DenominationHashP *denom_pub_hash = &denom_pub_hashes[i];
+        struct GNUNET_JSON_Specification csr_spec[] = {
+          GNUNET_JSON_spec_uint32 ("coin_offset",
+                                   &coin_off),
+          GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                       denom_pub_hash),
+          GNUNET_JSON_spec_end ()
+        };
+        enum GNUNET_GenericReturnValue res;
+
+        res = TALER_MHD_parse_json_array (rc->connection,
+                                          csr_requests,
+                                          csr_spec,
+                                          i,
+                                          -1);
+        if (GNUNET_OK != res)
+        {
+          return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+        }
+        TALER_cs_refresh_nonce_derive (&rms,
+                                       coin_off,
+                                       &nonces[i]);
+      }
+
+      for (unsigned int i = 0; i < csr_requests_num; i++)
+      {
+        const struct TALER_CsNonce *nonce = &nonces[i];
+        const struct TALER_DenominationHashP *denom_pub_hash =
+          &denom_pub_hashes[i];
+
+        ewvs[i].cipher = TALER_DENOMINATION_CS;
+        /* check denomination referenced by denom_pub_hash */
+        {
+          struct TEH_KeyStateHandle *ksh;
+
+          ksh = TEH_keys_get_state ();
+          if (NULL == ksh)
+          {
+            return TALER_MHD_reply_with_error (rc->connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                               NULL);
+          }
+          dk = TEH_keys_denomination_by_hash_from_state (ksh,
+                                                         denom_pub_hash,
+                                                         NULL,
+                                                         NULL);
+          if (NULL == dk)
+          {
+            return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+              rc->connection,
+              &denom_pub_hash[i]);
+          }
+          if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+          {
+            /* This denomination is past the expiration time for 
withdraws/refreshes*/
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+              "csr-melt");
+          }
+          if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+          {
+            /* This denomination is not yet valid, no need to check
+               for idempotency! */
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+              "csr-melt");
+          }
+          if (dk->recoup_possible)
+          {
+            /* This denomination has been revoked */
+            return TEH_RESPONSE_reply_expired_denom_pub_hash (
+              rc->connection,
+              denom_pub_hash,
+              TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+              "csr-melt");
+          }
+          if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+          {
+            /* denomination is valid but not for CS */
+            return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+              rc->connection,
+              denom_pub_hash);
+          }
+        }
+        cdds[i].h_denom_pub = denom_pub_hash;
+        cdds[i].nonce = nonce;
+      } /* for (i) */
+      ec = TEH_keys_denomination_cs_batch_r_pub (cdds,
+                                                 csr_requests_num,
+                                                 true,
+                                                 r_pubs);
+      if (TALER_EC_NONE != ec)
+      {
+        GNUNET_break (0);
+        return TALER_MHD_reply_with_ec (rc->connection,
+                                        ec,
+                                        NULL);
+      }
+      for (unsigned int i = 0; i < csr_requests_num; i++)
+        ewvs[i].details.cs_values = r_pubs[i];
+    } /* end scope */
+
+    /* send response */
+    {
+      json_t *csr_response_ewvs;
+      json_t *csr_response;
+
+      csr_response_ewvs = json_array ();
+      for (unsigned int i = 0; i < csr_requests_num; i++)
+      {
+        json_t *csr_obj;
+
+        csr_obj = GNUNET_JSON_PACK (
+          TALER_JSON_pack_exchange_withdraw_values ("ewv",
+                                                    &ewvs[i]));
+        GNUNET_assert (NULL != csr_obj);
+        GNUNET_assert (0 ==
+                       json_array_append_new (csr_response_ewvs,
+                                              csr_obj));
+      }
+      csr_response = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_array_steal ("ewvs",
+                                      csr_response_ewvs));
+      GNUNET_assert (NULL != csr_response);
+      return TALER_MHD_reply_json_steal (rc->connection,
+                                         csr_response,
+                                         MHD_HTTP_OK);
+    }
+  }
+}
+
+
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+                          const json_t *root,
+                          const char *const args[])
+{
+  struct TALER_CsNonce nonce;
+  struct TALER_DenominationHashP denom_pub_hash;
+  struct TALER_ExchangeWithdrawValues ewv = {
+    .cipher = TALER_DENOMINATION_CS
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("nonce",
+                                 &nonce),
+    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                 &denom_pub_hash),
+    GNUNET_JSON_spec_end ()
+  };
+  struct TEH_DenominationKey *dk;
+
+  (void) args;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+  }
+
+  {
+    struct TEH_KeyStateHandle *ksh;
+
+    ksh = TEH_keys_get_state ();
+    if (NULL == ksh)
+    {
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                         NULL);
+    }
+    dk = TEH_keys_denomination_by_hash_from_state (ksh,
+                                                   &denom_pub_hash,
+                                                   NULL,
+                                                   NULL);
+    if (NULL == dk)
+    {
+      return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash);
+    }
+    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+    {
+      /* This denomination is past the expiration time for 
withdraws/refreshes*/
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+        "csr-withdraw");
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid, no need to check
+         for idempotency! */
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+        "csr-withdraw");
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+        "csr-withdraw");
+    }
+    if (TALER_DENOMINATION_CS != dk->denom_pub.cipher)
+    {
+      /* denomination is valid but not for CS */
+      return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+        rc->connection,
+        &denom_pub_hash);
+    }
+  }
+
+  /* derive r_pub */
+  {
+    enum TALER_ErrorCode ec;
+    const struct TEH_CsDeriveData cdd = {
+      .h_denom_pub = &denom_pub_hash,
+      .nonce = &nonce
+    };
+
+    ec = TEH_keys_denomination_cs_r_pub (&cdd,
+                                         false,
+                                         &ewv.details.cs_values);
+    if (TALER_EC_NONE != ec)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_ec (rc->connection,
+                                      ec,
+                                      NULL);
+    }
+  }
+
+  return TALER_MHD_REPLY_JSON_PACK (
+    rc->connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_exchange_withdraw_values ("ewv",
+                                              &ewv));
+}
+
+
+/* end of taler-exchange-httpd_csr.c */
diff --git a/src/exchange/taler-exchange-httpd_csr.h 
b/src/exchange/taler-exchange-httpd_csr.h
new file mode 100644
index 0000000..615255f
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_csr.h
@@ -0,0 +1,56 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_csr.h
+ * @brief Handle /csr-* requests
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmles
+ */
+#ifndef TALER_EXCHANGE_HTTPD_CSR_H
+#define TALER_EXCHANGE_HTTPD_CSR_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/csr-melt" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_csr_melt (struct TEH_RequestContext *rc,
+                      const json_t *root,
+                      const char *const args[]);
+
+
+/**
+ * Handle a "/csr-withdraw" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_csr_withdraw (struct TEH_RequestContext *rc,
+                          const json_t *root,
+                          const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_db.c 
b/src/exchange/taler-exchange-httpd_db.c
new file mode 100644
index 0000000..5660074
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_db.c
@@ -0,0 +1,173 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017, 2021 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 taler-exchange-httpd_db.c
+ * @brief Generic database operations for the exchange.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <pthread.h>
+#include <jansson.h>
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_db.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
+                     struct MHD_Connection *connection,
+                     uint64_t *known_coin_id,
+                     MHD_RESULT *mhd_ret)
+{
+  enum TALER_EXCHANGEDB_CoinKnownStatus cks;
+  struct TALER_DenominationHashP h_denom_pub;
+  struct TALER_AgeCommitmentHash age_hash;
+
+  /* make sure coin is 'known' in database */
+  cks = TEH_plugin->ensure_coin_known (TEH_plugin->cls,
+                                       coin,
+                                       known_coin_id,
+                                       &h_denom_pub,
+                                       &age_hash);
+  switch (cks)
+  {
+  case TALER_EXCHANGEDB_CKS_ADDED:
+    return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  case TALER_EXCHANGEDB_CKS_PRESENT:
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  case TALER_EXCHANGEDB_CKS_SOFT_FAIL:
+    return GNUNET_DB_STATUS_SOFT_ERROR;
+  case TALER_EXCHANGEDB_CKS_HARD_FAIL:
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_STORE_FAILED,
+                                    NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case TALER_EXCHANGEDB_CKS_DENOM_CONFLICT:
+    /* FIXME: insufficient_funds != denom conflict! See issue #7267, need new
+     * strategy for evidence gathering */
+    *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+      connection,
+      TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+      &h_denom_pub,
+      &coin->coin_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case TALER_EXCHANGEDB_CKS_AGE_CONFLICT:
+    /* FIXME: insufficient_funds != Age conflict! See issue #7267, need new
+     * strategy for evidence gathering */
+    *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+      connection,
+      TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_AGE_HASH,
+      &h_denom_pub,
+      &coin->coin_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  GNUNET_assert (0);
+  return GNUNET_DB_STATUS_HARD_ERROR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_DB_run_transaction (struct MHD_Connection *connection,
+                        const char *name,
+                        enum TEH_MetricTypeRequest mt,
+                        MHD_RESULT *mhd_ret,
+                        TEH_DB_TransactionCallback cb,
+                        void *cb_cls)
+{
+  if (NULL != mhd_ret)
+    *mhd_ret = -1; /* set to invalid value, to help detect bugs */
+  if (GNUNET_OK !=
+      TEH_plugin->preflight (TEH_plugin->cls))
+  {
+    GNUNET_break (0);
+    if (NULL != mhd_ret)
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_SETUP_FAILED,
+                                             NULL);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_assert (mt < TEH_MT_REQUEST_COUNT);
+  TEH_METRICS_num_requests[mt]++;
+  for (unsigned int retries = 0;
+       retries < MAX_TRANSACTION_COMMIT_RETRIES;
+       retries++)
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    if (GNUNET_OK !=
+        TEH_plugin->start (TEH_plugin->cls,
+                           name))
+    {
+      GNUNET_break (0);
+      if (NULL != mhd_ret)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_START_FAILED,
+                                               NULL);
+      return GNUNET_SYSERR;
+    }
+    qs = cb (cb_cls,
+             connection,
+             mhd_ret);
+    if (0 > qs)
+    {
+      TEH_plugin->rollback (TEH_plugin->cls);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        return GNUNET_SYSERR;
+    }
+    else
+    {
+      qs = TEH_plugin->commit (TEH_plugin->cls);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      {
+        TEH_plugin->rollback (TEH_plugin->cls);
+        if (NULL != mhd_ret)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_COMMIT_FAILED,
+                                                 NULL);
+        return GNUNET_SYSERR;
+      }
+      if (0 > qs)
+        TEH_plugin->rollback (TEH_plugin->cls);
+    }
+    /* make sure callback did not violate invariants! */
+    GNUNET_assert ( (NULL == mhd_ret) ||
+                    (-1 == (int) *mhd_ret) );
+    if (0 <= qs)
+      return GNUNET_OK;
+    TEH_METRICS_num_conflict[mt]++;
+  }
+  TEH_plugin->rollback (TEH_plugin->cls);
+  TALER_LOG_ERROR ("Transaction `%s' commit failed %u times\n",
+                   name,
+                   MAX_TRANSACTION_COMMIT_RETRIES);
+  if (NULL != mhd_ret)
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                                           NULL);
+  return GNUNET_SYSERR;
+}
+
+
+/* end of taler-exchange-httpd_db.c */
diff --git a/src/exchange/taler-exchange-httpd_db.h 
b/src/exchange/taler-exchange-httpd_db.h
new file mode 100644
index 0000000..482bc59
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_db.h
@@ -0,0 +1,106 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017 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 exchange/taler-exchange-httpd_db.h
+ * @brief High-level (transactional-layer) database operations for the exchange
+ * @author Chrisitan Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_DB_H
+#define TALER_EXCHANGE_HTTPD_DB_H
+
+#include <microhttpd.h>
+#include "taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_metrics.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * How often should we retry a transaction before giving up
+ * (for transactions resulting in serialization/dead locks only).
+ *
+ * The current value is likely too high for production. We might want to
+ * benchmark good values once we have a good database setup.  The code is
+ * expected to work correctly with any positive value, albeit inefficiently if
+ * we too aggressively force clients to retry the HTTP request merely because
+ * we have database serialization issues.
+ */
+#define MAX_TRANSACTION_COMMIT_RETRIES 100
+
+
+/**
+ * Ensure coin is known in the database, and handle conflicts and errors.
+ *
+ * @param coin the coin to make known
+ * @param connection MHD request context
+ * @param[out] known_coin_id set to the unique ID for the coin in the DB
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status, negative on error (@a mhd_ret will be set in 
this case)
+ */
+enum GNUNET_DB_QueryStatus
+TEH_make_coin_known (const struct TALER_CoinPublicInfo *coin,
+                     struct MHD_Connection *connection,
+                     uint64_t *known_coin_id,
+                     MHD_RESULT *mhd_ret);
+
+
+/**
+ * Function implementing a database transaction.  Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response.  IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret.  IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!)
+ * @return transaction status
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*TEH_DB_TransactionCallback)(void *cls,
+                              struct MHD_Connection *connection,
+                              MHD_RESULT *mhd_ret);
+
+
+/**
+ * Run a database transaction for @a connection.
+ * Starts a transaction and calls @a cb.  Upon success,
+ * attempts to commit the transaction.  Upon soft failures,
+ * retries @a cb a few times.  Upon hard or persistent soft
+ * errors, generates an error message for @a connection.
+ *
+ * @param connection MHD connection to run @a cb for, can be NULL
+ * @param name name of the transaction (for debugging)
+ * @param mt type of the requests, for metric generation
+ * @param[out] mhd_ret set to MHD response code, if transaction failed 
(returned #GNUNET_SYSERR);
+ *             NULL if we are not running with a @a connection and thus
+ *             must not queue MHD replies
+ * @param cb callback implementing transaction logic
+ * @param cb_cls closure for @a cb, must be read-only!
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_DB_run_transaction (struct MHD_Connection *connection,
+                        const char *name,
+                        enum TEH_MetricTypeRequest mt,
+                        MHD_RESULT *mhd_ret,
+                        TEH_DB_TransactionCallback cb,
+                        void *cb_cls);
+
+
+#endif
+/* TALER_EXCHANGE_HTTPD_DB_H */
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c 
b/src/exchange/taler-exchange-httpd_deposits_get.c
new file mode 100644
index 0000000..818900c
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_deposits_get.c
@@ -0,0 +1,519 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_deposits_get.c
+ * @brief Handle wire deposit tracking-related requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_dbevents.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_signatures.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_deposits_get.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for #handle_wtid_data.
+ */
+struct DepositWtidContext
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DepositWtidContext *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct DepositWtidContext *prev;
+
+  /**
+   * Context for the request we are processing.
+   */
+  struct TEH_RequestContext *rc;
+
+  /**
+   * Subscription for the database event we are waiting for.
+   */
+  struct GNUNET_DB_EventHandler *eh;
+
+  /**
+   * Hash over the proposal data of the contract for which this deposit is 
made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire;
+
+  /**
+   * The Merchant's public key.  The deposit inquiry request is to be
+   * signed by the corresponding private key (using EdDSA).
+   */
+  struct TALER_MerchantPublicKeyP merchant;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Set by #handle_wtid data to the wire transfer ID.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * Signature by the merchant.
+   */
+  struct TALER_MerchantSignatureP merchant_sig;
+
+
+  /**
+   * Set by #handle_wtid data to the coin's contribution to the wire transfer.
+   */
+  struct TALER_Amount coin_contribution;
+
+  /**
+   * Set by #handle_wtid data to the fee charged to the coin.
+   */
+  struct TALER_Amount coin_fee;
+
+  /**
+   * Set by #handle_wtid data to the wire transfer execution time.
+   */
+  struct GNUNET_TIME_Timestamp execution_time;
+
+  /**
+   * Timeout of the request, for long-polling.
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Set by #handle_wtid to the coin contribution to the transaction
+   * (that is, @e coin_contribution minus @e coin_fee).
+   */
+  struct TALER_Amount coin_delta;
+
+  /**
+   * KYC status information for the receiving account.
+   */
+  struct TALER_EXCHANGEDB_KycStatus kyc;
+
+  /**
+   * AML status information for the receiving account.
+   */
+  enum TALER_AmlDecisionState aml_decision;
+
+  /**
+   * Set to #GNUNET_YES by #handle_wtid if the wire transfer is still pending
+   * (and the above were not set).
+   * Set to #GNUNET_SYSERR if there was a serious error.
+   */
+  enum GNUNET_GenericReturnValue pending;
+
+  /**
+   * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
+   * if we were woken up due to shutdown.
+   */
+  enum GNUNET_GenericReturnValue suspended;
+};
+
+
+/**
+ * Head of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_head;
+
+/**
+ * Tail of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_tail;
+
+
+void
+TEH_deposits_get_cleanup ()
+{
+  struct DepositWtidContext *n;
+  for (struct DepositWtidContext *ctx = dwc_head;
+       NULL != ctx;
+       ctx = n)
+  {
+    n = ctx->next;
+    GNUNET_assert (GNUNET_YES == ctx->suspended);
+    ctx->suspended = GNUNET_SYSERR;
+    MHD_resume_connection (ctx->rc->connection);
+    GNUNET_CONTAINER_DLL_remove (dwc_head,
+                                 dwc_tail,
+                                 ctx);
+  }
+}
+
+
+/**
+ * A merchant asked for details about a deposit.  Provide
+ * them. Generates the 200 reply.
+ *
+ * @param connection connection to the client
+ * @param ctx details to respond with
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_deposit_details (
+  struct MHD_Connection *connection,
+  const struct DepositWtidContext *ctx)
+{
+  struct TALER_ExchangePublicKeyP pub;
+  struct TALER_ExchangeSignatureP sig;
+  enum TALER_ErrorCode ec;
+
+  if (TALER_EC_NONE !=
+      (ec = TALER_exchange_online_confirm_wire_sign (
+         &TEH_keys_exchange_sign_,
+         &ctx->h_wire,
+         &ctx->h_contract_terms,
+         &ctx->wtid,
+         &ctx->coin_pub,
+         ctx->execution_time,
+         &ctx->coin_delta,
+         &pub,
+         &sig)))
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_ec (connection,
+                                    ec,
+                                    NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_data_auto ("wtid",
+                                &ctx->wtid),
+    GNUNET_JSON_pack_timestamp ("execution_time",
+                                ctx->execution_time),
+    TALER_JSON_pack_amount ("coin_contribution",
+                            &ctx->coin_delta),
+    GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                &sig),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &pub));
+}
+
+
+/**
+ * Execute a "deposits" GET.  Returns the transfer information
+ * associated with the given deposit.
+ *
+ * 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 closure of type `struct DepositWtidContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposits_get_transaction (void *cls,
+                          struct MHD_Connection *connection,
+                          MHD_RESULT *mhd_ret)
+{
+  struct DepositWtidContext *ctx = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  bool pending;
+  struct TALER_Amount fee;
+
+  qs = TEH_plugin->lookup_transfer_by_deposit (TEH_plugin->cls,
+                                               &ctx->h_contract_terms,
+                                               &ctx->h_wire,
+                                               &ctx->coin_pub,
+                                               &ctx->merchant,
+                                               &pending,
+                                               &ctx->wtid,
+                                               &ctx->execution_time,
+                                               &ctx->coin_contribution,
+                                               &fee,
+                                               &ctx->kyc,
+                                               &ctx->aml_decision);
+  if (0 > qs)
+  {
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                             NULL);
+    }
+    return qs;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           
TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
+                                           NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  if (0 >
+      TALER_amount_subtract (&ctx->coin_delta,
+                             &ctx->coin_contribution,
+                             &fee))
+  {
+    GNUNET_break (0);
+    ctx->pending = GNUNET_SYSERR;
+    return qs;
+  }
+  ctx->pending = (pending) ? GNUNET_YES : GNUNET_NO;
+  return qs;
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct DepositWtidContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+             const void *extra,
+             size_t extra_size)
+{
+  struct DepositWtidContext *ctx = cls;
+  struct GNUNET_AsyncScopeSave old_scope;
+
+  (void) extra;
+  (void) extra_size;
+  if (GNUNET_NO != ctx->suspended)
+    return; /* might get multiple wake-up events */
+  GNUNET_CONTAINER_DLL_remove (dwc_head,
+                               dwc_tail,
+                               ctx);
+  GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
+                            &old_scope);
+  TEH_check_invariants ();
+  ctx->suspended = GNUNET_NO;
+  MHD_resume_connection (ctx->rc->connection);
+  TALER_MHD_daemon_trigger ();
+  TEH_check_invariants ();
+  GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * Lookup and return the wire transfer identifier.
+ *
+ * @param ctx context of the signed request to execute
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_track_transaction_request (
+  struct DepositWtidContext *ctx)
+{
+  struct MHD_Connection *connection = ctx->rc->connection;
+
+  if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+       (NULL == ctx->eh) )
+  {
+    struct TALER_CoinDepositEventP rep = {
+      .header.size = htons (sizeof (rep)),
+      .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+      .merchant_pub = ctx->merchant
+    };
+
+    ctx->eh = TEH_plugin->event_listen (
+      TEH_plugin->cls,
+      GNUNET_TIME_absolute_get_remaining (ctx->timeout),
+      &rep.header,
+      &db_event_cb,
+      ctx);
+  }
+  {
+    MHD_RESULT mhd_ret;
+
+    if (GNUNET_OK !=
+        TEH_DB_run_transaction (connection,
+                                "handle deposits GET",
+                                TEH_MT_REQUEST_OTHER,
+                                &mhd_ret,
+                                &deposits_get_transaction,
+                                ctx))
+      return mhd_ret;
+  }
+  if (GNUNET_SYSERR == ctx->pending)
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+                                       "wire fees exceed aggregate in 
database");
+  if (GNUNET_YES == ctx->pending)
+  {
+    if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+         (GNUNET_NO == ctx->suspended) )
+    {
+      GNUNET_CONTAINER_DLL_insert (dwc_head,
+                                   dwc_tail,
+                                   ctx);
+      ctx->suspended = GNUNET_YES;
+      MHD_suspend_connection (connection);
+      return MHD_YES;
+    }
+    return TALER_MHD_REPLY_JSON_PACK (
+      connection,
+      MHD_HTTP_ACCEPTED,
+      GNUNET_JSON_pack_allow_null (
+        (0 == ctx->kyc.requirement_row)
+        ? GNUNET_JSON_pack_string ("requirement_row",
+                                   NULL)
+        : GNUNET_JSON_pack_uint64 ("requirement_row",
+                                   ctx->kyc.requirement_row)),
+      GNUNET_JSON_pack_uint64 ("aml_decision",
+                               (uint32_t) ctx->aml_decision),
+      GNUNET_JSON_pack_bool ("kyc_ok",
+                             ctx->kyc.ok),
+      GNUNET_JSON_pack_timestamp ("execution_time",
+                                  ctx->execution_time));
+  }
+  return reply_deposit_details (connection,
+                                ctx);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+dwc_cleaner (struct TEH_RequestContext *rc)
+{
+  struct DepositWtidContext *ctx = rc->rh_ctx;
+
+  GNUNET_assert (GNUNET_NO == ctx->suspended);
+  if (NULL != ctx->eh)
+  {
+    TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+                                     ctx->eh);
+    ctx->eh = NULL;
+  }
+  GNUNET_free (ctx);
+}
+
+
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
+                          const char *const args[4])
+{
+  struct DepositWtidContext *ctx = rc->rh_ctx;
+
+  if (NULL == ctx)
+  {
+    ctx = GNUNET_new (struct DepositWtidContext);
+    ctx->rc = rc;
+    rc->rh_ctx = ctx;
+    rc->rh_cleaner = &dwc_cleaner;
+
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &ctx->h_wire,
+                                       sizeof (ctx->h_wire)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
+                                         args[0]);
+    }
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[1],
+                                       strlen (args[1]),
+                                       &ctx->merchant,
+                                       sizeof (ctx->merchant)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
+                                         args[1]);
+    }
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[2],
+                                       strlen (args[2]),
+                                       &ctx->h_contract_terms,
+                                       sizeof (ctx->h_contract_terms)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
+                                         args[2]);
+    }
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[3],
+                                       strlen (args[3]),
+                                       &ctx->coin_pub,
+                                       sizeof (ctx->coin_pub)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
+                                         args[3]);
+    }
+    TALER_MHD_parse_request_arg_auto_t (rc->connection,
+                                        "merchant_sig",
+                                        &ctx->merchant_sig);
+    TALER_MHD_parse_request_timeout (rc->connection,
+                                     &ctx->timeout);
+    TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+    {
+      if (GNUNET_OK !=
+          TALER_merchant_deposit_verify (&ctx->merchant,
+                                         &ctx->coin_pub,
+                                         &ctx->h_contract_terms,
+                                         &ctx->h_wire,
+                                         &ctx->merchant_sig))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (rc->connection,
+                                           MHD_HTTP_FORBIDDEN,
+                                           
TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
+                                           NULL);
+      }
+    }
+  }
+
+  return handle_track_transaction_request (ctx);
+}
+
+
+/* end of taler-exchange-httpd_deposits_get.c */
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.h 
b/src/exchange/taler-exchange-httpd_deposits_get.h
new file mode 100644
index 0000000..c7b1698
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_deposits_get.h
@@ -0,0 +1,50 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2017, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_deposits_get.h
+ * @brief Handle wire transfer tracking-related requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_DEPOSITS_GET_H
+#define TALER_EXCHANGE_HTTPD_DEPOSITS_GET_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume long pollers on GET /deposits.
+ */
+void
+TEH_deposits_get_cleanup (void);
+
+
+/**
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 4, contains:
+ *      h_wire, merchant_pub, h_contract_terms and coin_pub)
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
+                          const char *const args[4]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_keys.c 
b/src/exchange/taler-exchange-httpd_keys.c
new file mode 100644
index 0000000..2389a21
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_keys.c
@@ -0,0 +1,4377 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2020-2023 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Affero General Public License as published by the Free 
Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+   You should have received a copy of the GNU Affero General Public License 
along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file taler-exchange-httpd_keys.c
+ * @brief management of our various keys
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_config.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_extensions.h"
+
+
+/**
+ * How many /keys request do we hold in suspension at
+ * most at any time?
+ */
+#define SKR_LIMIT 32
+
+
+/**
+ * When do we forcefully timeout a /keys request?
+ */
+#define KEYS_TIMEOUT GNUNET_TIME_UNIT_MINUTES
+
+
+/**
+ * Information about a denomination on offer by the denomination helper.
+ */
+struct HelperDenomination
+{
+
+  /**
+   * When will the helper start to use this key for signing?
+   */
+  struct GNUNET_TIME_Timestamp start_time;
+
+  /**
+   * For how long will the helper allow signing? 0 if
+   * the key was revoked or purged.
+   */
+  struct GNUNET_TIME_Relative validity_duration;
+
+  /**
+   * Hash of the full denomination key.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Signature over this key from the security module's key.
+   */
+  struct TALER_SecurityModuleSignatureP sm_sig;
+
+  /**
+   * The (full) public key.
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Details depend on the @e denom_pub.cipher type.
+   */
+  union
+  {
+
+    /**
+     * Hash of the RSA key.
+     */
+    struct TALER_RsaPubHashP h_rsa;
+
+    /**
+     * Hash of the CS key.
+     */
+    struct TALER_CsPubHashP h_cs;
+
+  } h_details;
+
+  /**
+   * Name in configuration section for this denomination type.
+   */
+  char *section_name;
+
+
+};
+
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature
+{
+  /**
+   * We store the signatures in a DLL.
+   */
+  struct TEH_AuditorSignature *prev;
+
+  /**
+   * We store the signatures in a DLL.
+   */
+  struct TEH_AuditorSignature *next;
+
+  /**
+   * A signature from the auditor.
+   */
+  struct TALER_AuditorSignatureP asig;
+
+  /**
+   * Public key of the auditor.
+   */
+  struct TALER_AuditorPublicKeyP apub;
+
+};
+
+
+/**
+ * Information about a signing key on offer by the esign helper.
+ */
+struct HelperSignkey
+{
+  /**
+   * When will the helper start to use this key for signing?
+   */
+  struct GNUNET_TIME_Timestamp start_time;
+
+  /**
+   * For how long will the helper allow signing? 0 if
+   * the key was revoked or purged.
+   */
+  struct GNUNET_TIME_Relative validity_duration;
+
+  /**
+   * The public key.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature over this key from the security module's key.
+   */
+  struct TALER_SecurityModuleSignatureP sm_sig;
+
+};
+
+
+/**
+ * State associated with the crypto helpers / security modules.  NOT updated
+ * when the #key_generation is updated (instead constantly kept in sync
+ * whenever #TEH_keys_get_state() is called).
+ */
+struct HelperState
+{
+
+  /**
+   * Handle for the esign/EdDSA helper.
+   */
+  struct TALER_CRYPTO_ExchangeSignHelper *esh;
+
+  /**
+   * Handle for the denom/RSA helper.
+   */
+  struct TALER_CRYPTO_RsaDenominationHelper *rsadh;
+
+  /**
+   * Handle for the denom/CS helper.
+   */
+  struct TALER_CRYPTO_CsDenominationHelper *csdh;
+
+  /**
+   * Map from H(denom_pub) to `struct HelperDenomination` entries.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *denom_keys;
+
+  /**
+   * Map from H(rsa_pub) to `struct HelperDenomination` entries.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *rsa_keys;
+
+  /**
+   * Map from H(cs_pub) to `struct HelperDenomination` entries.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *cs_keys;
+
+  /**
+   * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey`
+   * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also
+   * an EdDSA public key.
+   */
+  struct GNUNET_CONTAINER_MultiPeerMap *esign_keys;
+
+};
+
+
+/**
+ * Entry in (sorted) array with possible pre-build responses for /keys.
+ * We keep pre-build responses for the various (valid) cherry-picking
+ * values around.
+ */
+struct KeysResponseData
+{
+
+  /**
+   * Response to return if the client supports (deflate) compression.
+   */
+  struct MHD_Response *response_compressed;
+
+  /**
+   * Response to return if the client does not support compression.
+   */
+  struct MHD_Response *response_uncompressed;
+
+  /**
+   * ETag for these responses.
+   */
+  char *etag;
+
+  /**
+   * Cherry-picking timestamp the client must have set for this
+   * response to be valid.  0 if this is the "full" response.
+   * The client's request must include this date or a higher one
+   * for this response to be applicable.
+   */
+  struct GNUNET_TIME_Timestamp cherry_pick_date;
+
+};
+
+
+/**
+ * @brief All information about an exchange online signing key (which is used 
to
+ * sign messages from the exchange).
+ */
+struct SigningKey
+{
+
+  /**
+   * The exchange's (online signing) public key.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Meta data about the signing key, such as validity periods.
+   */
+  struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+  /**
+   * The long-term offline master key's signature for this signing key.
+   * Signs over @e exchange_pub and @e meta.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+};
+
+struct TEH_KeyStateHandle
+{
+
+  /**
+   * Mapping from denomination keys to denomination key issue struct.
+   * Used to lookup the key by hash.
+   */
+  struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
+
+  /**
+   * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey`
+   * entries.  Based on the fact that a `struct GNUNET_PeerIdentity` is also
+   * an EdDSA public key.
+   */
+  struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;
+
+  /**
+   * Head of DLL of our global fees.
+   */
+  struct TEH_GlobalFee *gf_head;
+
+  /**
+   * Tail of DLL of our global fees.
+   */
+  struct TEH_GlobalFee *gf_tail;
+
+  /**
+   * json array with the auditors of this exchange. Contains exactly
+   * the information needed for the "auditors" field of the /keys response.
+   */
+  json_t *auditors;
+
+  /**
+   * json array with the global fees of this exchange. Contains exactly
+   * the information needed for the "global_fees" field of the /keys response.
+   */
+  json_t *global_fees;
+
+  /**
+   * Sorted array of responses to /keys (MUST be sorted by cherry-picking 
date) of
+   * length @e krd_array_length;
+   */
+  struct KeysResponseData *krd_array;
+
+  /**
+   * Length of the @e krd_array.
+   */
+  unsigned int krd_array_length;
+
+  /**
+   * Information we track for thecrypto helpers.  Preserved
+   * when the @e key_generation changes, thus kept separate.
+   */
+  struct HelperState *helpers;
+
+  /**
+   * Cached reply for a GET /management/keys request.  Used so we do not
+   * re-create the reply every time.
+   */
+  json_t *management_keys_reply;
+
+  /**
+   * For which (global) key_generation was this data structure created?
+   * Used to check when we are outdated and need to be re-generated.
+   */
+  uint64_t key_generation;
+
+  /**
+   * When did we initiate the key reloading?
+   */
+  struct GNUNET_TIME_Timestamp reload_time;
+
+  /**
+   * What is the period at which we rotate keys
+   * (signing or denomination keys)?
+   */
+  struct GNUNET_TIME_Relative rekey_frequency;
+
+  /**
+   * When does our online signing key expire and we
+   * thus need to re-generate this response?
+   */
+  struct GNUNET_TIME_Timestamp signature_expires;
+
+  /**
+   * True if #finish_keys_response() was not yet run and this key state
+   * is only suitable for the /management/keys API.
+   */
+  bool management_only;
+
+};
+
+
+/**
+ * Entry of /keys requests that are currently suspended because we are
+ * waiting for /keys to become ready.
+ */
+struct SuspendedKeysRequests
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct SuspendedKeysRequests *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct SuspendedKeysRequests *prev;
+
+  /**
+   * The suspended connection.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * When does this request timeout?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+};
+
+
+/**
+ * Information we track about wire fees.
+ */
+struct WireFeeSet
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireFeeSet *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct WireFeeSet *prev;
+
+  /**
+   * Actual fees.
+   */
+  struct TALER_WireFeeSet fees;
+
+  /**
+   * Start date of fee validity (inclusive).
+   */
+  struct GNUNET_TIME_Timestamp start_date;
+
+  /**
+   * End date of fee validity (exclusive).
+   */
+  struct GNUNET_TIME_Timestamp end_date;
+
+  /**
+   * Wire method the fees apply to.
+   */
+  char *method;
+};
+
+
+/**
+ * State we keep per thread to cache the /wire response.
+ */
+struct WireStateHandle
+{
+  /**
+   * Cached reply for /wire response.
+   */
+  struct MHD_Response *wire_reply;
+
+  /**
+   * JSON reply for /wire response.
+   */
+  json_t *json_reply;
+
+  /**
+   * ETag for this response (if any).
+   */
+  char *etag;
+
+  /**
+   * head of DLL of wire fees.
+   */
+  struct WireFeeSet *wfs_head;
+
+  /**
+   * Tail of DLL of wire fees.
+   */
+  struct WireFeeSet *wfs_tail;
+
+  /**
+   * Earliest timestamp of all the wire methods when we have no more fees.
+   */
+  struct GNUNET_TIME_Absolute cache_expiration;
+
+  /**
+   * @e cache_expiration time, formatted.
+   */
+  char dat[128];
+
+  /**
+   * For which (global) wire_generation was this data structure created?
+   * Used to check when we are outdated and need to be re-generated.
+   */
+  uint64_t wire_generation;
+
+  /**
+   * HTTP status to return with this response.
+   */
+  unsigned int http_status;
+
+};
+
+
+/**
+ * Stores the latest generation of our wire response.
+ */
+static struct WireStateHandle *wire_state;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *wire_eh;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the #wire_state
+ * because something external changed.
+ */
+static uint64_t wire_generation;
+
+
+/**
+ * Stores the latest generation of our key state.
+ */
+static struct TEH_KeyStateHandle *key_state;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the keys because
+ * something external changed.  See #TEH_keys_get_state() and
+ * #TEH_keys_update_states() for uses of this variable.
+ */
+static uint64_t key_generation;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *keys_eh;
+
+/**
+ * Head of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_head;
+
+/**
+ * Tail of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_tail;
+
+/**
+ * Number of entries in the @e skr_head DLL.
+ */
+static unsigned int skr_size;
+
+/**
+ * Handle to a connection that should be force-resumed
+ * with a hard error due to @a skr_size hitting
+ * #SKR_LIMIT.
+ */
+static struct MHD_Connection *skr_connection;
+
+/**
+ * Task to force timeouts on /keys requests.
+ */
+static struct GNUNET_SCHEDULER_Task *keys_tt;
+
+/**
+ * For how long should a signing key be legally retained?
+ * Configuration value.
+ */
+static struct GNUNET_TIME_Relative signkey_legal_duration;
+
+/**
+ * What type of asset are we dealing with here?
+ */
+static char *asset_type;
+
+/**
+ * RSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;
+
+/**
+ * CS security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub;
+
+/**
+ * EdDSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
+
+/**
+ * Are we shutting down?
+ */
+static bool terminating;
+
+
+/**
+ * Free memory associated with @a wsh
+ *
+ * @param[in] wsh wire state to destroy
+ */
+static void
+destroy_wire_state (struct WireStateHandle *wsh)
+{
+  struct WireFeeSet *wfs;
+
+  while (NULL != (wfs = wsh->wfs_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
+                                 wsh->wfs_tail,
+                                 wfs);
+    GNUNET_free (wfs->method);
+    GNUNET_free (wfs);
+  }
+  MHD_destroy_response (wsh->wire_reply);
+  json_decref (wsh->json_reply);
+  GNUNET_free (wsh->etag);
+  GNUNET_free (wsh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the wire data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+wire_update_event_cb (void *cls,
+                      const void *extra,
+                      size_t extra_size)
+{
+  (void) cls;
+  (void) extra;
+  (void) extra_size;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Received /wire update event\n");
+  TEH_check_invariants ();
+  wire_generation++;
+  key_generation++;
+  TEH_resume_keys_requests (false);
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_wire_init ()
+{
+  struct GNUNET_DB_EventHeaderP es = {
+    .size = htons (sizeof (es)),
+    .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+  };
+
+  wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+                                      GNUNET_TIME_UNIT_FOREVER_REL,
+                                      &es,
+                                      &wire_update_event_cb,
+                                      NULL);
+  if (NULL == wire_eh)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+void
+TEH_wire_done ()
+{
+  if (NULL != wire_state)
+  {
+    destroy_wire_state (wire_state);
+    wire_state = NULL;
+  }
+  if (NULL != wire_eh)
+  {
+    TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+                                     wire_eh);
+    wire_eh = NULL;
+  }
+}
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `json_t *` object to expand with wire account details
+ * @param payto_uri the exchange bank account URI to add
+ * @param conversion_url URL of a conversion service, NULL if there is no 
conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the 
account
+ * @param master_sig master key signature affirming that this is a bank
+ *                   account of the exchange (of purpose 
#TALER_SIGNATURE_MASTER_WIRE_DETAILS)
+ */
+static void
+add_wire_account (void *cls,
+                  const char *payto_uri,
+                  const char *conversion_url,
+                  const json_t *debit_restrictions,
+                  const json_t *credit_restrictions,
+                  const struct TALER_MasterSignatureP *master_sig)
+{
+  json_t *a = cls;
+
+  if (GNUNET_OK !=
+      TALER_exchange_wire_signature_check (
+        payto_uri,
+        conversion_url,
+        debit_restrictions,
+        credit_restrictions,
+        &TEH_master_public_key,
+        master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database has wire account with invalid signature. Skipping 
entry. Did the exchange offline public key change?\n");
+    return;
+  }
+  if (0 !=
+      json_array_append_new (
+        a,
+        GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("payto_uri",
+                                   payto_uri),
+          GNUNET_JSON_pack_allow_null (
+            GNUNET_JSON_pack_string ("conversion_url",
+                                     conversion_url)),
+          GNUNET_JSON_pack_array_incref ("debit_restrictions",
+                                         (json_t *) debit_restrictions),
+          GNUNET_JSON_pack_array_incref ("credit_restrictions",
+                                         (json_t *) credit_restrictions),
+          GNUNET_JSON_pack_data_auto ("master_sig",
+                                      master_sig))))
+  {
+    GNUNET_break (0);   /* out of memory!? */
+    return;
+  }
+}
+
+
+/**
+ * Closure for #add_wire_fee().
+ */
+struct AddContext
+{
+  /**
+   * Wire method the fees are for.
+   */
+  char *wire_method;
+
+  /**
+   * Wire state we are building.
+   */
+  struct WireStateHandle *wsh;
+
+  /**
+   * Array to append the fee to.
+   */
+  json_t *a;
+
+  /**
+   * Context we hash "everything" we add into. This is used
+   * to compute the etag. Technically, we only hash the
+   * master_sigs, as they imply the rest.
+   */
+  struct GNUNET_HashContext *hc;
+
+  /**
+   * Set to the maximum end-date seen.
+   */
+  struct GNUNET_TIME_Absolute max_seen;
+};
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `struct AddContext`
+ * @param fees the wire fees we charge
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ *                   fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
+ */
+static void
+add_wire_fee (void *cls,
+              const struct TALER_WireFeeSet *fees,
+              struct GNUNET_TIME_Timestamp start_date,
+              struct GNUNET_TIME_Timestamp end_date,
+              const struct TALER_MasterSignatureP *master_sig)
+{
+  struct AddContext *ac = cls;
+  struct WireFeeSet *wfs;
+
+  if (GNUNET_OK !=
+      TALER_exchange_offline_wire_fee_verify (
+        ac->wire_method,
+        start_date,
+        end_date,
+        fees,
+        &TEH_master_public_key,
+        master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database has wire fee with invalid signature. Skipping entry. 
Did the exchange offline public key change?\n");
+    return;
+  }
+  GNUNET_CRYPTO_hash_context_read (ac->hc,
+                                   master_sig,
+                                   sizeof (*master_sig));
+  ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
+                                           end_date.abs_time);
+  wfs = GNUNET_new (struct WireFeeSet);
+  wfs->start_date = start_date;
+  wfs->end_date = end_date;
+  wfs->fees = *fees;
+  wfs->method = GNUNET_strdup (ac->wire_method);
+  GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
+                               ac->wsh->wfs_tail,
+                               wfs);
+  if (0 !=
+      json_array_append_new (
+        ac->a,
+        GNUNET_JSON_PACK (
+          TALER_JSON_pack_amount ("wire_fee",
+                                  &fees->wire),
+          TALER_JSON_pack_amount ("closing_fee",
+                                  &fees->closing),
+          GNUNET_JSON_pack_timestamp ("start_date",
+                                      start_date),
+          GNUNET_JSON_pack_timestamp ("end_date",
+                                      end_date),
+          GNUNET_JSON_pack_data_auto ("sig",
+                                      master_sig))))
+  {
+    GNUNET_break (0);   /* out of memory!? */
+    return;
+  }
+}
+
+
+/**
+ * Create the /wire response from our database state.
+ *
+ * @return NULL on error
+ */
+static struct WireStateHandle *
+build_wire_state (void)
+{
+  json_t *wire_accounts_array;
+  json_t *wire_fee_object;
+  uint64_t wg = wire_generation; /* must be obtained FIRST */
+  enum GNUNET_DB_QueryStatus qs;
+  struct WireStateHandle *wsh;
+  struct GNUNET_HashContext *hc;
+  json_t *wads;
+
+  wsh = GNUNET_new (struct WireStateHandle);
+  wsh->wire_generation = wg;
+  wire_accounts_array = json_array ();
+  GNUNET_assert (NULL != wire_accounts_array);
+  qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
+                                      &add_wire_account,
+                                      wire_accounts_array);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    json_decref (wire_accounts_array);
+    wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+    wsh->wire_reply
+      = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
+                              "get_wire_accounts");
+    return wsh;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Build /wire data with %u accounts\n",
+              (unsigned int) json_array_size (wire_accounts_array));
+  wire_fee_object = json_object ();
+  GNUNET_assert (NULL != wire_fee_object);
+  wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
+  hc = GNUNET_CRYPTO_hash_context_start ();
+  {
+    json_t *account;
+    size_t index;
+
+    json_array_foreach (wire_accounts_array, index, account) {
+      char *wire_method;
+      const char *payto_uri = json_string_value (json_object_get (account,
+                                                                  
"payto_uri"));
+
+      GNUNET_assert (NULL != payto_uri);
+      wire_method = TALER_payto_get_method (payto_uri);
+      if (NULL == wire_method)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "No wire method in `%s'\n",
+                    payto_uri);
+        wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+        wsh->wire_reply
+          = TALER_MHD_make_error (
+              TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
+              payto_uri);
+        json_decref (wire_accounts_array);
+        json_decref (wire_fee_object);
+        GNUNET_CRYPTO_hash_context_abort (hc);
+        return wsh;
+      }
+      if (NULL == json_object_get (wire_fee_object,
+                                   wire_method))
+      {
+        struct AddContext ac = {
+          .wire_method = wire_method,
+          .wsh = wsh,
+          .a = json_array (),
+          .hc = hc
+        };
+
+        GNUNET_assert (NULL != ac.a);
+        qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
+                                        wire_method,
+                                        &add_wire_fee,
+                                        &ac);
+        if (0 > qs)
+        {
+          GNUNET_break (0);
+          json_decref (ac.a);
+          json_decref (wire_fee_object);
+          json_decref (wire_accounts_array);
+          GNUNET_free (wire_method);
+          wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+          wsh->wire_reply
+            = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "get_wire_fees");
+          GNUNET_CRYPTO_hash_context_abort (hc);
+          return wsh;
+        }
+        if (0 != json_array_size (ac.a))
+        {
+          wsh->cache_expiration
+            = GNUNET_TIME_absolute_min (ac.max_seen,
+                                        wsh->cache_expiration);
+          GNUNET_assert (0 ==
+                         json_object_set_new (wire_fee_object,
+                                              wire_method,
+                                              ac.a));
+        }
+        else
+        {
+          json_decref (ac.a);
+        }
+      }
+      GNUNET_free (wire_method);
+    }
+  }
+
+  wads = json_array (); /* #7271 */
+  GNUNET_assert (NULL != wads);
+  wsh->json_reply = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_array_incref ("accounts",
+                                   wire_accounts_array),
+    GNUNET_JSON_pack_array_incref ("wads",
+                                   wads),
+    GNUNET_JSON_pack_object_incref ("fees",
+                                    wire_fee_object));
+  wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
+    GNUNET_JSON_pack_array_steal ("accounts",
+                                  wire_accounts_array),
+    GNUNET_JSON_pack_array_steal ("wads",
+                                  wads),
+    GNUNET_JSON_pack_object_steal ("fees",
+                                   wire_fee_object),
+    GNUNET_JSON_pack_data_auto ("master_public_key",
+                                &TEH_master_public_key));
+  {
+    struct GNUNET_TIME_Timestamp m;
+
+    m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
+    TALER_MHD_get_date_string (m.abs_time,
+                               wsh->dat);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Setting 'Expires' header for '/wire' to '%s'\n",
+                wsh->dat);
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (wsh->wire_reply,
+                                           MHD_HTTP_HEADER_EXPIRES,
+                                           wsh->dat));
+  }
+  /* Set cache control headers: our response varies depending on these headers 
*/
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (wsh->wire_reply,
+                                         MHD_HTTP_HEADER_VARY,
+                                         MHD_HTTP_HEADER_ACCEPT_ENCODING));
+  /* Information is always public, revalidate after 1 day */
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (wsh->wire_reply,
+                                         MHD_HTTP_HEADER_CACHE_CONTROL,
+                                         "public,max-age=86400"));
+
+  {
+    struct GNUNET_HashCode h;
+    char etag[sizeof (h) * 2];
+    char *end;
+
+    GNUNET_CRYPTO_hash_context_finish (hc,
+                                       &h);
+    end = GNUNET_STRINGS_data_to_string (&h,
+                                         sizeof (h),
+                                         etag,
+                                         sizeof (etag));
+    *end = '\0';
+    wsh->etag = GNUNET_strdup (etag);
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (wsh->wire_reply,
+                                           MHD_HTTP_HEADER_ETAG,
+                                           etag));
+  }
+  wsh->http_status = MHD_HTTP_OK;
+  return wsh;
+}
+
+
+void
+TEH_wire_update_state (void)
+{
+  struct GNUNET_DB_EventHeaderP es = {
+    .size = htons (sizeof (es)),
+    .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
+  };
+
+  TEH_plugin->event_notify (TEH_plugin->cls,
+                            &es,
+                            NULL,
+                            0);
+  wire_generation++;
+  key_generation++;
+}
+
+
+/**
+ * Return the current key state for this thread.  Possibly
+ * re-builds the key state if we have reason to believe
+ * that something changed.
+ *
+ * @return NULL on error
+ */
+struct WireStateHandle *
+get_wire_state (void)
+{
+  struct WireStateHandle *old_wsh;
+
+  old_wsh = wire_state;
+  if ( (NULL == old_wsh) ||
+       (old_wsh->wire_generation < wire_generation) )
+  {
+    struct WireStateHandle *wsh;
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Rebuilding /wire, generation upgrade from %llu to %llu\n",
+                (unsigned long long) (NULL == old_wsh) ? 0LL :
+                old_wsh->wire_generation,
+                (unsigned long long) wire_generation);
+    TEH_check_invariants ();
+    wsh = build_wire_state ();
+    wire_state = wsh;
+    if (NULL != old_wsh)
+      destroy_wire_state (old_wsh);
+    TEH_check_invariants ();
+    return wsh;
+  }
+  return old_wsh;
+}
+
+
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+  struct GNUNET_TIME_Timestamp ts,
+  const char *method)
+{
+  struct WireStateHandle *wsh = get_wire_state ();
+
+  for (struct WireFeeSet *wfs = wsh->wfs_head;
+       NULL != wfs;
+       wfs = wfs->next)
+  {
+    if (0 != strcmp (method,
+                     wfs->method))
+      continue;
+    if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
+                                     >,
+                                     ts)) ||
+         (GNUNET_TIME_timestamp_cmp (ts,
+                                     >=,
+                                     wfs->end_date)) )
+      continue;
+    return &wfs->fees;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "No wire fees for method `%s' at %s configured\n",
+              method,
+              GNUNET_TIME_timestamp2s (ts));
+  return NULL;
+}
+
+
+/**
+ * Function called to forcefully resume suspended keys requests.
+ *
+ * @param cls unused, NULL
+ */
+static void
+keys_timeout_cb (void *cls)
+{
+  struct SuspendedKeysRequests *skr;
+
+  (void) cls;
+  keys_tt = NULL;
+  while (NULL != (skr = skr_head))
+  {
+    if (GNUNET_TIME_absolute_is_future (skr->timeout))
+      break;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Resuming /keys request due to timeout\n");
+    GNUNET_CONTAINER_DLL_remove (skr_head,
+                                 skr_tail,
+                                 skr);
+    MHD_resume_connection (skr->connection);
+    TALER_MHD_daemon_trigger ();
+    GNUNET_free (skr);
+  }
+  if (NULL == skr)
+    return;
+  keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+                                     &keys_timeout_cb,
+                                     NULL);
+}
+
+
+/**
+ * Suspend /keys request while we (hopefully) are waiting to be
+ * provisioned with key material.
+ *
+ * @param[in] connection to suspend
+ */
+static MHD_RESULT
+suspend_request (struct MHD_Connection *connection)
+{
+  struct SuspendedKeysRequests *skr;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Suspending /keys request until key material changes\n");
+  if (terminating)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                       "Exchange terminating");
+  }
+  skr = GNUNET_new (struct SuspendedKeysRequests);
+  skr->connection = connection;
+  MHD_suspend_connection (connection);
+  GNUNET_CONTAINER_DLL_insert (skr_head,
+                               skr_tail,
+                               skr);
+  skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT);
+  if (NULL == keys_tt)
+  {
+    keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+                                       &keys_timeout_cb,
+                                       NULL);
+  }
+  skr_size++;
+  if (skr_size > SKR_LIMIT)
+  {
+    skr = skr_tail;
+    GNUNET_CONTAINER_DLL_remove (skr_head,
+                                 skr_tail,
+                                 skr);
+    skr_size--;
+    skr_connection = skr->connection;
+    MHD_resume_connection (skr->connection);
+    TALER_MHD_daemon_trigger ();
+    GNUNET_free (skr);
+  }
+  return MHD_YES;
+}
+
+
+/**
+ * Called on each denomination key. Checks that the key still works.
+ *
+ * @param cls NULL
+ * @param hc denomination hash (unused)
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+check_dk (void *cls,
+          const struct GNUNET_HashCode *hc,
+          void *value)
+{
+  struct TEH_DenominationKey *dk = value;
+
+  (void) cls;
+  (void) hc;
+  GNUNET_assert (TALER_DENOMINATION_INVALID != dk->denom_pub.cipher);
+  if (TALER_DENOMINATION_RSA == dk->denom_pub.cipher)
+    GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check (
+                     dk->denom_pub.details.rsa_public_key));
+  // nothing to do for TALER_DENOMINATION_CS
+  return GNUNET_OK;
+}
+
+
+void
+TEH_check_invariants ()
+{
+  struct TEH_KeyStateHandle *ksh;
+
+  if (0 == TEH_check_invariants_flag)
+    return;
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+    return;
+  GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+                                         &check_dk,
+                                         NULL);
+}
+
+
+void
+TEH_resume_keys_requests (bool do_shutdown)
+{
+  struct SuspendedKeysRequests *skr;
+
+  if (do_shutdown)
+    terminating = true;
+  while (NULL != (skr = skr_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (skr_head,
+                                 skr_tail,
+                                 skr);
+    skr_size--;
+    MHD_resume_connection (skr->connection);
+    TALER_MHD_daemon_trigger ();
+    GNUNET_free (skr);
+  }
+}
+
+
+/**
+ * Clear memory for responses to "/keys" in @a ksh.
+ *
+ * @param[in,out] ksh key state to update
+ */
+static void
+clear_response_cache (struct TEH_KeyStateHandle *ksh)
+{
+  for (unsigned int i = 0; i<ksh->krd_array_length; i++)
+  {
+    struct KeysResponseData *krd = &ksh->krd_array[i];
+
+    MHD_destroy_response (krd->response_compressed);
+    MHD_destroy_response (krd->response_uncompressed);
+    GNUNET_free (krd->etag);
+  }
+  GNUNET_array_grow (ksh->krd_array,
+                     ksh->krd_array_length,
+                     0);
+}
+
+
+/**
+ * Check that the given RSA security module's public key is the one
+ * we have pinned.  If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+  if (0 !=
+      GNUNET_memcmp (sm_pub,
+                     &denom_rsa_sm_pub))
+  {
+    if (! GNUNET_is_zero (&denom_rsa_sm_pub))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Our RSA security module changed its key. This must not 
happen.\n");
+      GNUNET_assert (0);
+    }
+    denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */
+  }
+}
+
+
+/**
+ * Check that the given CS security module's public key is the one
+ * we have pinned.  If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+  if (0 !=
+      GNUNET_memcmp (sm_pub,
+                     &denom_cs_sm_pub))
+  {
+    if (! GNUNET_is_zero (&denom_cs_sm_pub))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Our CS security module changed its key. This must not 
happen.\n");
+      GNUNET_assert (0);
+    }
+    denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */
+  }
+}
+
+
+/**
+ * Check that the given EdDSA security module's public key is the one
+ * we have pinned.  If it does not match, we die hard.
+ *
+ * @param sm_pub EdDSA security module public key to check
+ */
+static void
+check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+  if (0 !=
+      GNUNET_memcmp (sm_pub,
+                     &esign_sm_pub))
+  {
+    if (! GNUNET_is_zero (&esign_sm_pub))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Our EdDSA security module changed its key. This must not 
happen.\n");
+      GNUNET_assert (0);
+    }
+    esign_sm_pub = *sm_pub; /* TOFU ;-) */
+  }
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `denom_keys` map.
+ *
+ * @param cls the `struct HelperDenomination`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value the `struct HelperDenomination` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_denom_cb (void *cls,
+               const struct GNUNET_HashCode *h_denom_pub,
+               void *value)
+{
+  struct HelperDenomination *hd = value;
+
+  (void) cls;
+  (void) h_denom_pub;
+  TALER_denom_pub_free (&hd->denom_pub);
+  GNUNET_free (hd->section_name);
+  GNUNET_free (hd);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `esign_keys` map.
+ *
+ * @param cls the `struct HelperSignkey`
+ * @param pid unused, matches the exchange public key
+ * @param value the `struct HelperSignkey` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_esign_cb (void *cls,
+               const struct GNUNET_PeerIdentity *pid,
+               void *value)
+{
+  struct HelperSignkey *hsk = value;
+
+  (void) cls;
+  (void) pid;
+  GNUNET_free (hsk);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Destroy helper state. Does NOT call free() on @a hs, as that
+ * state is not separately allocated!  Dual to #setup_key_helpers().
+ *
+ * @param[in] hs helper state to free, but NOT the @a hs pointer itself!
+ */
+static void
+destroy_key_helpers (struct HelperState *hs)
+{
+  GNUNET_CONTAINER_multihashmap_iterate (hs->denom_keys,
+                                         &free_denom_cb,
+                                         hs);
+  GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys);
+  hs->rsa_keys = NULL;
+  GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys);
+  hs->cs_keys = NULL;
+  GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys);
+  hs->denom_keys = NULL;
+  GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys,
+                                         &free_esign_cb,
+                                         hs);
+  GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys);
+  hs->esign_keys = NULL;
+  if (NULL != hs->rsadh)
+  {
+    TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh);
+    hs->rsadh = NULL;
+  }
+  if (NULL != hs->csdh)
+  {
+    TALER_CRYPTO_helper_cs_disconnect (hs->csdh);
+    hs->csdh = NULL;
+  }
+  if (NULL != hs->esh)
+  {
+    TALER_CRYPTO_helper_esign_disconnect (hs->esh);
+    hs->esh = NULL;
+  }
+}
+
+
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ *    denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char *section_name)
+{
+  static const struct TALER_AgeMask null_mask = {0};
+  enum GNUNET_GenericReturnValue ret;
+
+  if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+                      TEH_cfg,
+                      section_name,
+                      "AGE_RESTRICTED")))
+    return null_mask;
+
+  if (GNUNET_SYSERR ==
+      (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
+                                                   section_name,
+                                                   "AGE_RESTRICTED")))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               section_name,
+                               "AGE_RESTRICTED",
+                               "Value must be YES or NO\n");
+    return null_mask;
+  }
+
+  if (GNUNET_OK == ret)
+  {
+    if (! TEH_age_restriction_enabled)
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "age restriction set in section %s, yet, age restriction is 
not enabled\n",
+                  section_name);
+    return TEH_age_restriction_config.mask;
+  }
+
+
+  return null_mask;
+}
+
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_rsa hash of the @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+static void
+helper_rsa_cb (
+  void *cls,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_RsaPubHashP *h_rsa,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+  struct HelperState *hs = cls;
+  struct HelperDenomination *hd;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "RSA helper announces key %s for denomination type %s with 
validity %s\n",
+              GNUNET_h2s (&h_rsa->hash),
+              section_name,
+              GNUNET_STRINGS_relative_time_to_string (validity_duration,
+                                                      GNUNET_NO));
+  key_generation++;
+  TEH_resume_keys_requests (false);
+  hd = GNUNET_CONTAINER_multihashmap_get (hs->rsa_keys,
+                                          &h_rsa->hash);
+  if (NULL != hd)
+  {
+    /* should be just an update (revocation!), so update existing entry */
+    hd->validity_duration = validity_duration;
+    return;
+  }
+  GNUNET_assert (NULL != sm_pub);
+  check_denom_rsa_sm_pub (sm_pub);
+  hd = GNUNET_new (struct HelperDenomination);
+  hd->start_time = start_time;
+  hd->validity_duration = validity_duration;
+  hd->h_details.h_rsa = *h_rsa;
+  hd->sm_sig = *sm_sig;
+  GNUNET_assert (TALER_DENOMINATION_RSA == denom_pub->cipher);
+  TALER_denom_pub_deep_copy (&hd->denom_pub,
+                             denom_pub);
+  GNUNET_assert (TALER_DENOMINATION_RSA == hd->denom_pub.cipher);
+  /* load the age mask for the denomination, if applicable */
+  hd->denom_pub.age_mask = load_age_mask (section_name);
+  TALER_denom_pub_hash (&hd->denom_pub,
+                        &hd->h_denom_pub);
+  hd->section_name = GNUNET_strdup (section_name);
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multihashmap_put (
+      hs->denom_keys,
+      &hd->h_denom_pub.hash,
+      hd,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multihashmap_put (
+      hs->rsa_keys,
+      &hd->h_details.h_rsa.hash,
+      hd,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available CS keys for signing. 
Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_cs hash of the @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+static void
+helper_cs_cb (
+  void *cls,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_CsPubHashP *h_cs,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+  struct HelperState *hs = cls;
+  struct HelperDenomination *hd;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "CS helper announces key %s for denomination type %s with 
validity %s\n",
+              GNUNET_h2s (&h_cs->hash),
+              section_name,
+              GNUNET_STRINGS_relative_time_to_string (validity_duration,
+                                                      GNUNET_NO));
+  key_generation++;
+  TEH_resume_keys_requests (false);
+  hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys,
+                                          &h_cs->hash);
+  if (NULL != hd)
+  {
+    /* should be just an update (revocation!), so update existing entry */
+    hd->validity_duration = validity_duration;
+    return;
+  }
+  GNUNET_assert (NULL != sm_pub);
+  check_denom_cs_sm_pub (sm_pub);
+  hd = GNUNET_new (struct HelperDenomination);
+  hd->start_time = start_time;
+  hd->validity_duration = validity_duration;
+  hd->h_details.h_cs = *h_cs;
+  hd->sm_sig = *sm_sig;
+  GNUNET_assert (TALER_DENOMINATION_CS == denom_pub->cipher);
+  TALER_denom_pub_deep_copy (&hd->denom_pub,
+                             denom_pub);
+  /* load the age mask for the denomination, if applicable */
+  hd->denom_pub.age_mask = load_age_mask (section_name);
+  TALER_denom_pub_hash (&hd->denom_pub,
+                        &hd->h_denom_pub);
+  hd->section_name = GNUNET_strdup (section_name);
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multihashmap_put (
+      hs->denom_keys,
+      &hd->h_denom_pub.hash,
+      hd,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multihashmap_put (
+      hs->cs_keys,
+      &hd->h_details.h_cs.hash,
+      hd,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+static void
+helper_esign_cb (
+  void *cls,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+  struct HelperState *hs = cls;
+  struct HelperSignkey *hsk;
+  struct GNUNET_PeerIdentity pid;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "EdDSA helper announces signing key %s with validity %s\n",
+              TALER_B2S (exchange_pub),
+              GNUNET_STRINGS_relative_time_to_string (validity_duration,
+                                                      GNUNET_NO));
+  key_generation++;
+  TEH_resume_keys_requests (false);
+  pid.public_key = exchange_pub->eddsa_pub;
+  hsk = GNUNET_CONTAINER_multipeermap_get (hs->esign_keys,
+                                           &pid);
+  if (NULL != hsk)
+  {
+    /* should be just an update (revocation!), so update existing entry */
+    hsk->validity_duration = validity_duration;
+    return;
+  }
+  GNUNET_assert (NULL != sm_pub);
+  check_esign_sm_pub (sm_pub);
+  hsk = GNUNET_new (struct HelperSignkey);
+  hsk->start_time = start_time;
+  hsk->validity_duration = validity_duration;
+  hsk->exchange_pub = *exchange_pub;
+  hsk->sm_sig = *sm_sig;
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multipeermap_put (
+      hs->esign_keys,
+      &pid,
+      hsk,
+      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Setup helper state.
+ *
+ * @param[out] hs helper state to initialize
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key_helpers (struct HelperState *hs)
+{
+  hs->denom_keys
+    = GNUNET_CONTAINER_multihashmap_create (1024,
+                                            GNUNET_YES);
+  hs->rsa_keys
+    = GNUNET_CONTAINER_multihashmap_create (1024,
+                                            GNUNET_YES);
+  hs->cs_keys
+    = GNUNET_CONTAINER_multihashmap_create (1024,
+                                            GNUNET_YES);
+  hs->esign_keys
+    = GNUNET_CONTAINER_multipeermap_create (32,
+                                            GNUNET_NO /* MUST BE NO! */);
+  hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
+                                               &helper_rsa_cb,
+                                               hs);
+  if (NULL == hs->rsadh)
+  {
+    destroy_key_helpers (hs);
+    return GNUNET_SYSERR;
+  }
+  hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
+                                             &helper_cs_cb,
+                                             hs);
+  if (NULL == hs->csdh)
+  {
+    destroy_key_helpers (hs);
+    return GNUNET_SYSERR;
+  }
+  hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
+                                               &helper_esign_cb,
+                                               hs);
+  if (NULL == hs->esh)
+  {
+    destroy_key_helpers (hs);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Synchronize helper state. Polls the key helper for updates.
+ *
+ * @param[in,out] hs helper state to synchronize
+ */
+static void
+sync_key_helpers (struct HelperState *hs)
+{
+  TALER_CRYPTO_helper_rsa_poll (hs->rsadh);
+  TALER_CRYPTO_helper_cs_poll (hs->csdh);
+  TALER_CRYPTO_helper_esign_poll (hs->esh);
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param h_denom_pub hash of the denomination public key, unused
+ * @param value a `struct TEH_DenominationKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_denomination_cb (void *cls,
+                       const struct GNUNET_HashCode *h_denom_pub,
+                       void *value)
+{
+  struct TEH_DenominationKey *dk = value;
+  struct TEH_AuditorSignature *as;
+
+  (void) cls;
+  (void) h_denom_pub;
+  TALER_denom_pub_free (&dk->denom_pub);
+  while (NULL != (as = dk->as_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (dk->as_head,
+                                 dk->as_tail,
+                                 as);
+    GNUNET_free (as);
+  }
+  GNUNET_free (dk);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param pid the online signing key (type-disguised), unused
+ * @param value a `struct SigningKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_signkey_cb (void *cls,
+                  const struct GNUNET_PeerIdentity *pid,
+                  void *value)
+{
+  struct SigningKey *sk = value;
+
+  (void) cls;
+  (void) pid;
+  GNUNET_free (sk);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Free resources associated with @a cls, possibly excluding
+ * the helper data.
+ *
+ * @param[in] ksh key state to release
+ * @param free_helper true to also release the helper state
+ */
+static void
+destroy_key_state (struct TEH_KeyStateHandle *ksh,
+                   bool free_helper)
+{
+  struct TEH_GlobalFee *gf;
+
+  clear_response_cache (ksh);
+  while (NULL != (gf = ksh->gf_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (ksh->gf_head,
+                                 ksh->gf_tail,
+                                 gf);
+    GNUNET_free (gf);
+  }
+  GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+                                         &clear_denomination_cb,
+                                         ksh);
+  GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map);
+  GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+                                         &clear_signkey_cb,
+                                         ksh);
+  GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);
+  json_decref (ksh->auditors);
+  ksh->auditors = NULL;
+  json_decref (ksh->global_fees);
+  ksh->global_fees = NULL;
+  if (free_helper)
+  {
+    destroy_key_helpers (ksh->helpers);
+    GNUNET_free (ksh->helpers);
+  }
+  if (NULL != ksh->management_keys_reply)
+  {
+    json_decref (ksh->management_keys_reply);
+    ksh->management_keys_reply = NULL;
+  }
+  GNUNET_free (ksh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the keys data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+keys_update_event_cb (void *cls,
+                      const void *extra,
+                      size_t extra_size)
+{
+  (void) cls;
+  (void) extra;
+  (void) extra_size;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Received /keys update event\n");
+  TEH_check_invariants ();
+  key_generation++;
+  TEH_resume_keys_requests (false);
+  TEH_check_invariants ();
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_init ()
+{
+  struct GNUNET_DB_EventHeaderP es = {
+    .size = htons (sizeof (es)),
+    .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+                                           "exchange",
+                                           "SIGNKEY_LEGAL_DURATION",
+                                           &signkey_legal_duration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "SIGNKEY_LEGAL_DURATION");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+                                             "exchange",
+                                             "ASSET_TYPE",
+                                             &asset_type))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+                               "exchange",
+                               "ASSET_TYPE");
+    asset_type = GNUNET_strdup ("fiat");
+  }
+  keys_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+                                      GNUNET_TIME_UNIT_FOREVER_REL,
+                                      &es,
+                                      &keys_update_event_cb,
+                                      NULL);
+  if (NULL == keys_eh)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Fully clean up our state.
+ */
+void
+TEH_keys_finished ()
+{
+  if (NULL != keys_tt)
+  {
+    GNUNET_SCHEDULER_cancel (keys_tt);
+    keys_tt = NULL;
+  }
+  if (NULL != key_state)
+    destroy_key_state (key_state,
+                       true);
+  if (NULL != keys_eh)
+  {
+    TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+                                     keys_eh);
+    keys_eh = NULL;
+  }
+}
+
+
+/**
+ * Function called with information about the exchange's denomination keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param denom_pub public key of the denomination
+ * @param h_denom_pub hash of @a denom_pub
+ * @param meta meta data information about the denomination type (value, 
expirations, fees)
+ * @param master_sig master signature affirming the validity of this 
denomination
+ * @param recoup_possible true if the key was revoked and clients can 
currently recoup
+ *        coins of this denomination
+ */
+static void
+denomination_info_cb (
+  void *cls,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig,
+  bool recoup_possible)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  struct TEH_DenominationKey *dk;
+
+  if (GNUNET_OK !=
+      TALER_exchange_offline_denom_validity_verify (
+        h_denom_pub,
+        meta->start,
+        meta->expire_withdraw,
+        meta->expire_deposit,
+        meta->expire_legal,
+        &meta->value,
+        &meta->fees,
+        &TEH_master_public_key,
+        master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database has denomination with invalid signature. Skipping 
entry. Did the exchange offline public key change?\n");
+    return;
+  }
+
+  GNUNET_assert (TALER_DENOMINATION_INVALID != denom_pub->cipher);
+  if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) ||
+      GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) ||
+      GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) ||
+      GNUNET_TIME_absolute_is_zero (meta->expire_legal.abs_time) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database contains invalid denomination key %s\n",
+                GNUNET_h2s (&h_denom_pub->hash));
+    return;
+  }
+  dk = GNUNET_new (struct TEH_DenominationKey);
+  TALER_denom_pub_deep_copy (&dk->denom_pub,
+                             denom_pub);
+  dk->h_denom_pub = *h_denom_pub;
+  dk->meta = *meta;
+  dk->master_sig = *master_sig;
+  dk->recoup_possible = recoup_possible;
+  dk->denom_pub.age_mask = meta->age_mask;
+
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
+                                       &dk->h_denom_pub.hash,
+                                       dk,
+                                       
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about the exchange's online signing keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param exchange_pub the public key
+ * @param meta meta data information about the denomination type (expirations)
+ * @param master_sig master signature affirming the validity of this 
denomination
+ */
+static void
+signkey_info_cb (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  struct SigningKey *sk;
+  struct GNUNET_PeerIdentity pid;
+
+  if (GNUNET_OK !=
+      TALER_exchange_offline_signkey_validity_verify (
+        exchange_pub,
+        meta->start,
+        meta->expire_sign,
+        meta->expire_legal,
+        &TEH_master_public_key,
+        master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database has signing key with invalid signature. Skipping 
entry. Did the exchange offline public key change?\n");
+    return;
+  }
+  sk = GNUNET_new (struct SigningKey);
+  sk->exchange_pub = *exchange_pub;
+  sk->meta = *meta;
+  sk->master_sig = *master_sig;
+  pid.public_key = exchange_pub->eddsa_pub;
+  GNUNET_assert (
+    GNUNET_OK ==
+    GNUNET_CONTAINER_multipeermap_put (ksh->signkey_map,
+                                       &pid,
+                                       sk,
+                                       
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Closure for #get_auditor_sigs.
+ */
+struct GetAuditorSigsContext
+{
+  /**
+   * Where to store the matching signatures.
+   */
+  json_t *denom_keys;
+
+  /**
+   * Public key of the auditor to match against.
+   */
+  const struct TALER_AuditorPublicKeyP *auditor_pub;
+};
+
+
+/**
+ * Extract the auditor signatures matching the auditor's public
+ * key from the @a value and generate the respective JSON.
+ *
+ * @param cls a `struct GetAuditorSigsContext`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+get_auditor_sigs (void *cls,
+                  const struct GNUNET_HashCode *h_denom_pub,
+                  void *value)
+{
+  struct GetAuditorSigsContext *ctx = cls;
+  struct TEH_DenominationKey *dk = value;
+
+  for (struct TEH_AuditorSignature *as = dk->as_head;
+       NULL != as;
+       as = as->next)
+  {
+    if (0 !=
+        GNUNET_memcmp (ctx->auditor_pub,
+                       &as->apub))
+      continue;
+    GNUNET_break (0 ==
+                  json_array_append_new (
+                    ctx->denom_keys,
+                    GNUNET_JSON_PACK (
+                      GNUNET_JSON_pack_data_auto ("denom_pub_h",
+                                                  h_denom_pub),
+                      GNUNET_JSON_pack_data_auto ("auditor_sig",
+                                                  &as->asig))));
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of the auditor
+ * @param auditor_url URL of the REST API of the auditor
+ * @param auditor_name human readable official name of the auditor
+ */
+static void
+auditor_info_cb (
+  void *cls,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  const char *auditor_name)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  struct GetAuditorSigsContext ctx;
+
+  ctx.denom_keys = json_array ();
+  GNUNET_assert (NULL != ctx.denom_keys);
+  ctx.auditor_pub = auditor_pub;
+  GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+                                         &get_auditor_sigs,
+                                         &ctx);
+  GNUNET_break (0 ==
+                json_array_append_new (
+                  ksh->auditors,
+                  GNUNET_JSON_PACK (
+                    GNUNET_JSON_pack_string ("auditor_name",
+                                             auditor_name),
+                    GNUNET_JSON_pack_data_auto ("auditor_pub",
+                                                auditor_pub),
+                    GNUNET_JSON_pack_string ("auditor_url",
+                                             auditor_url),
+                    GNUNET_JSON_pack_array_steal ("denomination_keys",
+                                                  ctx.denom_keys))));
+}
+
+
+/**
+ * Function called with information about the denominations
+ * audited by the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of an auditor
+ * @param h_denom_pub hash of a denomination key audited by this auditor
+ * @param auditor_sig signature from the auditor affirming this
+ */
+static void
+auditor_denom_cb (
+  void *cls,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AuditorSignatureP *auditor_sig)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  struct TEH_DenominationKey *dk;
+  struct TEH_AuditorSignature *as;
+
+  dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+                                          &h_denom_pub->hash);
+  if (NULL == dk)
+  {
+    /* Odd, this should be impossible as per foreign key
+       constraint on 'auditor_denom_sigs'! Well, we can
+       safely continue anyway, so let's just log it. */
+    GNUNET_break (0);
+    return;
+  }
+  as = GNUNET_new (struct TEH_AuditorSignature);
+  as->asig = *auditor_sig;
+  as->apub = *auditor_pub;
+  GNUNET_CONTAINER_DLL_insert (dk->as_head,
+                               dk->as_tail,
+                               as);
+}
+
+
+/**
+ * Closure for #add_sign_key_cb.
+ */
+struct SignKeyCtx
+{
+  /**
+   * What is the current rotation frequency for signing keys. Updated.
+   */
+  struct GNUNET_TIME_Relative min_sk_frequency;
+
+  /**
+   * JSON array of signing keys (being created).
+   */
+  json_t *signkeys;
+};
+
+
+/**
+ * Function called for all signing keys, used to build up the
+ * respective JSON response.
+ *
+ * @param cls a `struct SignKeyCtx *` with the array to append keys to
+ * @param pid the exchange public key (in type disguise)
+ * @param value a `struct SigningKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_sign_key_cb (void *cls,
+                 const struct GNUNET_PeerIdentity *pid,
+                 void *value)
+{
+  struct SignKeyCtx *ctx = cls;
+  struct SigningKey *sk = value;
+
+  (void) pid;
+  if (GNUNET_TIME_absolute_is_future (sk->meta.expire_sign.abs_time))
+  {
+    ctx->min_sk_frequency =
+      GNUNET_TIME_relative_min (ctx->min_sk_frequency,
+                                GNUNET_TIME_absolute_get_difference (
+                                  sk->meta.start.abs_time,
+                                  sk->meta.expire_sign.abs_time));
+  }
+  GNUNET_assert (
+    0 ==
+    json_array_append_new (
+      ctx->signkeys,
+      GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_timestamp ("stamp_start",
+                                    sk->meta.start),
+        GNUNET_JSON_pack_timestamp ("stamp_expire",
+                                    sk->meta.expire_sign),
+        GNUNET_JSON_pack_timestamp ("stamp_end",
+                                    sk->meta.expire_legal),
+        GNUNET_JSON_pack_data_auto ("master_sig",
+                                    &sk->master_sig),
+        GNUNET_JSON_pack_data_auto ("key",
+                                    &sk->exchange_pub))));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_denom_key_cb.
+ */
+struct DenomKeyCtx
+{
+  /**
+   * Heap for sorting active denomination keys by start time.
+   */
+  struct GNUNET_CONTAINER_Heap *heap;
+
+  /**
+   * JSON array of revoked denomination keys.
+   */
+  json_t *recoup;
+
+  /**
+   * What is the minimum key rotation frequency of
+   * valid denomination keys?
+   */
+  struct GNUNET_TIME_Relative min_dk_frequency;
+};
+
+
+/**
+ * Function called for all denomination keys, used to build up the
+ * JSON list of *revoked* denomination keys and the
+ * heap of non-revoked denomination keys by timeout.
+ *
+ * @param cls a `struct DenomKeyCtx`
+ * @param h_denom_pub hash of the denomination key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_denom_key_cb (void *cls,
+                  const struct GNUNET_HashCode *h_denom_pub,
+                  void *value)
+{
+  struct DenomKeyCtx *dkc = cls;
+  struct TEH_DenominationKey *dk = value;
+
+  if (dk->recoup_possible)
+  {
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        dkc->recoup,
+        GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                      h_denom_pub))));
+  }
+  else
+  {
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      dkc->min_dk_frequency =
+        GNUNET_TIME_relative_min (dkc->min_dk_frequency,
+                                  GNUNET_TIME_absolute_get_difference (
+                                    dk->meta.start.abs_time,
+                                    dk->meta.expire_withdraw.abs_time));
+    }
+    (void) GNUNET_CONTAINER_heap_insert (dkc->heap,
+                                         dk,
+                                         dk->meta.start.abs_time.abs_value_us);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Add the headers we want to set for every /keys response.
+ *
+ * @param ksh the key state to use
+ * @param wsh wire state to use
+ * @param[in,out] response the response to modify
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_general_response_headers (struct TEH_KeyStateHandle *ksh,
+                                struct WireStateHandle *wsh,
+                                struct MHD_Response *response)
+{
+  char dat[128];
+
+  TALER_MHD_add_global_headers (response);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_CONTENT_TYPE,
+                                         "application/json"));
+  TALER_MHD_get_date_string (ksh->reload_time.abs_time,
+                             dat);
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_LAST_MODIFIED,
+                                         dat));
+  if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
+  {
+    struct GNUNET_TIME_Relative r;
+    struct GNUNET_TIME_Absolute a;
+    struct GNUNET_TIME_Timestamp km;
+    struct GNUNET_TIME_Timestamp m;
+    struct GNUNET_TIME_Timestamp we;
+
+    r = GNUNET_TIME_relative_min (TEH_max_keys_caching,
+                                  ksh->rekey_frequency);
+    a = GNUNET_TIME_relative_to_absolute (r);
+    km = GNUNET_TIME_absolute_to_timestamp (a);
+    we = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
+    m = GNUNET_TIME_timestamp_min (we,
+                                   km);
+    TALER_MHD_get_date_string (m.abs_time,
+                               dat);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Setting /keys 'Expires' header to '%s' (rekey frequency is 
%s)\n",
+                dat,
+                GNUNET_TIME_relative2s (ksh->rekey_frequency,
+                                        false));
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (response,
+                                           MHD_HTTP_HEADER_EXPIRES,
+                                           dat));
+    ksh->signature_expires
+      = GNUNET_TIME_timestamp_min (m,
+                                   ksh->signature_expires);
+  }
+  /* Set cache control headers: our response varies depending on these headers 
*/
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_VARY,
+                                         MHD_HTTP_HEADER_ACCEPT_ENCODING));
+  /* Information is always public, revalidate after 1 hour */
+  GNUNET_break (MHD_YES ==
+                MHD_add_response_header (response,
+                                         MHD_HTTP_HEADER_CACHE_CONTROL,
+                                         "public,max-age=3600"));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with wallet balance thresholds.
+ *
+ * @param[in,out] cls a `json **` where to put the array of json amounts 
discovered
+ * @param threshold another threshold amount to add
+ */
+static void
+wallet_threshold_cb (void *cls,
+                     const struct TALER_Amount *threshold)
+{
+  json_t **ret = cls;
+
+  if (NULL == *ret)
+    *ret = json_array ();
+  GNUNET_assert (0 ==
+                 json_array_append_new (*ret,
+                                        TALER_JSON_from_amount (
+                                          threshold)));
+}
+
+
+/**
+ * Initialize @a krd using the given values for @a signkeys,
+ * @a recoup and @a denoms.
+ *
+ * @param[in,out] ksh key state handle we build @a krd for
+ * @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
+ * @param last_cherry_pick_date timestamp to use
+ * @param[in,out] signkeys list of sign keys to return
+ * @param[in,out] recoup list of revoked keys to return
+ * @param[in,out] grouped_denominations list of grouped denominations to return
+ * @param h_grouped XOR of all hashes in @a grouped_demoninations
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_krd (struct TEH_KeyStateHandle *ksh,
+            const struct GNUNET_HashCode *denom_keys_hash,
+            struct GNUNET_TIME_Timestamp last_cherry_pick_date,
+            json_t *signkeys,
+            json_t *recoup,
+            json_t *grouped_denominations,
+            const struct GNUNET_HashCode *h_grouped)
+{
+  struct KeysResponseData krd;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_ExchangePublicKeyP grouped_exchange_pub;
+  struct TALER_ExchangeSignatureP grouped_exchange_sig;
+  struct WireStateHandle *wsh;
+  json_t *keys;
+
+  wsh = get_wire_state ();
+  if (MHD_HTTP_OK != wsh->http_status)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   last_cherry_pick_date.abs_time));
+  GNUNET_assert (NULL != signkeys);
+  GNUNET_assert (NULL != recoup);
+  GNUNET_assert (NULL != grouped_denominations);
+  GNUNET_assert (NULL != h_grouped);
+  GNUNET_assert (NULL != ksh->auditors);
+  GNUNET_assert (NULL != TEH_currency);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Creating /keys at cherry pick date %s\n",
+              GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+
+  /* Sign hash over denomination keys */
+  {
+    enum TALER_ErrorCode ec;
+
+    if (TALER_EC_NONE !=
+        (ec =
+           TALER_exchange_online_key_set_sign (
+             &TEH_keys_exchange_sign2_,
+             ksh,
+             last_cherry_pick_date,
+             denom_keys_hash,
+             &exchange_pub,
+             &exchange_sig)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Could not create key response data: cannot sign (%s)\n",
+                  TALER_ErrorCode_get_hint (ec));
+      return GNUNET_SYSERR;
+    }
+  }
+
+  /* Sign grouped hash */
+  {
+    enum TALER_ErrorCode ec;
+
+    if (TALER_EC_NONE !=
+        (ec =
+           TALER_exchange_online_key_set_sign (
+             &TEH_keys_exchange_sign2_,
+             ksh,
+             last_cherry_pick_date,
+             h_grouped,
+             &grouped_exchange_pub,
+             &grouped_exchange_sig)))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Could not create key response data: cannot sign grouped 
hash (%s)\n",
+                  TALER_ErrorCode_get_hint (ec));
+      return GNUNET_SYSERR;
+    }
+  }
+
+  /* both public keys really must be the same */
+  GNUNET_assert (0 ==
+                 memcmp (&grouped_exchange_pub,
+                         &exchange_pub,
+                         sizeof(exchange_pub)));
+
+  {
+    const struct SigningKey *sk;
+
+    sk = GNUNET_CONTAINER_multipeermap_get (
+      ksh->signkey_map,
+      (const struct GNUNET_PeerIdentity *) &exchange_pub);
+    ksh->signature_expires = GNUNET_TIME_timestamp_min (sk->meta.expire_sign,
+                                                        
ksh->signature_expires);
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Build /keys data with %u wire accounts\n",
+              (unsigned int) json_array_size (
+                json_object_get (wsh->json_reply,
+                                 "accounts")));
+
+  keys = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("version",
+                             EXCHANGE_PROTOCOL_VERSION),
+    GNUNET_JSON_pack_string ("base_url",
+                             TEH_base_url),
+    GNUNET_JSON_pack_string ("currency",
+                             TEH_currency),
+    GNUNET_JSON_pack_uint64 ("currency_fraction_digits",
+                             TEH_currency_fraction_digits),
+    TALER_JSON_pack_amount ("stefan_abs",
+                            &TEH_stefan_abs),
+    TALER_JSON_pack_amount ("stefan_log",
+                            &TEH_stefan_log),
+    TALER_JSON_pack_amount ("stefan_lin",
+                            &TEH_stefan_lin),
+    GNUNET_JSON_pack_string ("asset_type",
+                             asset_type),
+    GNUNET_JSON_pack_bool ("rewards_allowed",
+                           GNUNET_YES ==
+                           TEH_enable_rewards),
+    GNUNET_JSON_pack_data_auto ("master_public_key",
+                                &TEH_master_public_key),
+    GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+                               TEH_reserve_closing_delay),
+    GNUNET_JSON_pack_array_incref ("signkeys",
+                                   signkeys),
+    GNUNET_JSON_pack_array_incref ("recoup",
+                                   recoup),
+    GNUNET_JSON_pack_array_incref ("wads",
+                                   json_object_get (wsh->json_reply,
+                                                    "wads")),
+    GNUNET_JSON_pack_array_incref ("accounts",
+                                   json_object_get (wsh->json_reply,
+                                                    "accounts")),
+    GNUNET_JSON_pack_object_incref ("wire_fees",
+                                    json_object_get (wsh->json_reply,
+                                                     "fees")),
+    GNUNET_JSON_pack_array_incref ("denominations",
+                                   grouped_denominations),
+    GNUNET_JSON_pack_array_incref ("auditors",
+                                   ksh->auditors),
+    GNUNET_JSON_pack_array_incref ("global_fees",
+                                   ksh->global_fees),
+    GNUNET_JSON_pack_timestamp ("list_issue_date",
+                                last_cherry_pick_date),
+    GNUNET_JSON_pack_data_auto ("eddsa_pub",
+                                &exchange_pub),
+    GNUNET_JSON_pack_data_auto ("eddsa_sig",
+                                &exchange_sig),
+    GNUNET_JSON_pack_data_auto ("denominations_sig",
+                                &grouped_exchange_sig));
+  GNUNET_assert (NULL != keys);
+
+  /* Set wallet limit if KYC is configured */
+  {
+    json_t *wblwk = NULL;
+
+    TALER_KYCLOGIC_kyc_iterate_thresholds (
+      TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
+      &wallet_threshold_cb,
+      &wblwk);
+    if (NULL != wblwk)
+      GNUNET_assert (
+        0 ==
+        json_object_set_new (
+          keys,
+          "wallet_balance_limit_without_kyc",
+          wblwk));
+  }
+
+  /* Signal support for the configured, enabled extensions. */
+  {
+    json_t *extensions = json_object ();
+    bool has_extensions = false;
+
+    GNUNET_assert (NULL != extensions);
+    /* Fill in the configurations of the enabled extensions */
+    for (const struct TALER_Extensions *iter = TALER_extensions_get_head ();
+         NULL != iter && NULL != iter->extension;
+         iter = iter->next)
+    {
+      const struct TALER_Extension *extension = iter->extension;
+      json_t *manifest;
+      int r;
+
+      /* skip if not enabled */
+      if (! extension->enabled)
+        continue;
+
+      /* flag our findings so far */
+      has_extensions = true;
+
+
+      manifest = extension->manifest (extension);
+      GNUNET_assert (manifest);
+
+      r = json_object_set_new (
+        extensions,
+        extension->name,
+        manifest);
+      GNUNET_assert (0 == r);
+    }
+
+    /* Update the keys object with the extensions and its signature */
+    if (has_extensions)
+    {
+      json_t *sig;
+      int r;
+
+      r = json_object_set_new (
+        keys,
+        "extensions",
+        extensions);
+      GNUNET_assert (0 == r);
+
+      /* Add the signature of the extensions, if it is not zero */
+      if (TEH_extensions_signed)
+      {
+        sig = GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_data_auto ("extensions_sig",
+                                      &TEH_extensions_sig));
+
+        r = json_object_update (keys, sig);
+        GNUNET_assert (0 == r);
+      }
+    }
+    else
+    {
+      json_decref (extensions);
+    }
+  }
+
+
+  {
+    char *keys_json;
+    void *keys_jsonz;
+    size_t keys_jsonz_size;
+    int comp;
+    char etag[sizeof (struct GNUNET_HashCode) * 2];
+
+    /* Convert /keys response to UTF8-String */
+    keys_json = json_dumps (keys,
+                            JSON_INDENT (2));
+    json_decref (keys);
+    GNUNET_assert (NULL != keys_json);
+
+    /* Keep copy for later compression... */
+    keys_jsonz = GNUNET_strdup (keys_json);
+    keys_jsonz_size = strlen (keys_json);
+
+    /* hash to compute etag */
+    {
+      struct GNUNET_HashCode ehash;
+      char *end;
+
+      GNUNET_CRYPTO_hash (keys_jsonz,
+                          keys_jsonz_size,
+                          &ehash);
+      end = GNUNET_STRINGS_data_to_string (&ehash,
+                                           sizeof (ehash),
+                                           etag,
+                                           sizeof (etag));
+      *end = '\0';
+    }
+
+    /* Create uncompressed response */
+    krd.response_uncompressed
+      = MHD_create_response_from_buffer (keys_jsonz_size,
+                                         keys_json,
+                                         MHD_RESPMEM_MUST_FREE);
+    GNUNET_assert (NULL != krd.response_uncompressed);
+    GNUNET_assert (GNUNET_OK ==
+                   setup_general_response_headers (ksh,
+                                                   wsh,
+                                                   krd.response_uncompressed));
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (krd.response_uncompressed,
+                                           MHD_HTTP_HEADER_ETAG,
+                                           etag));
+    /* Also compute compressed version of /keys response */
+    comp = TALER_MHD_body_compress (&keys_jsonz,
+                                    &keys_jsonz_size);
+    krd.response_compressed
+      = MHD_create_response_from_buffer (keys_jsonz_size,
+                                         keys_jsonz,
+                                         MHD_RESPMEM_MUST_FREE);
+    GNUNET_assert (NULL != krd.response_compressed);
+    /* If the response is actually compressed, set the
+       respective header. */
+    GNUNET_assert ( (MHD_YES != comp) ||
+                    (MHD_YES ==
+                     MHD_add_response_header (krd.response_compressed,
+                                              MHD_HTTP_HEADER_CONTENT_ENCODING,
+                                              "deflate")) );
+    GNUNET_assert (GNUNET_OK ==
+                   setup_general_response_headers (ksh,
+                                                   wsh,
+                                                   krd.response_compressed));
+    /* Set cache control headers: our response varies depending on these 
headers */
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (wsh->wire_reply,
+                                           MHD_HTTP_HEADER_VARY,
+                                           MHD_HTTP_HEADER_ACCEPT_ENCODING));
+    /* Information is always public, revalidate after 1 day */
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (wsh->wire_reply,
+                                           MHD_HTTP_HEADER_CACHE_CONTROL,
+                                           "public,max-age=86400"));
+    GNUNET_break (MHD_YES ==
+                  MHD_add_response_header (krd.response_compressed,
+                                           MHD_HTTP_HEADER_ETAG,
+                                           etag));
+    krd.etag = GNUNET_strdup (etag);
+  }
+  krd.cherry_pick_date = last_cherry_pick_date;
+  GNUNET_array_append (ksh->krd_array,
+                       ksh->krd_array_length,
+                       krd);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Update the "/keys" responses in @a ksh, computing the detailed replies.
+ *
+ * This function is to recompute all (including cherry-picked) responses we
+ * might want to return, based on the state already in @a ksh.
+ *
+ * @param[in,out] ksh state handle to update
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+finish_keys_response (struct TEH_KeyStateHandle *ksh)
+{
+  enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+  json_t *recoup;
+  struct SignKeyCtx sctx;
+  json_t *grouped_denominations = NULL;
+  struct GNUNET_TIME_Timestamp last_cherry_pick_date;
+  struct GNUNET_CONTAINER_Heap *heap;
+  struct GNUNET_HashContext *hash_context = NULL;
+  struct GNUNET_HashCode grouped_hash_xor = {0};
+  /* Remember if we have any denomination with age restriction */
+  bool has_age_restricted_denomination = false;
+
+  sctx.signkeys = json_array ();
+  GNUNET_assert (NULL != sctx.signkeys);
+  sctx.min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL;
+  GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+                                         &add_sign_key_cb,
+                                         &sctx);
+  recoup = json_array ();
+  GNUNET_assert (NULL != recoup);
+  heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
+  {
+    struct DenomKeyCtx dkc = {
+      .recoup = recoup,
+      .heap = heap,
+      .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
+    };
+
+    GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+                                           &add_denom_key_cb,
+                                           &dkc);
+    ksh->rekey_frequency
+      = GNUNET_TIME_relative_min (dkc.min_dk_frequency,
+                                  sctx.min_sk_frequency);
+  }
+
+  hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+  grouped_denominations = json_array ();
+  GNUNET_assert (NULL != grouped_denominations);
+
+  last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
+
+  // FIXME: This block contains the implementation of the DEPRECATED
+  // "denom_pubs" array along with the new grouped "denominations".
+  // "denom_pubs" Will be removed sooner or later.
+  {
+    struct TEH_DenominationKey *dk;
+    struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
+    /* GroupData is the value we store for each group meta-data */
+    struct GroupData
+    {
+      /**
+       * The json blob with the group meta-data and list of denominations
+       */
+      json_t *json;
+
+      /**
+       * xor of all hashes of denominations in that group
+       */
+      struct GNUNET_HashCode hash_xor;
+    };
+
+    denominations_by_group =
+      GNUNET_CONTAINER_multihashmap_create (1024,
+                                            GNUNET_NO /* NO, because keys are 
only on the stack */);
+
+
+    /* heap = min heap, sorted by start time */
+    while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+    {
+      if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
+                                     !=,
+                                     dk->meta.start) &&
+          (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
+      {
+        /*
+         * This is not the first entry in the heap (because 
last_cherry_pick_date !=
+         * GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
+         * start time.  Therefore, we create a new entry in ksh.
+         */
+        struct GNUNET_HashCode hc;
+
+        GNUNET_CRYPTO_hash_context_finish (
+          GNUNET_CRYPTO_hash_context_copy (hash_context),
+          &hc);
+
+        if (GNUNET_OK !=
+            create_krd (ksh,
+                        &hc,
+                        last_cherry_pick_date,
+                        sctx.signkeys,
+                        recoup,
+                        grouped_denominations,
+                        &grouped_hash_xor))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                      "Failed to generate key response data for %s\n",
+                      GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+          GNUNET_CRYPTO_hash_context_abort (hash_context);
+          /* drain heap before destroying it */
+          while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+            /* intentionally empty */;
+          GNUNET_CONTAINER_heap_destroy (heap);
+          goto CLEANUP;
+        }
+      }
+
+      last_cherry_pick_date = dk->meta.start;
+      /*
+       * Group the denominations by {cipher, value, fees, age_mask}.
+       *
+       * For each group we save the group meta-data and the list of
+       * denominations in this group as a json-blob in the multihashmap
+       * denominations_by_group.
+       */
+      {
+        static const char *denoms_key = "denoms";
+        struct GroupData *group;
+        json_t *list;
+        json_t *entry;
+        struct GNUNET_HashCode key;
+        struct TALER_DenominationGroup meta = {
+          .cipher = dk->denom_pub.cipher,
+          .value = dk->meta.value,
+          .fees = dk->meta.fees,
+          .age_mask = dk->meta.age_mask,
+        };
+
+        /* Search the group/JSON-blob for the key */
+        TALER_denomination_group_get_key (&meta,
+                                          &key);
+        group = GNUNET_CONTAINER_multihashmap_get (
+          denominations_by_group,
+          &key);
+        if (NULL == group)
+        {
+          /* There is no group for this meta-data yet, so we create a new 
group */
+          bool age_restricted = meta.age_mask.bits != 0;
+          const char *cipher;
+
+          group = GNUNET_new (struct GroupData);
+          switch (meta.cipher)
+          {
+          case TALER_DENOMINATION_RSA:
+            cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+            break;
+          case TALER_DENOMINATION_CS:
+            cipher = age_restricted ? "CS+age_restricted" : "CS";
+            break;
+          default:
+            GNUNET_assert (false);
+          }
+
+          group->json = GNUNET_JSON_PACK (
+            GNUNET_JSON_pack_string ("cipher",
+                                     cipher),
+            TALER_JSON_PACK_DENOM_FEES ("fee",
+                                        &meta.fees),
+            TALER_JSON_pack_amount ("value",
+                                    &meta.value));
+          GNUNET_assert (NULL != group->json);
+
+          if (age_restricted)
+          {
+            int r = json_object_set_new (group->json,
+                                         "age_mask",
+                                         json_integer (meta.age_mask.bits));
+
+            GNUNET_assert (0 == r);
+
+            /* Remember that we have found at least _one_ age restricted 
denomination */
+            has_age_restricted_denomination = true;
+          }
+
+          /* Create a new array for the denominations in this group */
+          list = json_array ();
+          GNUNET_assert (NULL != list);
+          GNUNET_assert (0 ==
+                         json_object_set_new (group->json,
+                                              denoms_key,
+                                              list));
+          GNUNET_assert (
+            GNUNET_OK ==
+            GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
+                                               &key,
+                                               group,
+                                               
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+        }
+
+        /* Now that we have found/created the right group, add the
+           denomination to the list */
+        {
+          struct HelperDenomination *hd;
+          struct GNUNET_JSON_PackSpec key_spec;
+          bool private_key_lost;
+
+          hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                                  &dk->h_denom_pub.hash);
+          private_key_lost
+            = (NULL == hd) ||
+              GNUNET_TIME_absolute_is_past (
+                GNUNET_TIME_absolute_add (
+                  hd->start_time.abs_time,
+                  hd->validity_duration));
+          switch (meta.cipher)
+          {
+          case TALER_DENOMINATION_RSA:
+            key_spec =
+              GNUNET_JSON_pack_rsa_public_key (
+                "rsa_pub",
+                dk->denom_pub.details.rsa_public_key);
+            break;
+          case TALER_DENOMINATION_CS:
+            key_spec =
+              GNUNET_JSON_pack_data_varsize (
+                "cs_pub",
+                &dk->denom_pub.details.cs_public_key,
+                sizeof (dk->denom_pub.details.cs_public_key));
+            break;
+          default:
+            GNUNET_assert (false);
+          }
+
+          entry = GNUNET_JSON_PACK (
+            GNUNET_JSON_pack_data_auto ("master_sig",
+                                        &dk->master_sig),
+            GNUNET_JSON_pack_allow_null (
+              private_key_lost
+              ? GNUNET_JSON_pack_bool ("lost",
+                                       true)
+              : GNUNET_JSON_pack_string ("dummy",
+                                         NULL)),
+            GNUNET_JSON_pack_timestamp ("stamp_start",
+                                        dk->meta.start),
+            GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+                                        dk->meta.expire_withdraw),
+            GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+                                        dk->meta.expire_deposit),
+            GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+                                        dk->meta.expire_legal),
+            key_spec
+            );
+          GNUNET_assert (NULL != entry);
+        }
+
+        /* Build up the running xor of all hashes of the denominations in this
+           group */
+        GNUNET_CRYPTO_hash_xor (&dk->h_denom_pub.hash,
+                                &group->hash_xor,
+                                &group->hash_xor);
+
+        /* Finally, add the denomination to the list of denominations in this
+           group */
+        list = json_object_get (group->json, denoms_key);
+        GNUNET_assert (NULL != list);
+        GNUNET_assert (true == json_is_array (list));
+        GNUNET_assert (0 ==
+                       json_array_append_new (list, entry));
+      }
+    } /* loop over heap ends */
+
+    /* Create the JSON-array of grouped denominations */
+    if (0 <
+        GNUNET_CONTAINER_multihashmap_size (denominations_by_group))
+    {
+      struct GNUNET_CONTAINER_MultiHashMapIterator *iter;
+      struct GroupData *group = NULL;
+
+      iter =
+        GNUNET_CONTAINER_multihashmap_iterator_create (denominations_by_group);
+
+      while (GNUNET_OK ==
+             GNUNET_CONTAINER_multihashmap_iterator_next (iter,
+                                                          NULL,
+                                                          (const
+                                                           void **) &group))
+      {
+        /* Add the XOR over all hashes of denominations in this group to the 
group */
+        GNUNET_assert (0 ==
+                       json_object_set_new (
+                         group->json,
+                         "hash",
+                         GNUNET_JSON_PACK (
+                           GNUNET_JSON_pack_data_auto (NULL,
+                                                       &group->hash_xor))));
+
+        /* Add this group to the array */
+        GNUNET_assert (0 ==
+                       json_array_append_new (
+                         grouped_denominations,
+                         group->json));
+        /* Build the running XOR over all hash(_xor) */
+        GNUNET_CRYPTO_hash_xor (&group->hash_xor,
+                                &grouped_hash_xor,
+                                &grouped_hash_xor);
+        GNUNET_free (group);
+      }
+      GNUNET_CONTAINER_multihashmap_iterator_destroy (iter);
+
+    }
+
+    GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
+  }
+
+  GNUNET_CONTAINER_heap_destroy (heap);
+  if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
+  {
+    struct GNUNET_HashCode hc;
+
+    GNUNET_CRYPTO_hash_context_finish (hash_context,
+                                       &hc);
+    if (GNUNET_OK !=
+        create_krd (ksh,
+                    &hc,
+                    last_cherry_pick_date,
+                    sctx.signkeys,
+                    recoup,
+                    grouped_denominations,
+                    &grouped_hash_xor))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to generate key response data for %s\n",
+                  GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+      goto CLEANUP;
+    }
+    ksh->management_only = false;
+
+    /* Sanity check:  Make sure that age restriction is enabled IFF at least
+     * one age restricted denomination exist */
+    if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Age restriction is enabled, but NO denominations with age 
restriction found!\n");
+      goto CLEANUP;
+    }
+    else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Age restriction is NOT enabled, but denominations with age 
restriction found!\n");
+      goto CLEANUP;
+    }
+  }
+  else
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "No denomination keys available. Refusing to generate /keys 
response.\n");
+    GNUNET_CRYPTO_hash_context_abort (hash_context);
+  }
+
+  ret = GNUNET_OK;
+
+CLEANUP:
+  json_decref (grouped_denominations);
+  json_decref (sctx.signkeys);
+  json_decref (recoup);
+  return ret;
+}
+
+
+/**
+ * Called with information about global fees.
+ *
+ * @param cls `struct TEH_KeyStateHandle *` we are building
+ * @param fees the global fees we charge
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ *                   fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES)
+ */
+static void
+global_fee_info_cb (
+  void *cls,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  struct TEH_GlobalFee *gf;
+
+  if (GNUNET_OK !=
+      TALER_exchange_offline_global_fee_verify (
+        start_date,
+        end_date,
+        fees,
+        purse_timeout,
+        history_expiration,
+        purse_account_limit,
+        &TEH_master_public_key,
+        master_sig))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Database has global fee with invalid signature. Skipping 
entry. Did the exchange offline public key change?\n");
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Found global fees with %u purses\n",
+              purse_account_limit);
+  gf = GNUNET_new (struct TEH_GlobalFee);
+  gf->start_date = start_date;
+  gf->end_date = end_date;
+  gf->fees = *fees;
+  gf->purse_timeout = purse_timeout;
+  gf->history_expiration = history_expiration;
+  gf->purse_account_limit = purse_account_limit;
+  gf->master_sig = *master_sig;
+  GNUNET_CONTAINER_DLL_insert (ksh->gf_head,
+                               ksh->gf_tail,
+                               gf);
+  GNUNET_assert (
+    0 ==
+    json_array_append_new (
+      ksh->global_fees,
+      GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_timestamp ("start_date",
+                                    start_date),
+        GNUNET_JSON_pack_timestamp ("end_date",
+                                    end_date),
+        TALER_JSON_PACK_GLOBAL_FEES (fees),
+        GNUNET_JSON_pack_time_rel ("history_expiration",
+                                   history_expiration),
+        GNUNET_JSON_pack_time_rel ("purse_timeout",
+                                   purse_timeout),
+        GNUNET_JSON_pack_uint64 ("purse_account_limit",
+                                 purse_account_limit),
+        GNUNET_JSON_pack_data_auto ("master_sig",
+                                    master_sig))));
+}
+
+
+/**
+ * Create a key state.
+ *
+ * @param[in] hs helper state to (re)use, NULL if not available
+ * @param management_only if we should NOT run 'finish_keys_response()'
+ *                  because we only need the state for the /management/keys API
+ * @return NULL on error (i.e. failed to access database)
+ */
+static struct TEH_KeyStateHandle *
+build_key_state (struct HelperState *hs,
+                 bool management_only)
+{
+  struct TEH_KeyStateHandle *ksh;
+  enum GNUNET_DB_QueryStatus qs;
+
+  ksh = GNUNET_new (struct TEH_KeyStateHandle);
+  ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
+  ksh->reload_time = GNUNET_TIME_timestamp_get ();
+  /* We must use the key_generation from when we STARTED the process! */
+  ksh->key_generation = key_generation;
+  if (NULL == hs)
+  {
+    ksh->helpers = GNUNET_new (struct HelperState);
+    if (GNUNET_OK !=
+        setup_key_helpers (ksh->helpers))
+    {
+      GNUNET_free (ksh->helpers);
+      GNUNET_assert (NULL == ksh->management_keys_reply);
+      GNUNET_free (ksh);
+      return NULL;
+    }
+  }
+  else
+  {
+    ksh->helpers = hs;
+  }
+  ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
+                                                            true);
+  ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
+                                                           false /* MUST be 
false! */);
+  ksh->auditors = json_array ();
+  GNUNET_assert (NULL != ksh->auditors);
+  /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
+  GNUNET_break (GNUNET_OK ==
+                TEH_plugin->preflight (TEH_plugin->cls));
+  if (NULL != ksh->global_fees)
+    json_decref (ksh->global_fees);
+  ksh->global_fees = json_array ();
+  qs = TEH_plugin->get_global_fees (TEH_plugin->cls,
+                                    &global_fee_info_cb,
+                                    ksh);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Loading global fees from DB: %d\n",
+              qs);
+  if (qs < 0)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+  qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,
+                                          &denomination_info_cb,
+                                          ksh);
+  if (qs < 0)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+  /* NOTE: ONLY fetches non-revoked AND master-signed signkeys! */
+  qs = TEH_plugin->iterate_active_signkeys (TEH_plugin->cls,
+                                            &signkey_info_cb,
+                                            ksh);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+  qs = TEH_plugin->iterate_auditor_denominations (TEH_plugin->cls,
+                                                  &auditor_denom_cb,
+                                                  ksh);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+  qs = TEH_plugin->iterate_active_auditors (TEH_plugin->cls,
+                                            &auditor_info_cb,
+                                            ksh);
+  if (qs < 0)
+  {
+    GNUNET_break (0);
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+
+  if (management_only)
+  {
+    ksh->management_only = true;
+    return ksh;
+  }
+
+  if (GNUNET_OK !=
+      finish_keys_response (ksh))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Could not finish /keys response (likely no signing keys 
available yet)\n");
+    destroy_key_state (ksh,
+                       true);
+    return NULL;
+  }
+
+  return ksh;
+}
+
+
+void
+TEH_keys_update_states ()
+{
+  struct GNUNET_DB_EventHeaderP es = {
+    .size = htons (sizeof (es)),
+    .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+  };
+
+  TEH_plugin->event_notify (TEH_plugin->cls,
+                            &es,
+                            NULL,
+                            0);
+  key_generation++;
+  TEH_resume_keys_requests (false);
+}
+
+
+static struct TEH_KeyStateHandle *
+keys_get_state (bool management_only)
+{
+  struct TEH_KeyStateHandle *old_ksh;
+  struct TEH_KeyStateHandle *ksh;
+
+  old_ksh = key_state;
+  if (NULL == old_ksh)
+  {
+    ksh = build_key_state (NULL,
+                           management_only);
+    if (NULL == ksh)
+      return NULL;
+    key_state = ksh;
+    return ksh;
+  }
+  if ( (old_ksh->key_generation < key_generation) ||
+       (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Rebuilding /keys, generation upgrade from %llu to %llu\n",
+                (unsigned long long) old_ksh->key_generation,
+                (unsigned long long) key_generation);
+    ksh = build_key_state (old_ksh->helpers,
+                           management_only);
+    key_state = ksh;
+    old_ksh->helpers = NULL;
+    destroy_key_state (old_ksh,
+                       false);
+    return ksh;
+  }
+  sync_key_helpers (old_ksh->helpers);
+  return old_ksh;
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void)
+{
+  return keys_get_state (true);
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void)
+{
+  struct TEH_KeyStateHandle *ksh;
+
+  ksh = keys_get_state (false);
+  if (NULL == ksh)
+    return NULL;
+
+  if (ksh->management_only)
+  {
+    if (GNUNET_OK !=
+        finish_keys_response (ksh))
+      return NULL;
+  }
+
+  return ksh;
+}
+
+
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+  struct TEH_KeyStateHandle *ksh,
+  struct GNUNET_TIME_Timestamp ts)
+{
+  for (const struct TEH_GlobalFee *gf = ksh->gf_head;
+       NULL != gf;
+       gf = gf->next)
+  {
+    if (GNUNET_TIME_timestamp_cmp (ts,
+                                   >=,
+                                   gf->start_date) &&
+        GNUNET_TIME_timestamp_cmp (ts,
+                                   <,
+                                   gf->end_date))
+      return gf;
+  }
+  return NULL;
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct MHD_Connection *conn,
+  MHD_RESULT *mret)
+{
+  struct TEH_KeyStateHandle *ksh;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    *mret = TALER_MHD_reply_with_error (conn,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                        NULL);
+    return NULL;
+  }
+
+  return TEH_keys_denomination_by_hash_from_state (ksh,
+                                                   h_denom_pub,
+                                                   conn,
+                                                   mret);
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+  const struct TEH_KeyStateHandle *ksh,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct MHD_Connection *conn,
+  MHD_RESULT *mret)
+{
+  struct TEH_DenominationKey *dk;
+
+  dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+                                          &h_denom_pub->hash);
+  if (NULL == dk)
+  {
+    if (NULL == conn)
+      return NULL;
+    *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
+                                                       h_denom_pub);
+    return NULL;
+  }
+  return dk;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_sign (
+  const struct TEH_CoinSignData *csd,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bs)
+{
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperDenomination *hd;
+  const struct TALER_DenominationHashP *h_denom_pub = csd->h_denom_pub;
+  const struct TALER_BlindedPlanchet *bp = csd->bp;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                          &h_denom_pub->hash);
+  if (NULL == hd)
+    return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+  if (bp->cipher != hd->denom_pub.cipher)
+    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+  switch (hd->denom_pub.cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA]++;
+    {
+      struct TALER_CRYPTO_RsaSignRequest rsr = {
+        .h_rsa = &hd->h_details.h_rsa,
+        .msg = bp->details.rsa_blinded_planchet.blinded_msg,
+        .msg_size = bp->details.rsa_blinded_planchet.blinded_msg_size
+      };
+
+      return TALER_CRYPTO_helper_rsa_sign (
+        ksh->helpers->rsadh,
+        &rsr,
+        bs);
+    }
+  case TALER_DENOMINATION_CS:
+    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS]++;
+    {
+      struct TALER_CRYPTO_CsSignRequest csr;
+
+      csr.h_cs = &hd->h_details.h_cs;
+      csr.blinded_planchet = &bp->details.cs_blinded_planchet;
+      return TALER_CRYPTO_helper_cs_sign (
+        ksh->helpers->csdh,
+        &csr,
+        for_melt,
+        bs);
+    }
+  default:
+    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+  }
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+  const struct TEH_CoinSignData *csds,
+  unsigned int csds_length,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bss)
+{
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperDenomination *hd;
+  struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length];
+  struct TALER_CRYPTO_CsSignRequest csrs[csds_length];
+  struct TALER_BlindedDenominationSignature rs[csds_length];
+  struct TALER_BlindedDenominationSignature cs[csds_length];
+  unsigned int rsrs_pos = 0;
+  unsigned int csrs_pos = 0;
+  enum TALER_ErrorCode ec;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  for (unsigned int i = 0; i<csds_length; i++)
+  {
+    const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub;
+    const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+    hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                            &h_denom_pub->hash);
+    if (NULL == hd)
+      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+    if (bp->cipher != hd->denom_pub.cipher)
+      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+    switch (hd->denom_pub.cipher)
+    {
+    case TALER_DENOMINATION_RSA:
+      rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
+      rsrs[rsrs_pos].msg
+        = bp->details.rsa_blinded_planchet.blinded_msg;
+      rsrs[rsrs_pos].msg_size
+        = bp->details.rsa_blinded_planchet.blinded_msg_size;
+      rsrs_pos++;
+      break;
+    case TALER_DENOMINATION_CS:
+      csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
+      csrs[csrs_pos].blinded_planchet = &bp->details.cs_blinded_planchet;
+      csrs_pos++;
+      break;
+    default:
+      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+    }
+  }
+
+  if ( (0 != csrs_pos) &&
+       (0 != rsrs_pos) )
+  {
+    memset (rs,
+            0,
+            sizeof (rs));
+    memset (cs,
+            0,
+            sizeof (cs));
+  }
+  ec = TALER_EC_NONE;
+  if (0 != csrs_pos)
+  {
+    ec = TALER_CRYPTO_helper_cs_batch_sign (
+      ksh->helpers->csdh,
+      csrs,
+      csrs_pos,
+      for_melt,
+      (0 == rsrs_pos) ? bss : cs);
+    if (TALER_EC_NONE != ec)
+    {
+      for (unsigned int i = 0; i<csrs_pos; i++)
+        TALER_blinded_denom_sig_free (&cs[i]);
+      return ec;
+    }
+    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
+  }
+  if (0 != rsrs_pos)
+  {
+    ec = TALER_CRYPTO_helper_rsa_batch_sign (
+      ksh->helpers->rsadh,
+      rsrs,
+      rsrs_pos,
+      (0 == csrs_pos) ? bss : rs);
+    if (TALER_EC_NONE != ec)
+    {
+      for (unsigned int i = 0; i<csrs_pos; i++)
+        TALER_blinded_denom_sig_free (&cs[i]);
+      for (unsigned int i = 0; i<rsrs_pos; i++)
+        TALER_blinded_denom_sig_free (&rs[i]);
+      return ec;
+    }
+    TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
+  }
+
+  if ( (0 != csrs_pos) &&
+       (0 != rsrs_pos) )
+  {
+    rsrs_pos = 0;
+    csrs_pos = 0;
+    for (unsigned int i = 0; i<csds_length; i++)
+    {
+      const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+      switch (bp->cipher)
+      {
+      case TALER_DENOMINATION_RSA:
+        bss[i] = rs[rsrs_pos++];
+        break;
+      case TALER_DENOMINATION_CS:
+        bss[i] = cs[csrs_pos++];
+        break;
+      default:
+        GNUNET_assert (0);
+      }
+    }
+  }
+  return TALER_EC_NONE;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+  const struct TEH_CsDeriveData *cdd,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *r_pub)
+{
+  const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub;
+  const struct TALER_CsNonce *nonce = cdd->nonce;
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperDenomination *hd;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  }
+  hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                          &h_denom_pub->hash);
+  if (NULL == hd)
+  {
+    return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+  }
+  if (TALER_DENOMINATION_CS != hd->denom_pub.cipher)
+  {
+    return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+  }
+
+  {
+    struct TALER_CRYPTO_CsDeriveRequest cdr = {
+      .h_cs = &hd->h_details.h_cs,
+      .nonce = nonce
+    };
+    return TALER_CRYPTO_helper_cs_r_derive (ksh->helpers->csdh,
+                                            &cdr,
+                                            for_melt,
+                                            r_pub);
+  }
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+  const struct TEH_CsDeriveData *cdds,
+  unsigned int cdds_length,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *r_pubs)
+{
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperDenomination *hd;
+  struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length];
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  }
+  for (unsigned int i = 0; i<cdds_length; i++)
+  {
+    const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
+    const struct TALER_CsNonce *nonce = cdds[i].nonce;
+
+    hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                            &h_denom_pub->hash);
+    if (NULL == hd)
+    {
+      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+    }
+    if (TALER_DENOMINATION_CS != hd->denom_pub.cipher)
+    {
+      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+    }
+    cdrs[i].h_cs = &hd->h_details.h_cs;
+    cdrs[i].nonce = nonce;
+  }
+
+  return TALER_CRYPTO_helper_cs_r_batch_derive (ksh->helpers->csdh,
+                                                cdrs,
+                                                cdds_length,
+                                                for_melt,
+                                                r_pubs);
+}
+
+
+void
+TEH_keys_denomination_revoke (const struct TALER_DenominationHashP 
*h_denom_pub)
+{
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperDenomination *hd;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                          &h_denom_pub->hash);
+  if (NULL == hd)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  switch (hd->denom_pub.cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    TALER_CRYPTO_helper_rsa_revoke (ksh->helpers->rsadh,
+                                    &hd->h_details.h_rsa);
+    TEH_keys_update_states ();
+    return;
+  case TALER_DENOMINATION_CS:
+    TALER_CRYPTO_helper_cs_revoke (ksh->helpers->csdh,
+                                   &hd->h_details.h_cs);
+    TEH_keys_update_states ();
+    return;
+  default:
+    GNUNET_break (0);
+    return;
+  }
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TEH_KeyStateHandle *ksh;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    /* This *can* happen if the exchange's crypto helper is not running
+       or had some bad error. */
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Cannot sign request, no valid signing keys available.\n");
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  }
+  return TEH_keys_exchange_sign2_ (ksh,
+                                   purpose,
+                                   pub,
+                                   sig);
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+  void *cls,
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TEH_KeyStateHandle *ksh = cls;
+  enum TALER_ErrorCode ec;
+
+  TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA]++;
+  ec = TALER_CRYPTO_helper_esign_sign_ (ksh->helpers->esh,
+                                        purpose,
+                                        pub,
+                                        sig);
+  if (TALER_EC_NONE != ec)
+    return ec;
+  {
+    /* Here we check here that 'pub' is set to an exchange public key that is
+       actually signed by the master key! Otherwise, we happily continue to
+       use key material even if the offline signatures have not been made
+       yet! */
+    struct GNUNET_PeerIdentity pid;
+    struct SigningKey *sk;
+
+    pid.public_key = pub->eddsa_pub;
+    sk = GNUNET_CONTAINER_multipeermap_get (ksh->signkey_map,
+                                            &pid);
+    if (NULL == sk)
+    {
+      /* just to be safe, zero out the (valid) signature, as the key
+         should not or no longer be used */
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Cannot sign, offline key signatures are missing!\n");
+      memset (sig,
+              0,
+              sizeof (*sig));
+      return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG;
+    }
+  }
+  return ec;
+}
+
+
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct TEH_KeyStateHandle *ksh;
+
+  ksh = TEH_keys_get_state ();
+  if (NULL == ksh)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  TALER_CRYPTO_helper_esign_revoke (ksh->helpers->esh,
+                                    exchange_pub);
+  TEH_keys_update_states ();
+}
+
+
+/**
+ * Comparator used for a binary search by cherry_pick_date for @a key in the
+ * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
+ *
+ * @param key pointer to a `struct GNUNET_TIME_Timestamp`
+ * @param value pointer to a `struct KeysResponseData` array entry
+ * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
+ */
+static int
+krd_search_comparator (const void *key,
+                       const void *value)
+{
+  const struct GNUNET_TIME_Timestamp *kd = key;
+  const struct KeysResponseData *krd = value;
+
+  if (GNUNET_TIME_timestamp_cmp (*kd,
+                                 >,
+                                 krd->cherry_pick_date))
+    return -1;
+  if (GNUNET_TIME_timestamp_cmp (*kd,
+                                 <,
+                                 krd->cherry_pick_date))
+    return 1;
+  return 0;
+}
+
+
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+                      const char *const args[])
+{
+  struct GNUNET_TIME_Timestamp last_issue_date;
+  const char *etag;
+  struct WireStateHandle *wsh;
+
+  wsh = get_wire_state ();
+  etag = MHD_lookup_connection_value (rc->connection,
+                                      MHD_HEADER_KIND,
+                                      MHD_HTTP_HEADER_IF_NONE_MATCH);
+  (void) args;
+  {
+    const char *have_cherrypick;
+
+    have_cherrypick = MHD_lookup_connection_value (rc->connection,
+                                                   MHD_GET_ARGUMENT_KIND,
+                                                   "last_issue_date");
+    if (NULL != have_cherrypick)
+    {
+      unsigned long long cherrypickn;
+
+      if (1 !=
+          sscanf (have_cherrypick,
+                  "%llu",
+                  &cherrypickn))
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (rc->connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                           have_cherrypick);
+      }
+      /* The following multiplication may overflow; but this should not really
+         be a problem, as giving back 'older' data than what the client asks 
for
+         (given that the client asks for data in the distant future) is not
+         problematic */
+      last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn);
+    }
+    else
+    {
+      last_issue_date = GNUNET_TIME_UNIT_ZERO_TS;
+    }
+  }
+
+  {
+    struct TEH_KeyStateHandle *ksh;
+    const struct KeysResponseData *krd;
+
+    ksh = TEH_keys_get_state ();
+    if ( (NULL == ksh) ||
+         (0 == ksh->krd_array_length) )
+    {
+      if ( ( (SKR_LIMIT == skr_size) &&
+             (rc->connection == skr_connection) ) ||
+           TEH_suicide)
+      {
+        return TALER_MHD_reply_with_error (
+          rc->connection,
+          MHD_HTTP_SERVICE_UNAVAILABLE,
+          TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+          TEH_suicide
+          ? "server terminating"
+          : "too many connections suspended waiting on /keys");
+      }
+      return suspend_request (rc->connection);
+    }
+    krd = bsearch (&last_issue_date,
+                   ksh->krd_array,
+                   ksh->krd_array_length,
+                   sizeof (struct KeysResponseData),
+                   &krd_search_comparator);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Filtering /keys by cherry pick date %s found entry %u/%u\n",
+                GNUNET_TIME_timestamp2s (last_issue_date),
+                (unsigned int) (krd - ksh->krd_array),
+                ksh->krd_array_length);
+    if ( (NULL == krd) &&
+         (ksh->krd_array_length > 0) )
+    {
+      if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Client provided invalid cherry picking timestamp %s, 
returning full response\n",
+                    GNUNET_TIME_timestamp2s (last_issue_date));
+      krd = &ksh->krd_array[ksh->krd_array_length - 1];
+    }
+    if (NULL == krd)
+    {
+      /* Likely keys not ready *yet*.
+         Wait until they are. */
+      return suspend_request (rc->connection);
+    }
+    if ( (NULL != etag) &&
+         (0 == strcmp (etag,
+                       krd->etag)) )
+    {
+      MHD_RESULT ret;
+      struct MHD_Response *resp;
+
+      resp = MHD_create_response_from_buffer (0,
+                                              NULL,
+                                              MHD_RESPMEM_PERSISTENT);
+      TALER_MHD_add_global_headers (resp);
+      GNUNET_break (GNUNET_OK ==
+                    setup_general_response_headers (ksh,
+                                                    wsh,
+                                                    resp));
+      GNUNET_break (MHD_YES ==
+                    MHD_add_response_header (resp,
+                                             MHD_HTTP_HEADER_ETAG,
+                                             krd->etag));
+      ret = MHD_queue_response (rc->connection,
+                                MHD_HTTP_NOT_MODIFIED,
+                                resp);
+      GNUNET_break (MHD_YES == ret);
+      MHD_destroy_response (resp);
+      return ret;
+    }
+    return MHD_queue_response (rc->connection,
+                               MHD_HTTP_OK,
+                               (MHD_YES ==
+                                TALER_MHD_can_compress (rc->connection))
+                               ? krd->response_compressed
+                               : krd->response_uncompressed);
+  }
+}
+
+
+/**
+ * Load extension data, like fees, expiration times (!) and age restriction
+ * flags for the denomination type configured in section @a section_name.
+ * Before calling this function, the `start` and `validity_duration` times must
+ * already be initialized in @a meta.
+ *
+ * @param section_name section in the configuration to use
+ * @param[in,out] meta denomination type data to complete
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_extension_data (const char *section_name,
+                     struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+  struct GNUNET_TIME_Relative deposit_duration;
+  struct GNUNET_TIME_Relative legal_duration;
+
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (meta->start.abs_time)); /* 
caller bug */
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+                                           section_name,
+                                           "DURATION_SPEND",
+                                           &deposit_duration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section_name,
+                               "DURATION_SPEND");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+                                           section_name,
+                                           "DURATION_LEGAL",
+                                           &legal_duration))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section_name,
+                               "DURATION_LEGAL");
+    return GNUNET_SYSERR;
+  }
+  meta->expire_deposit
+    = GNUNET_TIME_absolute_to_timestamp (
+        GNUNET_TIME_absolute_add (meta->expire_withdraw.abs_time,
+                                  deposit_duration));
+  meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (meta->expire_deposit.abs_time,
+                              legal_duration));
+  if (GNUNET_OK !=
+      TALER_config_get_amount (TEH_cfg,
+                               section_name,
+                               "VALUE",
+                               &meta->value))
+  {
+    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+                               "Need amount for option `%s' in section `%s'\n",
+                               "VALUE",
+                               section_name);
+    return GNUNET_SYSERR;
+  }
+  if (0 != strcasecmp (TEH_currency,
+                       meta->value.currency))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Need denomination value in section `%s' to use currency 
`%s'\n",
+                section_name,
+                TEH_currency);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_config_get_denom_fees (TEH_cfg,
+                                   TEH_currency,
+                                   section_name,
+                                   &meta->fees))
+    return GNUNET_SYSERR;
+  meta->age_mask = load_age_mask (section_name);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+                    const struct TALER_DenominationHashP *h_denom_pub,
+                    struct TALER_DenominationPublicKey *denom_pub,
+                    struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+  struct HelperDenomination *hd;
+  enum GNUNET_GenericReturnValue ok;
+
+  hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys,
+                                          &h_denom_pub->hash);
+  if (NULL == hd)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Denomination %s not known\n",
+                GNUNET_h2s (&h_denom_pub->hash));
+    return GNUNET_NO;
+  }
+  meta->start = hd->start_time;
+  meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (meta->start.abs_time,
+                              hd->validity_duration));
+  ok = load_extension_data (hd->section_name,
+                            meta);
+  if (GNUNET_OK == ok)
+  {
+    GNUNET_assert (TALER_DENOMINATION_INVALID != hd->denom_pub.cipher);
+    TALER_denom_pub_deep_copy (denom_pub,
+                               &hd->denom_pub);
+  }
+  else
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "No fees for `%s', voiding key\n",
+                hd->section_name);
+    memset (denom_pub,
+            0,
+            sizeof (*denom_pub));
+  }
+  return ok;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+                     struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+  struct TEH_KeyStateHandle *ksh;
+  struct HelperSignkey *hsk;
+  struct GNUNET_PeerIdentity pid;
+
+  ksh = TEH_keys_get_state_for_management_only ();
+  if (NULL == ksh)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  pid.public_key = exchange_pub->eddsa_pub;
+  hsk = GNUNET_CONTAINER_multipeermap_get (ksh->helpers->esign_keys,
+                                           &pid);
+  meta->start = hsk->start_time;
+
+  meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (meta->start.abs_time,
+                              hsk->validity_duration));
+  meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (meta->expire_sign.abs_time,
+                              signkey_legal_duration));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_future_denomkey_cb and #add_future_signkey_cb.
+ */
+struct FutureBuilderContext
+{
+  /**
+   * Our key state.
+   */
+  struct TEH_KeyStateHandle *ksh;
+
+  /**
+   * Array of denomination keys.
+   */
+  json_t *denoms;
+
+  /**
+   * Array of signing keys.
+   */
+  json_t *signkeys;
+
+};
+
+
+/**
+ * Function called on all of our current and future denomination keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining denomination keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_denomkey_cb (void *cls,
+                        const struct GNUNET_HashCode *h_denom_pub,
+                        void *value)
+{
+  struct FutureBuilderContext *fbc = cls;
+  struct HelperDenomination *hd = value;
+  struct TEH_DenominationKey *dk;
+  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+
+  dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
+                                          h_denom_pub);
+  if (NULL != dk)
+    return GNUNET_OK; /* skip: this key is already active! */
+  if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
+    return GNUNET_OK; /* this key already expired! */
+  meta.start = hd->start_time;
+  meta.expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (meta.start.abs_time,
+                              hd->validity_duration));
+  if (GNUNET_OK !=
+      load_extension_data (hd->section_name,
+                           &meta))
+  {
+    /* Woops, couldn't determine fee structure!? */
+    return GNUNET_OK;
+  }
+  GNUNET_assert (
+    0 ==
+    json_array_append_new (
+      fbc->denoms,
+      GNUNET_JSON_PACK (
+        TALER_JSON_pack_amount ("value",
+                                &meta.value),
+        GNUNET_JSON_pack_timestamp ("stamp_start",
+                                    meta.start),
+        GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+                                    meta.expire_withdraw),
+        GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+                                    meta.expire_deposit),
+        GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+                                    meta.expire_legal),
+        TALER_JSON_pack_denom_pub ("denom_pub",
+                                   &hd->denom_pub),
+        TALER_JSON_PACK_DENOM_FEES ("fee",
+                                    &meta.fees),
+        GNUNET_JSON_pack_data_auto ("denom_secmod_sig",
+                                    &hd->sm_sig),
+        GNUNET_JSON_pack_string ("section_name",
+                                 hd->section_name))));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called on all of our current and future exchange signing keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining signing keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param pid actually the exchange public key (type disguised)
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_signkey_cb (void *cls,
+                       const struct GNUNET_PeerIdentity *pid,
+                       void *value)
+{
+  struct FutureBuilderContext *fbc = cls;
+  struct HelperSignkey *hsk = value;
+  struct SigningKey *sk;
+  struct GNUNET_TIME_Timestamp stamp_expire;
+  struct GNUNET_TIME_Timestamp legal_end;
+
+  sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map,
+                                          pid);
+  if (NULL != sk)
+    return GNUNET_OK; /* skip: this key is already active */
+  if (GNUNET_TIME_relative_is_zero (hsk->validity_duration))
+    return GNUNET_OK; /* this key already expired! */
+  stamp_expire = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (hsk->start_time.abs_time,
+                              hsk->validity_duration));
+  legal_end = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (stamp_expire.abs_time,
+                              signkey_legal_duration));
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   fbc->signkeys,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_data_auto ("key",
+                                                 &hsk->exchange_pub),
+                     GNUNET_JSON_pack_timestamp ("stamp_start",
+                                                 hsk->start_time),
+                     GNUNET_JSON_pack_timestamp ("stamp_expire",
+                                                 stamp_expire),
+                     GNUNET_JSON_pack_timestamp ("stamp_end",
+                                                 legal_end),
+                     GNUNET_JSON_pack_data_auto ("signkey_secmod_sig",
+                                                 &hsk->sm_sig))));
+  return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+                                      struct MHD_Connection *connection)
+{
+  struct TEH_KeyStateHandle *ksh;
+  json_t *reply;
+
+  (void) rh;
+  ksh = TEH_keys_get_state_for_management_only ();
+  if (NULL == ksh)
+  {
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_SERVICE_UNAVAILABLE,
+                                       TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                       "no key state");
+  }
+  sync_key_helpers (ksh->helpers);
+  if (NULL == ksh->management_keys_reply)
+  {
+    struct FutureBuilderContext fbc = {
+      .ksh = ksh,
+      .denoms = json_array (),
+      .signkeys = json_array ()
+    };
+
+    if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) &&
+         (GNUNET_is_zero (&denom_cs_sm_pub)) )
+    {
+      /* Either IPC failed, or neither helper had any denominations 
configured. */
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_GATEWAY,
+                                         
TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
+                                         NULL);
+    }
+    if (GNUNET_is_zero (&esign_sm_pub))
+    {
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_GATEWAY,
+                                         
TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
+                                         NULL);
+    }
+    GNUNET_assert (NULL != fbc.denoms);
+    GNUNET_assert (NULL != fbc.signkeys);
+    GNUNET_CONTAINER_multihashmap_iterate (ksh->helpers->denom_keys,
+                                           &add_future_denomkey_cb,
+                                           &fbc);
+    GNUNET_CONTAINER_multipeermap_iterate (ksh->helpers->esign_keys,
+                                           &add_future_signkey_cb,
+                                           &fbc);
+    reply = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_array_steal ("future_denoms",
+                                    fbc.denoms),
+      GNUNET_JSON_pack_array_steal ("future_signkeys",
+                                    fbc.signkeys),
+      GNUNET_JSON_pack_data_auto ("master_pub",
+                                  &TEH_master_public_key),
+      GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
+                                  &denom_rsa_sm_pub),
+      GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key",
+                                  &denom_cs_sm_pub),
+      GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
+                                  &esign_sm_pub));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Returning GET /management/keys response:\n");
+    if (NULL == reply)
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+                                         NULL);
+    GNUNET_assert (NULL == ksh->management_keys_reply);
+    ksh->management_keys_reply = reply;
+  }
+  else
+  {
+    reply = ksh->management_keys_reply;
+  }
+  return TALER_MHD_reply_json (connection,
+                               reply,
+                               MHD_HTTP_OK);
+}
+
+
+/* end of taler-exchange-httpd_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_keys.h 
b/src/exchange/taler-exchange-httpd_keys.h
new file mode 100644
index 0000000..e170b97
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_keys.h
@@ -0,0 +1,602 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020-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_keys.h
+ * @brief management of our various keys
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+#ifndef TALER_EXCHANGE_HTTPD_KEYS_H
+#define TALER_EXCHANGE_HTTPD_KEYS_H
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature;
+
+
+/**
+ * @brief All information about a denomination key (which is used to
+ * sign coins into existence).
+ */
+struct TEH_DenominationKey
+{
+
+  /**
+   * Decoded denomination public key (the hash of it is in
+   * @e issue, but we sometimes need the full public key as well).
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Hash code of the denomination public key.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Meta data about the type of the denomination, such as fees and validity
+   * periods.
+   */
+  struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+  /**
+   * The long-term offline master key's signature for this denomination.
+   * Signs over @e h_denom_pub and @e meta.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * We store the auditor signatures for this denomination in a DLL.
+   */
+  struct TEH_AuditorSignature *as_head;
+
+  /**
+   * We store the auditor signatures for this denomination in a DLL.
+   */
+  struct TEH_AuditorSignature *as_tail;
+
+  /**
+   * Set to 'true' if this denomination has been revoked and recoup is
+   * thus supported right now.
+   */
+  bool recoup_possible;
+
+};
+
+
+/**
+ * Set of global fees (and options) for a time range.
+ */
+struct TEH_GlobalFee
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct TEH_GlobalFee *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct TEH_GlobalFee *prev;
+
+  /**
+   * Beginning of the validity period (inclusive).
+   */
+  struct GNUNET_TIME_Timestamp start_date;
+
+  /**
+   * End of the validity period (exclusive).
+   */
+  struct GNUNET_TIME_Timestamp end_date;
+
+  /**
+   * How long do unmerged purses stay around at most?
+   */
+  struct GNUNET_TIME_Relative purse_timeout;
+
+  /**
+   * What is the longest history we return?
+   */
+  struct GNUNET_TIME_Relative history_expiration;
+
+  /**
+   * Signature affirming these details.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * Fee structure for operations that do not depend
+   * on a denomination or wire method.
+   */
+  struct TALER_GlobalFeeSet fees;
+
+  /**
+   * Number of free purses per account.
+   */
+  uint32_t purse_account_limit;
+};
+
+
+/**
+ * Snapshot of the (coin and signing) keys (including private keys) of
+ * the exchange.  There can be multiple instances of this struct, as it is
+ * reference counted and only destroyed once the last user is done
+ * with it.  The current instance is acquired using
+ * #TEH_KS_acquire().  Using this function increases the
+ * reference count.  The contents of this structure (except for the
+ * reference counter) should be considered READ-ONLY until it is
+ * ultimately destroyed (as there can be many concurrent users).
+ */
+struct TEH_KeyStateHandle;
+
+
+/**
+ * Run internal invariant checks. For debugging.
+ */
+void
+TEH_check_invariants (void);
+
+/**
+ * Clean up wire subsystem.
+ */
+void
+TEH_wire_done (void);
+
+
+/**
+ * Look up wire fee structure by @a ts.
+ *
+ * @param ts timestamp to lookup wire fees at
+ * @param method wire method to lookup fees for
+ * @return the wire fee details, or
+ *         NULL if none are configured for @a ts and @a method
+ */
+const struct TALER_WireFeeSet *
+TEH_wire_fees_by_time (
+  struct GNUNET_TIME_Timestamp ts,
+  const char *method);
+
+
+/**
+ * Initialize wire subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_wire_init (void);
+
+
+/**
+ * Something changed in the database. Rebuild the wire replies.  This function
+ * should be called if the exchange learns about a new signature from our
+ * master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their wire state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_wire_update_state (void);
+
+
+/**
+ * Return the current key state for this thread.  Possibly re-builds the key
+ * state if we have reason to believe that something changed.
+ *
+ * The result is ONLY valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state()
+ * or #TEH_keys_exchange_sign().
+ *
+ * @return NULL on error
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void);
+
+/**
+ * Obtain the key state if we should NOT run finish_keys_response() because we
+ * only need the state for the /management/keys API
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void);
+
+/**
+ * Something changed in the database. Rebuild all key states.  This function
+ * should be called if the exchange learns about a new signature from an
+ * auditor or our master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their key state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_keys_update_states (void);
+
+
+/**
+ * Look up global fee structure by @a ts.
+ *
+ * @param ksh key state state to look in
+ * @param ts timestamp to lookup global fees at
+ * @return the global fee details, or
+ *         NULL if none are configured for @a ts
+ */
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+  struct TEH_KeyStateHandle *ksh,
+  struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Look up the issue for a denom public key.  Note that the result
+ * must only be used in this thread and only until another key or
+ * key state is resolved.
+ *
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn used to return status message if NULL is returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ *         or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct MHD_Connection *conn,
+  MHD_RESULT *mret);
+
+
+/**
+ * Look up the issue for a denom public key using a given @a ksh.  This allows
+ * requesting multiple denominations with the same @a ksh which thus will
+ * remain valid until the next call to #TEH_keys_denomination_by_hash() or
+ * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state state to look in
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn connection used to return status message if NULL is 
returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ *         or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+  const struct TEH_KeyStateHandle *ksh,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct MHD_Connection *conn,
+  MHD_RESULT *mret);
+
+/**
+ * Information needed to create a blind signature.
+ */
+struct TEH_CoinSignData
+{
+  /**
+   * Hash of key to sign with.
+   */
+  const struct TALER_DenominationHashP *h_denom_pub;
+
+  /**
+   * Blinded planchet to sign over.
+   */
+  const struct TALER_BlindedPlanchet *bp;
+};
+
+
+/**
+ * Request to sign @a csd for melting.
+ *
+ * @param csd identifies data to blindly sign and key to sign with
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bs set to the blind signature on success
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_sign (
+  const struct TEH_CoinSignData *csd,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bs);
+
+
+/**
+ * Request to sign @a csds for melting.
+ *
+ * @param csds array with data to blindly sign (and keys to sign with)
+ * @param csds_length length of @a csds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bss array set to the blind signature on success; must be of 
length @a csds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+  const struct TEH_CoinSignData *csds,
+  unsigned int csds_length,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bss);
+
+
+/**
+ * Information needed to derive the CS r_pub.
+ */
+struct TEH_CsDeriveData
+{
+  /**
+   * Hash of key to sign with.
+   */
+  const struct TALER_DenominationHashP *h_denom_pub;
+
+  /**
+   * Nonce to use.
+   */
+  const struct TALER_CsNonce *nonce;
+};
+
+
+/**
+ * Request to derive CS @a r_pub using the denomination and nonce from @a cdd.
+ *
+ * @param cdd data to compute @a r_pub from
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pub where to write the result
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+  const struct TEH_CsDeriveData *cdd,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *r_pub);
+
+
+/**
+ * Request to derive a bunch of CS @a r_pubs using the
+ * denominations and nonces from @a cdds.
+ *
+ * @param cdds array to compute @a r_pubs from
+ * @param cdds_length length of the @a cdds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pubs array where to write the result; must be of length @a 
cdds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+  const struct TEH_CsDeriveData *cdds,
+  unsigned int cdds_length,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *r_pubs);
+
+
+/**
+ * Revoke the public key associated with @a h_denom_pub.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param h_denom_pub hash of the public key to revoke
+ */
+void
+TEH_keys_denomination_revoke (
+  const struct TALER_DenominationHashP *h_denom_pub);
+
+
+/**
+ * Fully clean up keys subsystem.
+ */
+void
+TEH_keys_finished (void);
+
+
+/**
+ * Resumse all suspended /keys requests, we may now have key material
+ * (or are shutting down).
+ *
+ * @param do_shutdown are we shutting down?
+ */
+void
+TEH_resume_keys_requests (bool do_shutdown);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.  Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.  Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param cls key state state to look in
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+  void *cls,
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign(ps,pub,sig) \
+  ({                                                  \
+    /* check size is set correctly */                 \
+    GNUNET_assert (htonl ((ps)->purpose.size) ==      \
+                   sizeof (*ps));                     \
+    /* check 'ps' begins with the purpose */          \
+    GNUNET_static_assert (((void*) (ps)) ==           \
+                          ((void*) &(ps)->purpose));  \
+    TEH_keys_exchange_sign_ (&(ps)->purpose,          \
+                             pub,                     \
+                             sig);                    \
+  })
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * This allows requesting multiple denominations with the same @a ksh which
+ * thus will remain valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state() or
+ * #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state to use
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign2(ksh,ps,pub,sig)       \
+  ({                                                  \
+    /* check size is set correctly */                 \
+    GNUNET_assert (htonl ((ps)->purpose.size) ==      \
+                   sizeof (*ps));                     \
+    /* check 'ps' begins with the purpose */          \
+    GNUNET_static_assert (((void*) (ps)) ==           \
+                          ((void*) &(ps)->purpose));  \
+    TEH_keys_exchange_sign2_ (ksh,                    \
+                              &(ps)->purpose,         \
+                              pub,                     \
+                              sig);                    \
+  })
+
+
+/**
+ * Revoke the given exchange's signing key.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param exchange_pub key to revoke
+ */
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Function to call to handle requests to "/keys" by sending
+ * back our current key material.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+                      const char *const args[]);
+
+
+/**
+ * Function to call to handle requests to "/management/keys" by sending
+ * back our future key material.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+                                      struct MHD_Connection *connection);
+
+
+/**
+ * Load fees and expiration times (!) for the denomination type configured for
+ * the denomination matching @a h_denom_pub.
+ *
+ * @param ksh key state to load fees from
+ * @param h_denom_pub hash of the denomination public key
+ *        to use to derive the section name of the configuration to use
+ * @param[out] denom_pub set to the denomination public key (to be freed by 
caller!)
+ * @param[out] meta denomination type data to complete
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO if @a h_denom_pub is not known
+ *         #GNUNET_SYSERR on hard errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+                    const struct TALER_DenominationHashP *h_denom_pub,
+                    struct TALER_DenominationPublicKey *denom_pub,
+                    struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+
+/**
+ * Load expiration times for the given onling signing key.
+ *
+ * @param exchange_pub the online signing key
+ * @param[out] meta set to meta data about the key
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+                     struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+
+
+/**
+ * Initialize keys subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_metrics.c 
b/src/exchange/taler-exchange-httpd_metrics.c
new file mode 100644
index 0000000..1542801
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_metrics.c
@@ -0,0 +1,165 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_metrics.c
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_metrics.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include <jansson.h>
+
+
+unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_batch_withdraw_num_coins;
+
+unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+
+MHD_RESULT
+TEH_handler_metrics (struct TEH_RequestContext *rc,
+                     const char *const args[])
+{
+  char *reply;
+  struct MHD_Response *resp;
+  MHD_RESULT ret;
+
+  (void) args;
+  GNUNET_asprintf (&reply,
+                   "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+                   "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+                   "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+                   "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+                   "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+                   "# HELP taler_exchange_serialization_failures "
+                   " number of database serialization errors by type\n"
+                   "# TYPE taler_exchange_serialization_failures counter\n"
+                   "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+                   "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+                   "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+                   "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+                   "# HELP taler_exchange_received_requests "
+                   " number of received requests by type\n"
+                   "# TYPE taler_exchange_received_requests counter\n"
+                   "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+                   "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+                   "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+                   "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+                   "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#if NOT_YET_IMPLEMENTED
+                   "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+                   "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#endif
+                   "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+                   "# HELP taler_exchange_num_signatures "
+                   " number of signatures created by cipher\n"
+                   "# TYPE taler_exchange_num_signatures counter\n"
+                   "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+                   "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+                   "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+                   "# HELP taler_exchange_num_signature_verifications "
+                   " number of signatures verified by cipher\n"
+                   "# TYPE taler_exchange_num_signature_verifications 
counter\n"
+                   "taler_exchange_num_signature_verifications{type=\"%s\"} 
%llu\n"
+                   "taler_exchange_num_signature_verifications{type=\"%s\"} 
%llu\n"
+                   "taler_exchange_num_signature_verifications{type=\"%s\"} 
%llu\n"
+                   "# HELP taler_exchange_num_keyexchanges "
+                   " number of key exchanges done by cipher\n"
+                   "# TYPE taler_exchange_num_keyexchanges counter\n"
+                   "taler_exchange_num_keyexchanges{type=\"%s\"} %llu\n"
+                   "# HELP taler_exchange_batch_withdraw_num_coins "
+                   " number of coins withdrawn in a batch-withdraw request\n"
+                   "# TYPE taler_exchange_batch_withdraw_num_coins counter\n"
+                   "taler_exchange_batch_withdraw_num_coins{} %llu\n",
+                   "deposit",
+                   TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT],
+                   "withdraw",
+                   TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW],
+                   "batch-withdraw",
+                   TEH_METRICS_num_success[TEH_MT_SUCCESS_BATCH_WITHDRAW],
+                   "melt",
+                   TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT],
+                   "refresh-reveal",
+                   TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL],
+                   "other",
+                   TEH_METRICS_num_conflict[TEH_MT_REQUEST_OTHER],
+                   "deposit",
+                   TEH_METRICS_num_conflict[TEH_MT_REQUEST_DEPOSIT],
+                   "withdraw",
+                   TEH_METRICS_num_conflict[TEH_MT_REQUEST_WITHDRAW],
+                   "melt",
+                   TEH_METRICS_num_conflict[TEH_MT_REQUEST_MELT],
+                   "other",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_OTHER],
+                   "deposit",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_DEPOSIT],
+                   "withdraw",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_WITHDRAW],
+                   "melt",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_MELT],
+                   "withdraw",
+                   
TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW],
+#if NOT_YET_IMPLEMENTED
+                   "deposit",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT],
+                   "melt",
+                   TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT],
+#endif
+                   "batch-withdraw",
+                   TEH_METRICS_num_requests[
+                     TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW],
+                   "rsa",
+                   TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA],
+                   "cs",
+                   TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS],
+                   "eddsa",
+                   TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA],
+                   "rsa",
+                   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA],
+                   "cs",
+                   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS],
+                   "eddsa",
+                   TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA],
+                   "ecdh",
+                   TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH],
+                   TEH_METRICS_batch_withdraw_num_coins);
+  resp = MHD_create_response_from_buffer (strlen (reply),
+                                          reply,
+                                          MHD_RESPMEM_MUST_FREE);
+  ret = MHD_queue_response (rc->connection,
+                            MHD_HTTP_OK,
+                            resp);
+  MHD_destroy_response (resp);
+  return ret;
+}
+
+
+/* end of taler-exchange-httpd_metrics.c */
diff --git a/src/exchange/taler-exchange-httpd_metrics.h 
b/src/exchange/taler-exchange-httpd_metrics.h
new file mode 100644
index 0000000..318113c
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_metrics.h
@@ -0,0 +1,136 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014--2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_metrics.h
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_METRICS_H
+#define TALER_EXCHANGE_HTTPD_METRICS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Request types for which we collect metrics.
+ */
+enum TEH_MetricTypeRequest
+{
+  TEH_MT_REQUEST_OTHER = 0,
+  TEH_MT_REQUEST_DEPOSIT = 1,
+  TEH_MT_REQUEST_WITHDRAW = 2,
+  TEH_MT_REQUEST_AGE_WITHDRAW = 3,
+  TEH_MT_REQUEST_MELT = 4,
+  TEH_MT_REQUEST_PURSE_CREATE = 5,
+  TEH_MT_REQUEST_PURSE_MERGE = 6,
+  TEH_MT_REQUEST_RESERVE_PURSE = 7,
+  TEH_MT_REQUEST_PURSE_DEPOSIT = 8,
+  TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 9,
+  TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 10,
+  TEH_MT_REQUEST_IDEMPOTENT_AGE_WITHDRAW = 11,
+  TEH_MT_REQUEST_IDEMPOTENT_MELT = 12,
+  TEH_MT_REQUEST_IDEMPOTENT_BATCH_WITHDRAW = 13,
+  TEH_MT_REQUEST_BATCH_DEPOSIT = 14,
+  TEH_MT_REQUEST_POLICY_FULFILLMENT = 15,
+  TEH_MT_REQUEST_COUNT = 16 /* MUST BE LAST! */
+};
+
+/**
+ * Success types for which we collect metrics.
+ */
+enum TEH_MetricTypeSuccess
+{
+  TEH_MT_SUCCESS_DEPOSIT = 0,
+  TEH_MT_SUCCESS_WITHDRAW = 1,
+  TEH_MT_SUCCESS_AGE_WITHDRAW = 2,
+  TEH_MT_SUCCESS_BATCH_WITHDRAW = 3,
+  TEH_MT_SUCCESS_MELT = 4,
+  TEH_MT_SUCCESS_REFRESH_REVEAL = 5,
+  TEH_MT_SUCCESS_AGE_WITHDRAW_REVEAL = 6,
+  TEH_MT_SUCCESS_COUNT = 7 /* MUST BE LAST! */
+};
+
+/**
+ * Cipher types for which we collect signature metrics.
+ */
+enum TEH_MetricTypeSignature
+{
+  TEH_MT_SIGNATURE_RSA = 0,
+  TEH_MT_SIGNATURE_CS = 1,
+  TEH_MT_SIGNATURE_EDDSA = 2,
+  TEH_MT_SIGNATURE_COUNT = 3
+};
+
+/**
+ * Cipher types for which we collect key exchange metrics.
+ */
+enum TEH_MetricTypeKeyX
+{
+  TEH_MT_KEYX_ECDH = 0,
+  TEH_MT_KEYX_COUNT = 1
+};
+
+/**
+ * Number of requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of successful requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+/**
+ * Number of coins withdrawn in a batch-withdraw request
+ */
+extern unsigned long long TEH_METRICS_batch_withdraw_num_coins;
+
+/**
+ * Number of serialization errors encountered when
+ * handling requests of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of signatures created by the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of signatures verified by the respective cipher.
+ */
+extern unsigned long long 
TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of key exchanges done with the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+/**
+ * Handle a "/metrics" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_metrics (struct TEH_RequestContext *rc,
+                     const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_mhd.c 
b/src/exchange/taler-exchange-httpd_mhd.c
new file mode 100644
index 0000000..8d07c3c
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_mhd.c
@@ -0,0 +1,66 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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_mhd.c
+ * @brief helpers for MHD interaction; these are TALER_EXCHANGE_handler_ 
functions
+ *        that generate simple MHD replies that do not require any real 
operations
+ *        to be performed (error handling, static pages, etc.)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_mhd.h"
+
+
+MHD_RESULT
+TEH_handler_static_response (struct TEH_RequestContext *rc,
+                             const char *const args[])
+{
+  const struct TEH_RequestHandler *rh = rc->rh;
+  size_t dlen;
+
+  (void) args;
+  dlen = (0 == rh->data_size)
+         ? strlen ((const char *) rh->data)
+         : rh->data_size;
+  return TALER_MHD_reply_static (rc->connection,
+                                 rh->response_code,
+                                 rh->mime_type,
+                                 rh->data,
+                                 dlen);
+}
+
+
+MHD_RESULT
+TEH_handler_agpl_redirect (struct TEH_RequestContext *rc,
+                           const char *const args[])
+{
+  (void) args;
+  return TALER_MHD_reply_agpl (rc->connection,
+                               "https://git.taler.net/?p=exchange.git";);
+}
+
+
+/* end of taler-exchange-httpd_mhd.c */
diff --git a/src/exchange/taler-exchange-httpd_mhd.h 
b/src/exchange/taler-exchange-httpd_mhd.h
new file mode 100644
index 0000000..563975b
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_mhd.h
@@ -0,0 +1,57 @@
+/*
+  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_mhd.h
+ * @brief helpers for MHD interaction, used to generate simple responses
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_MHD_H
+#define TALER_EXCHANGE_HTTPD_MHD_H
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the request handler.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_static_response (struct TEH_RequestContext *rc,
+                             const char *const args[]);
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_agpl_redirect (struct TEH_RequestContext *rc,
+                           const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c 
b/src/exchange/taler-exchange-httpd_reserves_close.c
new file mode 100644
index 0000000..bcde808
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_close.c
@@ -0,0 +1,448 @@
+/*
+  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_reserves_close.c
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_close.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_close_transaction.
+ */
+struct ReserveCloseContext
+{
+  /**
+   * Public key of the reserve the inquiry is about.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Timestamp of the request.
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  /**
+   * Client signature approving the request.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * Amount that will be wired (after closing fees).
+   */
+  struct TALER_Amount wire_amount;
+
+  /**
+   * Current balance of the reserve.
+   */
+  struct TALER_Amount balance;
+
+  /**
+   * Where to wire the funds, may be NULL.
+   */
+  const char *payto_uri;
+
+  /**
+   * Hash of the @e payto_uri, if given (otherwise zero).
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * KYC status for the request.
+   */
+  struct TALER_EXCHANGEDB_KycStatus kyc;
+
+  /**
+   * Hash of the payto-URI that was used for the KYC decision.
+   */
+  struct TALER_PaytoHashP kyc_payto;
+
+  /**
+   * Query status from the amount_it() helper function.
+   */
+  enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Send reserve close to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve close to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_close_success (struct MHD_Connection *connection,
+                             const struct ReserveCloseContext *rhc)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_amount ("wire_amount",
+                            &rhc->wire_amount));
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and
+ *        account to iterate over events for
+ * @param limit maximum time-range for which events
+ *        should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ *        events must be returned in reverse chronological
+ *        order
+ * @param cb_cls closure for @a cb
+ */
+static void
+amount_it (void *cls,
+           struct GNUNET_TIME_Absolute limit,
+           TALER_EXCHANGEDB_KycAmountCallback cb,
+           void *cb_cls)
+{
+  struct ReserveCloseContext *rcc = cls;
+  enum GNUNET_GenericReturnValue ret;
+
+  ret = cb (cb_cls,
+            &rcc->balance,
+            GNUNET_TIME_absolute_get ());
+  GNUNET_break (GNUNET_SYSERR != ret);
+  if (GNUNET_OK != ret)
+    return;
+  rcc->qs
+    = TEH_plugin->iterate_reserve_close_info (
+        TEH_plugin->cls,
+        &rcc->kyc_payto,
+        limit,
+        cb,
+        cb_cls);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/close transaction.  Given the public
+ * key of a reserve, return the associated transaction close.  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 ReserveCloseContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_close_transaction (void *cls,
+                           struct MHD_Connection *connection,
+                           MHD_RESULT *mhd_ret)
+{
+  struct ReserveCloseContext *rcc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  char *payto_uri = NULL;
+  const struct TALER_WireFeeSet *wf;
+
+  qs = TEH_plugin->select_reserve_close_info (
+    TEH_plugin->cls,
+    rcc->reserve_pub,
+    &rcc->balance,
+    &payto_uri);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "select_reserve_close_info");
+    return qs;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_NOT_FOUND,
+                                    TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+                                    NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break;
+  }
+
+  if ( (NULL == rcc->payto_uri) &&
+       (NULL == payto_uri) )
+  {
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_CONFLICT,
+                                    
TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
+                                    NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  if ( (NULL != rcc->payto_uri) &&
+       ( (NULL == payto_uri) ||
+         (0 != strcmp (payto_uri,
+                       rcc->payto_uri)) ) )
+  {
+    /* KYC check may be needed: we're not returning
+       the money to the account that funded the reserve
+       in the first place. */
+    char *kyc_needed;
+
+    TALER_payto_hash (rcc->payto_uri,
+                      &rcc->kyc_payto);
+    rcc->qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+    qs = TALER_KYCLOGIC_kyc_test_required (
+      TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
+      &rcc->kyc_payto,
+      TEH_plugin->select_satisfied_kyc_processes,
+      TEH_plugin->cls,
+      &amount_it,
+      rcc,
+      &kyc_needed);
+    if (qs < 0)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        return qs;
+      GNUNET_break (0);
+      *mhd_ret
+        = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                      TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                      "iterate_reserve_close_info");
+      return qs;
+    }
+    if (rcc->qs < 0)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == rcc->qs)
+        return rcc->qs;
+      GNUNET_break (0);
+      *mhd_ret
+        = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                      TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                      "iterate_reserve_close_info");
+      return qs;
+    }
+    if (NULL != kyc_needed)
+    {
+      rcc->kyc.ok = false;
+      qs = TEH_plugin->insert_kyc_requirement_for_account (
+        TEH_plugin->cls,
+        kyc_needed,
+        &rcc->kyc_payto,
+        rcc->reserve_pub,
+        &rcc->kyc.requirement_row);
+      GNUNET_free (kyc_needed);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      {
+        GNUNET_break (0);
+        *mhd_ret
+          = TALER_MHD_reply_with_error (connection,
+                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                        TALER_EC_GENERIC_DB_STORE_FAILED,
+                                        "insert_kyc_requirement_for_account");
+      }
+      return qs;
+    }
+  }
+
+  rcc->kyc.ok = true;
+  if (NULL == rcc->payto_uri)
+    rcc->payto_uri = payto_uri;
+
+  {
+    char *method;
+
+    method = TALER_payto_get_method (rcc->payto_uri);
+    wf = TEH_wire_fees_by_time (rcc->timestamp,
+                                method);
+    if (NULL == wf)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             
TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
+                                             method);
+      GNUNET_free (method);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+    GNUNET_free (method);
+  }
+
+  if (0 >
+      TALER_amount_subtract (&rcc->wire_amount,
+                             &rcc->balance,
+                             &wf->closing))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Client attempted to close reserve with insufficient 
balance.\n");
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &rcc->wire_amount));
+    *mhd_ret = reply_reserve_close_success (connection,
+                                            rcc);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+
+  qs = TEH_plugin->insert_close_request (TEH_plugin->cls,
+                                         rcc->reserve_pub,
+                                         payto_uri,
+                                         &rcc->reserve_sig,
+                                         rcc->timestamp,
+                                         &rcc->balance,
+                                         &wf->closing);
+  GNUNET_free (payto_uri);
+  if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+  {
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "insert_close_request");
+    return qs;
+  }
+  if (qs <= 0)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+    return qs;
+  }
+  return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_close (struct TEH_RequestContext *rc,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            const json_t *root)
+{
+  struct ReserveCloseContext rcc = {
+    .payto_uri = NULL,
+    .reserve_pub = reserve_pub
+  };
+  MHD_RESULT mhd_ret;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rcc.timestamp),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("payto_uri",
+                               &rcc.payto_uri),
+      NULL),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rcc.reserve_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard failure */
+    }
+    if (GNUNET_NO == res)
+    {
+      GNUNET_break_op (0);
+      return MHD_YES; /* failure */
+    }
+  }
+
+  {
+    struct GNUNET_TIME_Timestamp now;
+
+    now = GNUNET_TIME_timestamp_get ();
+    if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+                                          rcc.timestamp.abs_time,
+                                          TIMESTAMP_TOLERANCE))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+                                         NULL);
+    }
+  }
+
+  if (NULL != rcc.payto_uri)
+    TALER_payto_hash (rcc.payto_uri,
+                      &rcc.h_payto);
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_close_verify (rcc.timestamp,
+                                         &rcc.h_payto,
+                                         reserve_pub,
+                                         &rcc.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
+                                       NULL);
+  }
+
+  if (GNUNET_OK !=
+      TEH_DB_run_transaction (rc->connection,
+                              "reserve close",
+                              TEH_MT_REQUEST_OTHER,
+                              &mhd_ret,
+                              &reserve_close_transaction,
+                              &rcc))
+  {
+    return mhd_ret;
+  }
+  if (! rcc.kyc.ok)
+    return TEH_RESPONSE_reply_kyc_required (rc->connection,
+                                            &rcc.kyc_payto,
+                                            &rcc.kyc);
+
+  return reply_reserve_close_success (rc->connection,
+                                      &rcc);
+}
+
+
+/* end of taler-exchange-httpd_reserves_close.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.h 
b/src/exchange/taler-exchange-httpd_reserves_close.h
new file mode 100644
index 0000000..4c70b17
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_close.h
@@ -0,0 +1,41 @@
+/*
+  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 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_reserves_close.h
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/close" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_close (struct TEH_RequestContext *rc,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c 
b/src/exchange/taler-exchange-httpd_reserves_get.c
new file mode 100644
index 0000000..bbaac85
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -0,0 +1,274 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_reserves_get.c
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_get.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Reserve GET request that is long-polling.
+ */
+struct ReservePoller
+{
+  /**
+   * Kept in a DLL.
+   */
+  struct ReservePoller *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct ReservePoller *prev;
+
+  /**
+   * Connection we are handling.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Our request context.
+   */
+  struct TEH_RequestContext *rc;
+
+  /**
+   * Subscription for the database event we are waiting for.
+   */
+  struct GNUNET_DB_EventHandler *eh;
+
+  /**
+   * When will this request time out?
+   */
+  struct GNUNET_TIME_Absolute timeout;
+
+  /**
+   * Public key of the reserve the inquiry is about.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Balance of the reserve, set in the callback.
+   */
+  struct TALER_Amount balance;
+
+  /**
+   * True if we are still suspended.
+   */
+  bool suspended;
+
+};
+
+
+/**
+ * Head of list of requests in long polling.
+ */
+static struct ReservePoller *rp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct ReservePoller *rp_tail;
+
+
+void
+TEH_reserves_get_cleanup ()
+{
+  for (struct ReservePoller *rp = rp_head;
+       NULL != rp;
+       rp = rp->next)
+  {
+    if (rp->suspended)
+    {
+      rp->suspended = false;
+      MHD_resume_connection (rp->connection);
+    }
+  }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+rp_cleanup (struct TEH_RequestContext *rc)
+{
+  struct ReservePoller *rp = rc->rh_ctx;
+
+  GNUNET_assert (! rp->suspended);
+  if (NULL != rp->eh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Cancelling DB event listening on cleanup (odd unless during 
shutdown)\n");
+    TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+                                     rp->eh);
+    rp->eh = NULL;
+  }
+  GNUNET_CONTAINER_DLL_remove (rp_head,
+                               rp_tail,
+                               rp);
+  GNUNET_free (rp);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+             const void *extra,
+             size_t extra_size)
+{
+  struct ReservePoller *rp = cls;
+  struct GNUNET_AsyncScopeSave old_scope;
+
+  (void) extra;
+  (void) extra_size;
+  if (! rp->suspended)
+    return; /* might get multiple wake-up events */
+  GNUNET_async_scope_enter (&rp->rc->async_scope_id,
+                            &old_scope);
+  TEH_check_invariants ();
+  rp->suspended = false;
+  MHD_resume_connection (rp->connection);
+  TALER_MHD_daemon_trigger ();
+  TEH_check_invariants ();
+  GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_get (struct TEH_RequestContext *rc,
+                          const char *const args[1])
+{
+  struct ReservePoller *rp = rc->rh_ctx;
+
+  if (NULL == rp)
+  {
+    rp = GNUNET_new (struct ReservePoller);
+    rp->connection = rc->connection;
+    rp->rc = rc;
+    rc->rh_ctx = rp;
+    rc->rh_cleaner = &rp_cleanup;
+    GNUNET_CONTAINER_DLL_insert (rp_head,
+                                 rp_tail,
+                                 rp);
+    if (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (args[0],
+                                       strlen (args[0]),
+                                       &rp->reserve_pub,
+                                       sizeof (rp->reserve_pub)))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+                                         args[0]);
+    }
+    TALER_MHD_parse_request_timeout (rc->connection,
+                                     &rp->timeout);
+  }
+
+  if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
+       (NULL == rp->eh) )
+  {
+    struct TALER_ReserveEventP rep = {
+      .header.size = htons (sizeof (rep)),
+      .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
+      .reserve_pub = rp->reserve_pub
+    };
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Starting DB event listening until %s\n",
+                GNUNET_TIME_absolute2s (rp->timeout));
+    rp->eh = TEH_plugin->event_listen (
+      TEH_plugin->cls,
+      GNUNET_TIME_absolute_get_remaining (rp->timeout),
+      &rep.header,
+      &db_event_cb,
+      rp);
+  }
+  {
+    enum GNUNET_DB_QueryStatus qs;
+
+    qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
+                                          &rp->reserve_pub,
+                                          &rp->balance);
+    switch (qs)
+    {
+    case GNUNET_DB_STATUS_SOFT_ERROR:
+      GNUNET_break (0); /* single-shot query should never have soft-errors */
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                                         "get_reserve_balance");
+    case GNUNET_DB_STATUS_HARD_ERROR:
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "get_reserve_balance");
+    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Got reserve balance of %s\n",
+                  TALER_amount2s (&rp->balance));
+      return TALER_MHD_REPLY_JSON_PACK (rc->connection,
+                                        MHD_HTTP_OK,
+                                        TALER_JSON_pack_amount ("balance",
+                                                                &rp->balance));
+    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+      if (! GNUNET_TIME_absolute_is_future (rp->timeout))
+      {
+        return TALER_MHD_reply_with_error (rc->connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
+                                           args[0]);
+      }
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Long-polling on reserve for %s\n",
+                  GNUNET_STRINGS_relative_time_to_string (
+                    GNUNET_TIME_absolute_get_remaining (rp->timeout),
+                    true));
+      rp->suspended = true;
+      MHD_suspend_connection (rc->connection);
+      return MHD_YES;
+    }
+  }
+  GNUNET_break (0);
+  return MHD_NO;
+}
+
+
+/* end of taler-exchange-httpd_reserves_get.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h 
b/src/exchange/taler-exchange-httpd_reserves_get.h
new file mode 100644
index 0000000..30c6559
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_get.h
@@ -0,0 +1,53 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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_reserves_get.h
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_GET_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_GET_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown reserves-get subsystem.  Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/reserves/" request.  Parses the
+ * given "reserve_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then respond with the
+ * status of the reserve.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 1, just the reserve_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_get (struct TEH_RequestContext *rc,
+                          const char *const args[1]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c 
b/src/exchange/taler-exchange-httpd_reserves_history.c
new file mode 100644
index 0000000..ffdc6ea
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -0,0 +1,295 @@
+/*
+  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_reserves_history.c
+ * @brief Handle /reserves/$RESERVE_PUB/history requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_history.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_history_transaction.
+ */
+struct ReserveHistoryContext
+{
+  /**
+   * Public key of the reserve the inquiry is about.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Timestamp of the request.
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  /**
+   * Client signature approving the request.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * History of the reserve, set in the callback.
+   */
+  struct TALER_EXCHANGEDB_ReserveHistory *rh;
+
+  /**
+   * Global fees applying to the request.
+   */
+  const struct TEH_GlobalFee *gf;
+
+  /**
+   * Current reserve balance.
+   */
+  struct TALER_Amount balance;
+};
+
+
+/**
+ * Send reserve history to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve history to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_history_success (struct MHD_Connection *connection,
+                               const struct ReserveHistoryContext *rhc)
+{
+  const struct TALER_EXCHANGEDB_ReserveHistory *rh = rhc->rh;
+  json_t *json_history;
+
+  json_history = TEH_RESPONSE_compile_reserve_history (rh);
+  if (NULL == json_history)
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+                                       NULL);
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_amount ("balance",
+                            &rhc->balance),
+    GNUNET_JSON_pack_array_steal ("history",
+                                  json_history));
+}
+
+
+/**
+ * Function implementing /reserves/$RID/history transaction.  Given the public
+ * key of a reserve, return the associated transaction history.  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 ReserveHistoryContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_history_transaction (void *cls,
+                             struct MHD_Connection *connection,
+                             MHD_RESULT *mhd_ret)
+{
+  struct ReserveHistoryContext *rsc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  if (! TALER_amount_is_zero (&rsc->gf->fees.history))
+  {
+    bool balance_ok = false;
+    bool idempotent = true;
+
+    qs = TEH_plugin->insert_history_request (TEH_plugin->cls,
+                                             rsc->reserve_pub,
+                                             &rsc->reserve_sig,
+                                             rsc->timestamp,
+                                             &rsc->gf->fees.history,
+                                             &balance_ok,
+                                             &idempotent);
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      GNUNET_break (0);
+      *mhd_ret
+        = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                      TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                      "get_reserve_history");
+    }
+    if (qs <= 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      return qs;
+    }
+    if (! balance_ok)
+    {
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_CONFLICT,
+                                         
TALER_EC_EXCHANGE_GET_RESERVE_HISTORY_ERROR_INSUFFICIENT_BALANCE,
+                                         NULL);
+    }
+    if (idempotent)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Idempotent /reserves/history request observed. Is caching 
working?\n");
+    }
+  }
+  qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
+                                        rsc->reserve_pub,
+                                        &rsc->balance,
+                                        &rsc->rh);
+  if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+  {
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "get_reserve_history");
+  }
+  return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_history (struct TEH_RequestContext *rc,
+                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                              const json_t *root)
+{
+  struct ReserveHistoryContext rsc;
+  MHD_RESULT mhd_ret;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rsc.timestamp),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rsc.reserve_sig),
+    GNUNET_JSON_spec_end ()
+  };
+  struct GNUNET_TIME_Timestamp now;
+
+  rsc.reserve_pub = reserve_pub;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard failure */
+    }
+    if (GNUNET_NO == res)
+    {
+      GNUNET_break_op (0);
+      return MHD_YES; /* failure */
+    }
+  }
+  now = GNUNET_TIME_timestamp_get ();
+  if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+                                        rsc.timestamp.abs_time,
+                                        TIMESTAMP_TOLERANCE))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+                                       NULL);
+  }
+  {
+    struct TEH_KeyStateHandle *keys;
+
+    keys = TEH_keys_get_state ();
+    if (NULL == keys)
+    {
+      GNUNET_break (0);
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                         NULL);
+    }
+    rsc.gf = TEH_keys_global_fee_by_time (keys,
+                                          rsc.timestamp);
+  }
+  if (NULL == rsc.gf)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+                                       NULL);
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_history_verify (rsc.timestamp,
+                                           &rsc.gf->fees.history,
+                                           reserve_pub,
+                                           &rsc.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_EXCHANGE_RESERVES_HISTORY_BAD_SIGNATURE,
+                                       NULL);
+  }
+  rsc.rh = NULL;
+  if (GNUNET_OK !=
+      TEH_DB_run_transaction (rc->connection,
+                              "get reserve history",
+                              TEH_MT_REQUEST_OTHER,
+                              &mhd_ret,
+                              &reserve_history_transaction,
+                              &rsc))
+  {
+    return mhd_ret;
+  }
+  if (NULL == rsc.rh)
+  {
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
+                                       NULL);
+  }
+  mhd_ret = reply_reserve_history_success (rc->connection,
+                                           &rsc);
+  TEH_plugin->free_reserve_history (TEH_plugin->cls,
+                                    rsc.rh);
+  return mhd_ret;
+}
+
+
+/* end of taler-exchange-httpd_reserves_history.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h 
b/src/exchange/taler-exchange-httpd_reserves_history.h
new file mode 100644
index 0000000..e02cb4d
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_history.h
@@ -0,0 +1,42 @@
+/*
+  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_reserves_history.h
+ * @brief Handle /reserves/$RESERVE_PUB/history requests
+ * @author Florian Dold
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/history" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_history (struct TEH_RequestContext *rc,
+                              const struct TALER_ReservePublicKeyP 
*reserve_pub,
+                              const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c 
b/src/exchange/taler-exchange-httpd_reserves_open.c
new file mode 100644
index 0000000..5048799
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_open.c
@@ -0,0 +1,468 @@
+/*
+  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 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_reserves_open.c
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_open.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_open_transaction.
+ */
+struct ReserveOpenContext
+{
+  /**
+   * Public key of the reserve the inquiry is about.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Desired (minimum) expiration time for the reserve.
+   */
+  struct GNUNET_TIME_Timestamp desired_expiration;
+
+  /**
+   * Actual expiration time for the reserve.
+   */
+  struct GNUNET_TIME_Timestamp reserve_expiration;
+
+  /**
+   * Timestamp of the request.
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  /**
+   * Client signature approving the request.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * Global fees applying to the request.
+   */
+  const struct TEH_GlobalFee *gf;
+
+  /**
+   * Amount to be paid from the reserve.
+   */
+  struct TALER_Amount reserve_payment;
+
+  /**
+   * Actual cost to open the reserve.
+   */
+  struct TALER_Amount open_cost;
+
+  /**
+   * Total amount that was deposited.
+   */
+  struct TALER_Amount total;
+
+  /**
+   * Information about payments by coin.
+   */
+  struct TEH_PurseDepositedCoin *payments;
+
+  /**
+   * Length of the @e payments array.
+   */
+  unsigned int payments_len;
+
+  /**
+   * Desired minimum purse limit.
+   */
+  uint32_t purse_limit;
+
+  /**
+   * Set to true if the reserve balance is too low
+   * for the operation.
+   */
+  bool no_funds;
+
+};
+
+
+/**
+ * Send reserve open to client.
+ *
+ * @param connection connection to the client
+ * @param rsc reserve open data to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_open_success (struct MHD_Connection *connection,
+                            const struct ReserveOpenContext *rsc)
+{
+  struct GNUNET_TIME_Timestamp now;
+  struct GNUNET_TIME_Timestamp re;
+  unsigned int status;
+
+  status = MHD_HTTP_OK;
+  if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+                                 <,
+                                 rsc->desired_expiration))
+    status = MHD_HTTP_PAYMENT_REQUIRED;
+  now = GNUNET_TIME_timestamp_get ();
+  if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+                                 <,
+                                 now))
+    re = now;
+  else
+    re = rsc->reserve_expiration;
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    status,
+    GNUNET_JSON_pack_timestamp ("reserve_expiration",
+                                re),
+    TALER_JSON_pack_amount ("open_cost",
+                            &rsc->open_cost));
+}
+
+
+/**
+ * Cleans up information in @a rsc, but does not
+ * free @a rsc itself (allocated on the stack!).
+ *
+ * @param[in] rsc struct with information to clean up
+ */
+static void
+cleanup_rsc (struct ReserveOpenContext *rsc)
+{
+  for (unsigned int i = 0; i<rsc->payments_len; i++)
+  {
+    TEH_common_purse_deposit_free_coin (&rsc->payments[i]);
+  }
+  GNUNET_free (rsc->payments);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/open transaction.  Given the public
+ * key of a reserve, return the associated transaction open.  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 ReserveOpenContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_open_transaction (void *cls,
+                          struct MHD_Connection *connection,
+                          MHD_RESULT *mhd_ret)
+{
+  struct ReserveOpenContext *rsc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  for (unsigned int i = 0; i<rsc->payments_len; i++)
+  {
+    struct TEH_PurseDepositedCoin *coin = &rsc->payments[i];
+    bool insufficient_funds = true;
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Make coin %u known\n",
+                i);
+    qs = TEH_make_coin_known (&coin->cpi,
+                              connection,
+                              &coin->known_coin_id,
+                              mhd_ret);
+    if (qs < 0)
+      return qs;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Insert open deposit %u known\n",
+                i);
+    qs = TEH_plugin->insert_reserve_open_deposit (
+      TEH_plugin->cls,
+      &coin->cpi,
+      &coin->coin_sig,
+      coin->known_coin_id,
+      &coin->amount,
+      &rsc->reserve_sig,
+      rsc->reserve_pub,
+      &insufficient_funds);
+    /* 0 == qs is fine, then the coin was already
+       spent for this very operation as identified
+       by reserve_sig! */
+    if (qs < 0)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        return qs;
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_STORE_FAILED,
+                                             "insert_reserve_open_deposit");
+      return qs;
+    }
+    if (insufficient_funds)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Handle insufficient funds\n");
+      *mhd_ret
+        = TEH_RESPONSE_reply_coin_insufficient_funds (
+            connection,
+            TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+            &coin->cpi.denom_pub_hash,
+            &coin->cpi.coin_pub);
+      return GNUNET_DB_STATUS_HARD_ERROR;
+    }
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Do reserve open with reserve payment of %s\n",
+              TALER_amount2s (&rsc->total));
+  qs = TEH_plugin->do_reserve_open (TEH_plugin->cls,
+                                    /* inputs */
+                                    rsc->reserve_pub,
+                                    &rsc->total,
+                                    &rsc->reserve_payment,
+                                    rsc->purse_limit,
+                                    &rsc->reserve_sig,
+                                    rsc->desired_expiration,
+                                    rsc->timestamp,
+                                    &rsc->gf->fees.account,
+                                    /* outputs */
+                                    &rsc->no_funds,
+                                    &rsc->open_cost,
+                                    &rsc->reserve_expiration);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "do_reserve_open");
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_NOT_FOUND,
+                                    TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+                                    NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break;
+  }
+  if (rsc->no_funds)
+  {
+    TEH_plugin->rollback (TEH_plugin->cls);
+    *mhd_ret
+      = TEH_RESPONSE_reply_reserve_insufficient_balance (
+          connection,
+          TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
+          &rsc->reserve_payment,
+          rsc->reserve_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+                           const struct TALER_ReservePublicKeyP *reserve_pub,
+                           const json_t *root)
+{
+  struct ReserveOpenContext rsc;
+  const json_t *payments;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rsc.timestamp),
+    GNUNET_JSON_spec_timestamp ("reserve_expiration",
+                                &rsc.desired_expiration),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rsc.reserve_sig),
+    GNUNET_JSON_spec_uint32 ("purse_limit",
+                             &rsc.purse_limit),
+    GNUNET_JSON_spec_array_const ("payments",
+                                  &payments),
+    TALER_JSON_spec_amount ("reserve_payment",
+                            TEH_currency,
+                            &rsc.reserve_payment),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rsc.reserve_pub = reserve_pub;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard failure */
+    }
+    if (GNUNET_NO == res)
+    {
+      GNUNET_break_op (0);
+      return MHD_YES; /* failure */
+    }
+  }
+
+  {
+    struct GNUNET_TIME_Timestamp now;
+
+    now = GNUNET_TIME_timestamp_get ();
+    if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+                                          rsc.timestamp.abs_time,
+                                          TIMESTAMP_TOLERANCE))
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+                                         NULL);
+    }
+  }
+
+  rsc.payments_len = json_array_size (payments);
+  rsc.payments = GNUNET_new_array (rsc.payments_len,
+                                   struct TEH_PurseDepositedCoin);
+  rsc.total = rsc.reserve_payment;
+  for (unsigned int i = 0; i<rsc.payments_len; i++)
+  {
+    struct TEH_PurseDepositedCoin *coin = &rsc.payments[i];
+    enum GNUNET_GenericReturnValue res;
+
+    res = TEH_common_purse_deposit_parse_coin (
+      rc->connection,
+      coin,
+      json_array_get (payments,
+                      i));
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_break (0);
+      cleanup_rsc (&rsc);
+      return MHD_NO;   /* hard failure */
+    }
+    if (GNUNET_NO == res)
+    {
+      GNUNET_break_op (0);
+      cleanup_rsc (&rsc);
+      return MHD_YES;   /* failure */
+    }
+    if (0 >
+        TALER_amount_add (&rsc.total,
+                          &rsc.total,
+                          &coin->amount_minus_fee))
+    {
+      GNUNET_break (0);
+      cleanup_rsc (&rsc);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+                                         NULL);
+    }
+  }
+
+  {
+    struct TEH_KeyStateHandle *keys;
+
+    keys = TEH_keys_get_state ();
+    if (NULL == keys)
+    {
+      GNUNET_break (0);
+      cleanup_rsc (&rsc);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                         NULL);
+    }
+    rsc.gf = TEH_keys_global_fee_by_time (keys,
+                                          rsc.timestamp);
+  }
+  if (NULL == rsc.gf)
+  {
+    GNUNET_break (0);
+    cleanup_rsc (&rsc);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+                                       NULL);
+  }
+
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_open_verify (&rsc.reserve_payment,
+                                        rsc.timestamp,
+                                        rsc.desired_expiration,
+                                        rsc.purse_limit,
+                                        reserve_pub,
+                                        &rsc.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    cleanup_rsc (&rsc);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE,
+                                       NULL);
+  }
+
+  {
+    MHD_RESULT mhd_ret;
+
+    if (GNUNET_OK !=
+        TEH_DB_run_transaction (rc->connection,
+                                "reserve open",
+                                TEH_MT_REQUEST_OTHER,
+                                &mhd_ret,
+                                &reserve_open_transaction,
+                                &rsc))
+    {
+      cleanup_rsc (&rsc);
+      return mhd_ret;
+    }
+  }
+
+  {
+    MHD_RESULT mhd_ret;
+
+    mhd_ret = reply_reserve_open_success (rc->connection,
+                                          &rsc);
+    cleanup_rsc (&rsc);
+    return mhd_ret;
+  }
+}
+
+
+/* end of taler-exchange-httpd_reserves_open.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.h 
b/src/exchange/taler-exchange-httpd_reserves_open.h
new file mode 100644
index 0000000..e28c22c
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_open.h
@@ -0,0 +1,41 @@
+/*
+  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 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_reserves_open.h
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/open" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+                           const struct TALER_ReservePublicKeyP *reserve_pub,
+                           const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_status.c 
b/src/exchange/taler-exchange-httpd_reserves_status.c
new file mode 100644
index 0000000..4e7b4f4
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_status.c
@@ -0,0 +1,243 @@
+/*
+  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_reserves_status.c
+ * @brief Handle /reserves/$RESERVE_PUB STATUS requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler_mhd_lib.h"
+#include "taler_json_lib.h"
+#include "taler_dbevents.h"
+#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_reserves_status.h"
+#include "taler-exchange-httpd_responses.h"
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+  GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_status_transaction.
+ */
+struct ReserveStatusContext
+{
+  /**
+   * Public key of the reserve the inquiry is about.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * History of the reserve, set in the callback.
+   */
+  struct TALER_EXCHANGEDB_ReserveHistory *rh;
+
+  /**
+   * Sum of incoming transactions within the returned history.
+   * (currently not used).
+   */
+  struct TALER_Amount balance_in;
+
+  /**
+   * Sum of outgoing transactions within the returned history.
+   * (currently not used).
+   */
+  struct TALER_Amount balance_out;
+
+  /**
+   * Current reserve balance.
+   */
+  struct TALER_Amount balance;
+};
+
+
+/**
+ * Send reserve status to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve history to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_status_success (struct MHD_Connection *connection,
+                              const struct ReserveStatusContext *rhc)
+{
+  const struct TALER_EXCHANGEDB_ReserveHistory *rh = rhc->rh;
+  json_t *json_history;
+
+  json_history = TEH_RESPONSE_compile_reserve_history (rh);
+  if (NULL == json_history)
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+                                       NULL);
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_amount ("balance",
+                            &rhc->balance),
+    GNUNET_JSON_pack_array_steal ("history",
+                                  json_history));
+}
+
+
+/**
+ * Function implementing /reserves/ STATUS transaction.
+ * Execute a /reserves/ STATUS.  Given the public key of a reserve,
+ * return the associated transaction history.  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 ReserveStatusContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_status_transaction (void *cls,
+                            struct MHD_Connection *connection,
+                            MHD_RESULT *mhd_ret)
+{
+  struct ReserveStatusContext *rsc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = TEH_plugin->get_reserve_status (TEH_plugin->cls,
+                                       rsc->reserve_pub,
+                                       &rsc->balance_in,
+                                       &rsc->balance_out,
+                                       &rsc->rh);
+  if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+  {
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "get_reserve_status");
+  }
+  qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
+                                        rsc->reserve_pub,
+                                        &rsc->balance);
+  if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+  {
+    GNUNET_break (0);
+    *mhd_ret
+      = TALER_MHD_reply_with_error (connection,
+                                    MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                    TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                    "get_reserve_balance");
+  }
+  return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_status (struct TEH_RequestContext *rc,
+                             const struct TALER_ReservePublicKeyP *reserve_pub,
+                             const json_t *root)
+{
+  struct ReserveStatusContext rsc;
+  MHD_RESULT mhd_ret;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct TALER_ReserveSignatureP reserve_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &reserve_sig),
+    GNUNET_JSON_spec_end ()
+  };
+  struct GNUNET_TIME_Timestamp now;
+
+  rsc.reserve_pub = reserve_pub;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_SYSERR == res)
+    {
+      GNUNET_break (0);
+      return MHD_NO; /* hard failure */
+    }
+    if (GNUNET_NO == res)
+    {
+      GNUNET_break_op (0);
+      return MHD_YES; /* failure */
+    }
+  }
+  now = GNUNET_TIME_timestamp_get ();
+  if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+                                        timestamp.abs_time,
+                                        TIMESTAMP_TOLERANCE))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+                                       NULL);
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_status_verify (timestamp,
+                                          reserve_pub,
+                                          &reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_EXCHANGE_RESERVES_STATUS_BAD_SIGNATURE,
+                                       NULL);
+  }
+  rsc.rh = NULL;
+  if (GNUNET_OK !=
+      TEH_DB_run_transaction (rc->connection,
+                              "get reserve status",
+                              TEH_MT_REQUEST_OTHER,
+                              &mhd_ret,
+                              &reserve_status_transaction,
+                              &rsc))
+  {
+    return mhd_ret;
+  }
+  if (NULL == rsc.rh)
+  {
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_NOT_FOUND,
+                                       
TALER_EC_EXCHANGE_RESERVES_STATUS_UNKNOWN,
+                                       NULL);
+  }
+  mhd_ret = reply_reserve_status_success (rc->connection,
+                                          &rsc);
+  TEH_plugin->free_reserve_history (TEH_plugin->cls,
+                                    rsc.rh);
+  return mhd_ret;
+}
+
+
+/* end of taler-exchange-httpd_reserves_status.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_status.h 
b/src/exchange/taler-exchange-httpd_reserves_status.h
new file mode 100644
index 0000000..831b270
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_reserves_status.h
@@ -0,0 +1,43 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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_reserves_status.h
+ * @brief Handle /reserves/$RESERVE_PUB STATUS requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESERVES_STATUS_H
+#define TALER_EXCHANGE_HTTPD_RESERVES_STATUS_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/status" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_status (struct TEH_RequestContext *rc,
+                             const struct TALER_ReservePublicKeyP *reserve_pub,
+                             const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_responses.c 
b/src/exchange/taler-exchange-httpd_responses.c
new file mode 100644
index 0000000..7d2d7a9
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_responses.c
@@ -0,0 +1,1190 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_responses.c
+ * @brief API for generating generic replies of the exchange; these
+ *        functions are called TEH_RESPONSE_reply_ and they generate
+ *        and queue MHD response objects for a given connection.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <zlib.h>
+#include "taler-exchange-httpd_responses.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param coin_pub public key of the coin
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+json_t *
+TEH_RESPONSE_compile_transaction_history (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+  json_t *history;
+
+  history = json_array ();
+  if (NULL == history)
+  {
+    GNUNET_break (0); /* out of memory!? */
+    return NULL;
+  }
+  for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
+       NULL != pos;
+       pos = pos->next)
+  {
+    switch (pos->type)
+    {
+    case TALER_EXCHANGEDB_TT_DEPOSIT:
+      {
+        const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
+          pos->details.deposit;
+        struct TALER_MerchantWireHashP h_wire;
+
+        TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
+                                            &deposit->wire_salt,
+                                            &h_wire);
+#if ENABLE_SANITY_CHECKS
+        /* internal sanity check before we hand out a bogus sig... */
+        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->no_wallet_data_hash
+              ? NULL
+              : &deposit->wallet_data_hash,
+              deposit->no_age_commitment
+              ? NULL
+              : &deposit->h_age_commitment,
+              &deposit->h_policy,
+              &deposit->h_denom_pub,
+              deposit->timestamp,
+              &deposit->merchant_pub,
+              deposit->refund_deadline,
+              coin_pub,
+              &deposit->csig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+#endif
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "DEPOSIT"),
+                TALER_JSON_pack_amount ("amount",
+                                        &deposit->amount_with_fee),
+                TALER_JSON_pack_amount ("deposit_fee",
+                                        &deposit->deposit_fee),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            deposit->timestamp),
+                GNUNET_JSON_pack_allow_null (
+                  GNUNET_JSON_pack_timestamp ("refund_deadline",
+                                              deposit->refund_deadline)),
+                GNUNET_JSON_pack_data_auto ("merchant_pub",
+                                            &deposit->merchant_pub),
+                GNUNET_JSON_pack_data_auto ("h_contract_terms",
+                                            &deposit->h_contract_terms),
+                GNUNET_JSON_pack_data_auto ("h_wire",
+                                            &h_wire),
+                GNUNET_JSON_pack_allow_null (
+                  deposit->no_age_commitment ?
+                  GNUNET_JSON_pack_string (
+                    "h_age_commitment", NULL) :
+                  GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                              &deposit->h_age_commitment)),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &deposit->csig))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_MELT:
+      {
+        const struct TALER_EXCHANGEDB_MeltListEntry *melt =
+          pos->details.melt;
+        const struct TALER_AgeCommitmentHash *phac = NULL;
+
+#if ENABLE_SANITY_CHECKS
+        TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+        if (GNUNET_OK !=
+            TALER_wallet_melt_verify (
+              &melt->amount_with_fee,
+              &melt->melt_fee,
+              &melt->rc,
+              &melt->h_denom_pub,
+              &melt->h_age_commitment,
+              coin_pub,
+              &melt->coin_sig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+#endif
+
+        /* Age restriction is optional.  We communicate a NULL value to
+         * JSON_PACK below */
+        if (! melt->no_age_commitment)
+          phac = &melt->h_age_commitment;
+
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "MELT"),
+                TALER_JSON_pack_amount ("amount",
+                                        &melt->amount_with_fee),
+                TALER_JSON_pack_amount ("melt_fee",
+                                        &melt->melt_fee),
+                GNUNET_JSON_pack_data_auto ("rc",
+                                            &melt->rc),
+                GNUNET_JSON_pack_allow_null (
+                  GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                              phac)),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &melt->coin_sig))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_REFUND:
+      {
+        const struct TALER_EXCHANGEDB_RefundListEntry *refund =
+          pos->details.refund;
+        struct TALER_Amount value;
+
+#if ENABLE_SANITY_CHECKS
+        TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+        if (GNUNET_OK !=
+            TALER_merchant_refund_verify (
+              coin_pub,
+              &refund->h_contract_terms,
+              refund->rtransaction_id,
+              &refund->refund_amount,
+              &refund->merchant_pub,
+              &refund->merchant_sig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+#endif
+        if (0 >
+            TALER_amount_subtract (&value,
+                                   &refund->refund_amount,
+                                   &refund->refund_fee))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "REFUND"),
+                TALER_JSON_pack_amount ("amount",
+                                        &value),
+                TALER_JSON_pack_amount ("refund_fee",
+                                        &refund->refund_fee),
+                GNUNET_JSON_pack_data_auto ("h_contract_terms",
+                                            &refund->h_contract_terms),
+                GNUNET_JSON_pack_data_auto ("merchant_pub",
+                                            &refund->merchant_pub),
+                GNUNET_JSON_pack_uint64 ("rtransaction_id",
+                                         refund->rtransaction_id),
+                GNUNET_JSON_pack_data_auto ("merchant_sig",
+                                            &refund->merchant_sig))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+      {
+        struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+          pos->details.old_coin_recoup;
+        struct TALER_ExchangePublicKeyP epub;
+        struct TALER_ExchangeSignatureP esig;
+
+        if (TALER_EC_NONE !=
+            TALER_exchange_online_confirm_recoup_refresh_sign (
+              &TEH_keys_exchange_sign_,
+              pr->timestamp,
+              &pr->value,
+              &pr->coin.coin_pub,
+              &pr->old_coin_pub,
+              &epub,
+              &esig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        /* NOTE: we could also provide coin_pub's coin_sig, denomination key 
hash and
+           the denomination key's RSA signature over coin_pub, but as the
+           wallet should really already have this information (and cannot
+           check or do anything with it anyway if it doesn't), it seems
+           strictly unnecessary. */
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "OLD-COIN-RECOUP"),
+                TALER_JSON_pack_amount ("amount",
+                                        &pr->value),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &esig),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &epub),
+                GNUNET_JSON_pack_data_auto ("coin_pub",
+                                            &pr->coin.coin_pub),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            pr->timestamp))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_RECOUP:
+      {
+        const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
+          pos->details.recoup;
+        struct TALER_ExchangePublicKeyP epub;
+        struct TALER_ExchangeSignatureP esig;
+
+        if (TALER_EC_NONE !=
+            TALER_exchange_online_confirm_recoup_sign (
+              &TEH_keys_exchange_sign_,
+              recoup->timestamp,
+              &recoup->value,
+              coin_pub,
+              &recoup->reserve_pub,
+              &epub,
+              &esig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "RECOUP"),
+                TALER_JSON_pack_amount ("amount",
+                                        &recoup->value),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &esig),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &epub),
+                GNUNET_JSON_pack_data_auto ("reserve_pub",
+                                            &recoup->reserve_pub),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &recoup->coin_sig),
+                GNUNET_JSON_pack_data_auto ("coin_blind",
+                                            &recoup->coin_blind),
+                GNUNET_JSON_pack_data_auto ("reserve_pub",
+                                            &recoup->reserve_pub),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            recoup->timestamp))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+      {
+        struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+          pos->details.recoup_refresh;
+        struct TALER_ExchangePublicKeyP epub;
+        struct TALER_ExchangeSignatureP esig;
+
+        if (TALER_EC_NONE !=
+            TALER_exchange_online_confirm_recoup_refresh_sign (
+              &TEH_keys_exchange_sign_,
+              pr->timestamp,
+              &pr->value,
+              coin_pub,
+              &pr->old_coin_pub,
+              &epub,
+              &esig))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        /* NOTE: we could also provide coin_pub's coin_sig, denomination key
+           hash and the denomination key's RSA signature over coin_pub, but as
+           the wallet should really already have this information (and cannot
+           check or do anything with it anyway if it doesn't), it seems
+           strictly unnecessary. */
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "RECOUP-REFRESH"),
+                TALER_JSON_pack_amount ("amount",
+                                        &pr->value),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &esig),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &epub),
+                GNUNET_JSON_pack_data_auto ("old_coin_pub",
+                                            &pr->old_coin_pub),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &pr->coin_sig),
+                GNUNET_JSON_pack_data_auto ("coin_blind",
+                                            &pr->coin_blind),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            pr->timestamp))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        break;
+      }
+
+    case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+      {
+        struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
+          = pos->details.purse_deposit;
+        const struct TALER_AgeCommitmentHash *phac = NULL;
+
+        if (! pd->no_age_commitment)
+          phac = &pd->h_age_commitment;
+
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "PURSE-DEPOSIT"),
+                TALER_JSON_pack_amount ("amount",
+                                        &pd->amount),
+                GNUNET_JSON_pack_string ("exchange_base_url",
+                                         NULL == pd->exchange_base_url
+                                         ? TEH_base_url
+                                         : pd->exchange_base_url),
+                GNUNET_JSON_pack_allow_null (
+                  GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                              phac)),
+                GNUNET_JSON_pack_data_auto ("purse_pub",
+                                            &pd->purse_pub),
+                GNUNET_JSON_pack_bool ("refunded",
+                                       pd->refunded),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &pd->coin_sig))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        break;
+      }
+
+    case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+      {
+        const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
+          pos->details.purse_refund;
+        struct TALER_Amount value;
+        enum TALER_ErrorCode ec;
+        struct TALER_ExchangePublicKeyP epub;
+        struct TALER_ExchangeSignatureP esig;
+
+        if (0 >
+            TALER_amount_subtract (&value,
+                                   &prefund->refund_amount,
+                                   &prefund->refund_fee))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        ec = TALER_exchange_online_purse_refund_sign (
+          &TEH_keys_exchange_sign_,
+          &value,
+          &prefund->refund_fee,
+          coin_pub,
+          &prefund->purse_pub,
+          &epub,
+          &esig);
+        if (TALER_EC_NONE != ec)
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "PURSE-REFUND"),
+                TALER_JSON_pack_amount ("amount",
+                                        &value),
+                TALER_JSON_pack_amount ("refund_fee",
+                                        &prefund->refund_fee),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &esig),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &epub),
+                GNUNET_JSON_pack_data_auto ("purse_pub",
+                                            &prefund->purse_pub))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+      }
+      break;
+
+    case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+      {
+        struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
+          = pos->details.reserve_open;
+
+        if (0 !=
+            json_array_append_new (
+              history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "RESERVE-OPEN-DEPOSIT"),
+                TALER_JSON_pack_amount ("coin_contribution",
+                                        &role->coin_contribution),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &role->reserve_sig),
+                GNUNET_JSON_pack_data_auto ("coin_sig",
+                                            &role->coin_sig))))
+        {
+          GNUNET_break (0);
+          json_decref (history);
+          return NULL;
+        }
+        break;
+      }
+    }
+  }
+  return history;
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_unknown_denom_pub_hash (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph)
+{
+  struct TALER_ExchangePublicKeyP epub;
+  struct TALER_ExchangeSignatureP esig;
+  struct GNUNET_TIME_Timestamp now;
+  enum TALER_ErrorCode ec;
+
+  now = GNUNET_TIME_timestamp_get ();
+  ec = TALER_exchange_online_denomination_unknown_sign (
+    &TEH_keys_exchange_sign_,
+    now,
+    dph,
+    &epub,
+    &esig);
+  if (TALER_EC_NONE != ec)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       ec,
+                                       NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                now),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &epub),
+    GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                &esig),
+    GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                dph));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_expired_denom_pub_hash (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph,
+  enum TALER_ErrorCode ec,
+  const char *oper)
+{
+  struct TALER_ExchangePublicKeyP epub;
+  struct TALER_ExchangeSignatureP esig;
+  enum TALER_ErrorCode ecr;
+  struct GNUNET_TIME_Timestamp now
+    = GNUNET_TIME_timestamp_get ();
+
+  ecr = TALER_exchange_online_denomination_expired_sign (
+    &TEH_keys_exchange_sign_,
+    now,
+    dph,
+    oper,
+    &epub,
+    &esig);
+  if (TALER_EC_NONE != ecr)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       ec,
+                                       NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_GONE,
+    TALER_JSON_pack_ec (ec),
+    GNUNET_JSON_pack_string ("oper",
+                             oper),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                now),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &epub),
+    GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                &esig),
+    GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                dph));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph)
+{
+  struct TALER_ExchangePublicKeyP epub;
+  struct TALER_ExchangeSignatureP esig;
+  struct GNUNET_TIME_Timestamp now;
+  enum TALER_ErrorCode ec;
+
+  now = GNUNET_TIME_timestamp_get ();
+  ec = TALER_exchange_online_denomination_unknown_sign (
+    &TEH_keys_exchange_sign_,
+    now,
+    dph,
+    &epub,
+    &esig);
+  if (TALER_EC_NONE != ec)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       ec,
+                                       NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_NOT_FOUND,
+    TALER_JSON_pack_ec (
+      TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                now),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &epub),
+    GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                &esig),
+    GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                dph));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_coin_insufficient_funds (
+  struct MHD_Connection *connection,
+  enum TALER_ErrorCode ec,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+  struct TALER_EXCHANGEDB_TransactionList *tl;
+  enum GNUNET_DB_QueryStatus qs;
+  json_t *history;
+
+  TEH_plugin->rollback (TEH_plugin->cls);
+  if (GNUNET_OK !=
+      TEH_plugin->start_read_only (TEH_plugin->cls,
+                                   "get_coin_transactions"))
+  {
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_GENERIC_DB_START_FAILED,
+      NULL);
+  }
+  qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+                                          coin_pub,
+                                          &tl);
+  TEH_plugin->rollback (TEH_plugin->cls);
+  if (0 > qs)
+  {
+    return TALER_MHD_reply_with_error (
+      connection,
+      MHD_HTTP_INTERNAL_SERVER_ERROR,
+      TALER_EC_GENERIC_DB_FETCH_FAILED,
+      NULL);
+  }
+
+  history = TEH_RESPONSE_compile_transaction_history (coin_pub,
+                                                      tl);
+  TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+                                          tl);
+  if (NULL == history)
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+                                       "Failed to generated proof of 
insufficient funds");
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    TALER_ErrorCode_get_http_status_safe (ec),
+    TALER_JSON_pack_ec (ec),
+    GNUNET_JSON_pack_data_auto ("coin_pub",
+                                coin_pub),
+    GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                h_denom_pub),
+    GNUNET_JSON_pack_array_steal ("history",
+                                  history));
+}
+
+
+json_t *
+TEH_RESPONSE_compile_reserve_history (
+  const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+  json_t *json_history;
+
+  json_history = json_array ();
+  GNUNET_assert (NULL != json_history);
+  for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
+       NULL != pos;
+       pos = pos->next)
+  {
+    switch (pos->type)
+    {
+    case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+      {
+        const struct TALER_EXCHANGEDB_BankTransfer *bank =
+          pos->details.bank;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "CREDIT"),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            bank->execution_date),
+                GNUNET_JSON_pack_string ("sender_account_url",
+                                         bank->sender_account_details),
+                GNUNET_JSON_pack_uint64 ("wire_reference",
+                                         bank->wire_reference),
+                TALER_JSON_pack_amount ("amount",
+                                        &bank->amount))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
+      {
+        const struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw
+          = pos->details.withdraw;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "WITHDRAW"),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &withdraw->reserve_sig),
+                GNUNET_JSON_pack_data_auto ("h_coin_envelope",
+                                            &withdraw->h_coin_envelope),
+                GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                            &withdraw->denom_pub_hash),
+                TALER_JSON_pack_amount ("withdraw_fee",
+                                        &withdraw->withdraw_fee),
+                TALER_JSON_pack_amount ("amount",
+                                        &withdraw->amount_with_fee))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+      {
+        const struct TALER_EXCHANGEDB_Recoup *recoup
+          = pos->details.recoup;
+        struct TALER_ExchangePublicKeyP pub;
+        struct TALER_ExchangeSignatureP sig;
+
+        if (TALER_EC_NONE !=
+            TALER_exchange_online_confirm_recoup_sign (
+              &TEH_keys_exchange_sign_,
+              recoup->timestamp,
+              &recoup->value,
+              &recoup->coin.coin_pub,
+              &recoup->reserve_pub,
+              &pub,
+              &sig))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "RECOUP"),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &pub),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &sig),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            recoup->timestamp),
+                TALER_JSON_pack_amount ("amount",
+                                        &recoup->value),
+                GNUNET_JSON_pack_data_auto ("coin_pub",
+                                            &recoup->coin.coin_pub))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+      {
+        const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
+          pos->details.closing;
+        struct TALER_ExchangePublicKeyP pub;
+        struct TALER_ExchangeSignatureP sig;
+
+        if (TALER_EC_NONE !=
+            TALER_exchange_online_reserve_closed_sign (
+              &TEH_keys_exchange_sign_,
+              closing->execution_date,
+              &closing->amount,
+              &closing->closing_fee,
+              closing->receiver_account_details,
+              &closing->wtid,
+              &pos->details.closing->reserve_pub,
+              &pub,
+              &sig))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "CLOSING"),
+                GNUNET_JSON_pack_string ("receiver_account_details",
+                                         closing->receiver_account_details),
+                GNUNET_JSON_pack_data_auto ("wtid",
+                                            &closing->wtid),
+                GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                            &pub),
+                GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                            &sig),
+                GNUNET_JSON_pack_timestamp ("timestamp",
+                                            closing->execution_date),
+                TALER_JSON_pack_amount ("amount",
+                                        &closing->amount),
+                TALER_JSON_pack_amount ("closing_fee",
+                                        &closing->closing_fee))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+      {
+        const struct TALER_EXCHANGEDB_PurseMerge *merge =
+          pos->details.merge;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "MERGE"),
+                GNUNET_JSON_pack_data_auto ("h_contract_terms",
+                                            &merge->h_contract_terms),
+                GNUNET_JSON_pack_data_auto ("merge_pub",
+                                            &merge->merge_pub),
+                GNUNET_JSON_pack_uint64 ("min_age",
+                                         merge->min_age),
+                GNUNET_JSON_pack_uint64 ("flags",
+                                         merge->flags),
+                GNUNET_JSON_pack_data_auto ("purse_pub",
+                                            &merge->purse_pub),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &merge->reserve_sig),
+                GNUNET_JSON_pack_timestamp ("merge_timestamp",
+                                            merge->merge_timestamp),
+                GNUNET_JSON_pack_timestamp ("purse_expiration",
+                                            merge->purse_expiration),
+                TALER_JSON_pack_amount ("purse_fee",
+                                        &merge->purse_fee),
+                TALER_JSON_pack_amount ("amount",
+                                        &merge->amount_with_fee),
+                GNUNET_JSON_pack_bool ("merged",
+                                       merge->merged))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+      {
+        const struct TALER_EXCHANGEDB_HistoryRequest *history =
+          pos->details.history;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "HISTORY"),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &history->reserve_sig),
+                GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                            history->request_timestamp),
+                TALER_JSON_pack_amount ("amount",
+                                        &history->history_fee))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+
+    case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+      {
+        const struct TALER_EXCHANGEDB_OpenRequest *orq =
+          pos->details.open_request;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "OPEN"),
+                GNUNET_JSON_pack_uint64 ("requested_min_purses",
+                                         orq->purse_limit),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &orq->reserve_sig),
+                GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                            orq->request_timestamp),
+                GNUNET_JSON_pack_timestamp ("requested_expiration",
+                                            orq->reserve_expiration),
+                TALER_JSON_pack_amount ("open_fee",
+                                        &orq->open_fee))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+
+    case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+      {
+        const struct TALER_EXCHANGEDB_CloseRequest *crq =
+          pos->details.close_request;
+
+        if (0 !=
+            json_array_append_new (
+              json_history,
+              GNUNET_JSON_PACK (
+                GNUNET_JSON_pack_string ("type",
+                                         "CLOSE"),
+                GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                            &crq->reserve_sig),
+                GNUNET_is_zero (&crq->target_account_h_payto)
+                ? GNUNET_JSON_pack_allow_null (
+                  GNUNET_JSON_pack_string ("h_payto",
+                                           NULL))
+                : GNUNET_JSON_pack_data_auto ("h_payto",
+                                              &crq->target_account_h_payto),
+                GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                            crq->request_timestamp))))
+        {
+          GNUNET_break (0);
+          json_decref (json_history);
+          return NULL;
+        }
+      }
+      break;
+    }
+  }
+
+  return json_history;
+}
+
+
+/**
+ * Send reserve history information to client with the
+ * message that we have insufficient funds for the
+ * requested withdraw operation.
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param ebalance expected balance based on our database
+ * @param withdraw_amount amount that the client requested to withdraw
+ * @param rh reserve history to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_insufficient_funds (
+  struct MHD_Connection *connection,
+  enum TALER_ErrorCode ec,
+  const struct TALER_Amount *ebalance,
+  const struct TALER_Amount *withdraw_amount,
+  const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+  json_t *json_history;
+
+  json_history = TEH_RESPONSE_compile_reserve_history (rh);
+  if (NULL == json_history)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to compile reserve history\n");
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_EXCHANGE_RESERVE_HISTORY_ERROR_INSUFFICIENT_FUNDS,
+                                       NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_CONFLICT,
+    TALER_JSON_pack_ec (ec),
+    TALER_JSON_pack_amount ("balance",
+                            ebalance),
+    TALER_JSON_pack_amount ("requested_amount",
+                            withdraw_amount),
+    GNUNET_JSON_pack_array_steal ("history",
+                                  json_history));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_insufficient_balance (
+  struct MHD_Connection *connection,
+  enum TALER_ErrorCode ec,
+  const struct TALER_Amount *balance_required,
+  const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
+  struct TALER_Amount balance;
+  enum GNUNET_DB_QueryStatus qs;
+  MHD_RESULT mhd_ret;
+
+  if (GNUNET_OK !=
+      TEH_plugin->start_read_only (TEH_plugin->cls,
+                                   "get_reserve_history on insufficient 
balance"))
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_START_FAILED,
+                                       NULL);
+  }
+  /* The reserve does not have the required amount (actual
+   * amount + withdraw fee) */
+  qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
+                                        reserve_pub,
+                                        &balance,
+                                        &rh);
+  TEH_plugin->rollback (TEH_plugin->cls);
+  if ( (qs < 0) ||
+       (NULL == rh) )
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                       "reserve history");
+  }
+  mhd_ret = reply_reserve_insufficient_funds (
+    connection,
+    ec,
+    &balance,
+    balance_required,
+    rh);
+  TEH_plugin->free_reserve_history (TEH_plugin->cls,
+                                    rh);
+  return mhd_ret;
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+  struct MHD_Connection *connection,
+  uint16_t maximum_allowed_age)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_CONFLICT,
+    TALER_JSON_pack_ec (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED),
+    GNUNET_JSON_pack_uint64 ("maximum_allowed_age",
+                             maximum_allowed_age));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_purse_created (
+  struct MHD_Connection *connection,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  const struct TALER_Amount *purse_balance,
+  const struct TEH_PurseDetails *pd)
+{
+  struct TALER_ExchangePublicKeyP pub;
+  struct TALER_ExchangeSignatureP sig;
+  enum TALER_ErrorCode ec;
+
+  if (TALER_EC_NONE !=
+      (ec = TALER_exchange_online_purse_created_sign (
+         &TEH_keys_exchange_sign_,
+         exchange_timestamp,
+         pd->purse_expiration,
+         &pd->target_amount,
+         purse_balance,
+         &pd->purse_pub,
+         &pd->h_contract_terms,
+         &pub,
+         &sig)))
+  {
+    GNUNET_break (0);
+    return TALER_MHD_reply_with_ec (connection,
+                                    ec,
+                                    NULL);
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_amount ("total_deposited",
+                            purse_balance),
+    GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+                                exchange_timestamp),
+    GNUNET_JSON_pack_data_auto ("exchange_sig",
+                                &sig),
+    GNUNET_JSON_pack_data_auto ("exchange_pub",
+                                &pub));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
+                                 const struct TALER_PaytoHashP *h_payto,
+                                 const struct TALER_EXCHANGEDB_KycStatus *kyc)
+{
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+    TALER_JSON_pack_ec (TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED),
+    GNUNET_JSON_pack_data_auto ("h_payto",
+                                h_payto),
+    GNUNET_JSON_pack_uint64 ("requirement_row",
+                             kyc->requirement_row));
+}
+
+
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+                                enum TALER_AmlDecisionState status)
+{
+  enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+
+  switch (status)
+  {
+  case TALER_AML_NORMAL:
+    GNUNET_break (0);
+    return MHD_NO;
+  case TALER_AML_PENDING:
+    ec = TALER_EC_EXCHANGE_GENERIC_AML_PENDING;
+    break;
+  case TALER_AML_FROZEN:
+    ec = TALER_EC_EXCHANGE_GENERIC_AML_FROZEN;
+    break;
+  }
+  return TALER_MHD_REPLY_JSON_PACK (
+    connection,
+    MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+    TALER_JSON_pack_ec (ec));
+}
+
+
+/* end of taler-exchange-httpd_responses.c */
diff --git a/src/exchange/taler-exchange-httpd_responses.h 
b/src/exchange/taler-exchange-httpd_responses.h
new file mode 100644
index 0000000..a57fa49
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_responses.h
@@ -0,0 +1,229 @@
+/*
+  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_responses.h
+ * @brief API for generating generic replies of the exchange; these
+ *        functions are called TEH_RESPONSE_reply_ and they generate
+ *        and queue MHD response objects for a given connection.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_RESPONSES_H
+#define TALER_EXCHANGE_HTTPD_RESPONSES_H
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_error_codes.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_db.h"
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * Compile the history of a reserve into a JSON object.
+ *
+ * @param rh reserve history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+json_t *
+TEH_RESPONSE_compile_reserve_history (
+  const struct TALER_EXCHANGEDB_ReserveHistory *rh);
+
+
+/**
+ * Send assertion that the given denomination key hash
+ * is unknown to us at this time.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_unknown_denom_pub_hash (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph);
+
+
+/**
+ * Return error message indicating that a reserve had
+ * an insufficient balance for the given operation.
+ *
+ * @param connection connection to the client
+ * @param ec specific error code to return with the reserve history
+ * @param balance_required the balance required for the operation
+ * @param reserve_pub the reserve with insufficient balance
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_insufficient_balance (
+  struct MHD_Connection *connection,
+  enum TALER_ErrorCode ec,
+  const struct TALER_Amount *balance_required,
+  const struct TALER_ReservePublicKeyP *reserve_pub);
+
+/**
+ * Return error message indicating that a reserve requires age
+ * restriction to be set during withdraw, that is: the age-withdraw
+ * protocol MUST be used with commitment to an admissible age.
+ *
+ * @param connection connection to the client
+ * @param maximum_allowed_age the balance required for the operation
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_reserve_age_restriction_required (
+  struct MHD_Connection *connection,
+  uint16_t maximum_allowed_age);
+
+
+/**
+ * Send information that a KYC check must be
+ * satisfied to proceed to client.
+ *
+ * @param connection connection to the client
+ * @param h_payto account identifier to include in reply
+ * @param kyc details about the KYC requirements
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_kyc_required (struct MHD_Connection *connection,
+                                 const struct TALER_PaytoHashP *h_payto,
+                                 const struct TALER_EXCHANGEDB_KycStatus *kyc);
+
+
+/**
+ * Send information that an AML process is blocking
+ * the operation right now.
+ *
+ * @param connection connection to the client
+ * @param status current AML status
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_aml_blocked (struct MHD_Connection *connection,
+                                enum TALER_AmlDecisionState status);
+
+
+/**
+ * Send assertion that the given denomination key hash
+ * is not usable (typically expired) at this time.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @param ec error code to use
+ * @param oper name of the operation that is not allowed at this time
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_expired_denom_pub_hash (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph,
+  enum TALER_ErrorCode ec,
+  const char *oper);
+
+
+/**
+ * Send assertion that the given denomination cannot be used for this 
operation.
+ *
+ * @param connection connection to the client
+ * @param dph denomination public key hash
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+  struct MHD_Connection *connection,
+  const struct TALER_DenominationHashP *dph);
+
+
+/**
+ * Send proof that a request is invalid to client because of
+ * insufficient funds.  This function will create a message with all
+ * of the operations affecting the coin that demonstrate that the coin
+ * has insufficient value.
+ *
+ * @param connection connection to the client
+ * @param ec error code to return
+ * @param h_denom_pub hash of the denomination of the coin
+ * @param coin_pub public key of the coin
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_coin_insufficient_funds (
+  struct MHD_Connection *connection,
+  enum TALER_ErrorCode ec,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+/**
+ * Fundamental details about a purse.
+ */
+struct TEH_PurseDetails
+{
+  /**
+   * When should the purse expire.
+   */
+  struct GNUNET_TIME_Timestamp purse_expiration;
+
+  /**
+   * Hash of the contract terms of the purse.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Public key of the purse we are creating.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Total amount to be put into the purse.
+   */
+  struct TALER_Amount target_amount;
+};
+
+
+/**
+ * Send confirmation that a purse was created with
+ * the current purse balance.
+ *
+ * @param connection connection to the client
+ * @param pd purse details
+ * @param exchange_timestamp our time for purse creation
+ * @param purse_balance current balance in the purse
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_RESPONSE_reply_purse_created (
+  struct MHD_Connection *connection,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  const struct TALER_Amount *purse_balance,
+  const struct TEH_PurseDetails *pd);
+
+
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param coin_pub public key of the coin
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+json_t *
+TEH_RESPONSE_compile_transaction_history (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_EXCHANGEDB_TransactionList *tl);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_terms.c 
b/src/exchange/taler-exchange-httpd_terms.c
new file mode 100644
index 0000000..10114f1
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_terms.c
@@ -0,0 +1,81 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2019, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_terms.c
+ * @brief Handle /terms requests to return the terms of service
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+/**
+ * Our terms of service.
+ */
+static struct TALER_MHD_Legal *tos;
+
+
+/**
+ * Our privacy policy.
+ */
+static struct TALER_MHD_Legal *pp;
+
+
+MHD_RESULT
+TEH_handler_terms (struct TEH_RequestContext *rc,
+                   const char *const args[])
+{
+  (void) args;
+  return TALER_MHD_reply_legal (rc->connection,
+                                tos);
+}
+
+
+MHD_RESULT
+TEH_handler_privacy (struct TEH_RequestContext *rc,
+                     const char *const args[])
+{
+  (void) args;
+  return TALER_MHD_reply_legal (rc->connection,
+                                pp);
+}
+
+
+void
+TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  tos = TALER_MHD_legal_load (cfg,
+                              "exchange",
+                              "TERMS_DIR",
+                              "TERMS_ETAG");
+  if (NULL == tos)
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Terms of service not configured\n");
+  pp = TALER_MHD_legal_load (cfg,
+                             "exchange",
+                             "PRIVACY_DIR",
+                             "PRIVACY_ETAG");
+  if (NULL == pp)
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Privacy policy not configured\n");
+}
+
+
+/* end of taler-exchange-httpd_terms.c */
diff --git a/src/exchange/taler-exchange-httpd_terms.h 
b/src/exchange/taler-exchange-httpd_terms.h
new file mode 100644
index 0000000..9815080
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_terms.h
@@ -0,0 +1,65 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2019, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_terms.h
+ * @brief Handle /terms requests to return the terms of service
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_TERMS_H
+#define TALER_EXCHANGE_HTTPD_TERMS_H
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Handle a "/terms" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_terms (struct TEH_RequestContext *rc,
+                   const char *const args[]);
+
+
+/**
+ * Handle a "/privacy" request.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_privacy (struct TEH_RequestContext *rc,
+                     const char *const args[]);
+
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c 
b/src/exchange/taler-exchange-httpd_withdraw.c
new file mode 100644
index 0000000..07fcc84
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -0,0 +1,700 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as
+  published by the Free Software Foundation; either version 3,
+  or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty
+  of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General
+  Public License along with TALER; see the file COPYING.  If not,
+  see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_withdraw.c
+ * @brief Handle /reserves/$RESERVE_PUB/withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler_json_lib.h"
+#include "taler_kyclogic_lib.h"
+#include "taler_mhd_lib.h"
+#include "taler-exchange-httpd_withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_keys.h"
+
+
+/**
+ * Context for #withdraw_transaction.
+ */
+struct WithdrawContext
+{
+
+  /**
+   * Hash of the (blinded) message to be signed by the Exchange.
+   */
+  struct TALER_BlindedCoinHashP h_coin_envelope;
+
+  /**
+   * Blinded planchet.
+   */
+  struct TALER_BlindedPlanchet blinded_planchet;
+
+  /**
+   * Set to the resulting signed coin data to be returned to the client.
+   */
+  struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
+
+  /**
+   * KYC status for the operation.
+   */
+  struct TALER_EXCHANGEDB_KycStatus kyc;
+
+  /**
+   * Hash of the payto-URI representing the account
+   * from which the money was put into the reserve.
+   */
+  struct TALER_PaytoHashP h_account_payto;
+
+  /**
+   * Current time for the DB transaction.
+   */
+  struct GNUNET_TIME_Timestamp now;
+
+  /**
+   * AML decision, #TALER_AML_NORMAL if we may proceed.
+   */
+  enum TALER_AmlDecisionState aml_decision;
+
+};
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and
+ *        account to iterate over events for
+ * @param limit maximum time-range for which events
+ *        should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ *        events must be returned in reverse chronological
+ *        order
+ * @param cb_cls closure for @a cb
+ */
+static void
+withdraw_amount_cb (void *cls,
+                    struct GNUNET_TIME_Absolute limit,
+                    TALER_EXCHANGEDB_KycAmountCallback cb,
+                    void *cb_cls)
+{
+  struct WithdrawContext *wc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Signaling amount %s for KYC check\n",
+              TALER_amount2s (&wc->collectable.amount_with_fee));
+  if (GNUNET_OK !=
+      cb (cb_cls,
+          &wc->collectable.amount_with_fee,
+          wc->now.abs_time))
+    return;
+  qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
+    TEH_plugin->cls,
+    &wc->h_account_payto,
+    limit,
+    cb,
+    cb_cls);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Got %d additional transactions for this withdrawal and limit 
%llu\n",
+              qs,
+              (unsigned long long) limit.abs_value_us);
+  GNUNET_break (qs >= 0);
+}
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for the AML check as it was merged into
+ * the reserve.
+ *
+ * @param cls `struct TALER_Amount *` to total up the amounts
+ * @param amount encountered transaction amount
+ * @param date when was the amount encountered
+ * @return #GNUNET_OK to continue to iterate,
+ *         #GNUNET_NO to abort iteration
+ *         #GNUNET_SYSERR on internal error (also abort itaration)
+ */
+static enum GNUNET_GenericReturnValue
+aml_amount_cb (
+  void *cls,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Absolute date)
+{
+  struct TALER_Amount *total = cls;
+
+  GNUNET_assert (0 <=
+                 TALER_amount_add (total,
+                                   total,
+                                   amount));
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function implementing withdraw transaction.  Runs the
+ * transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response.  IF it returns an hard error,
+ * the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again
+ * to retry and MUST not queue a MHD response.
+ *
+ * Note that "wc->collectable.sig" is set before entering this function as we
+ * signed before entering the transaction.
+ *
+ * @param cls a `struct WithdrawContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ *             if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+withdraw_transaction (void *cls,
+                      struct MHD_Connection *connection,
+                      MHD_RESULT *mhd_ret)
+{
+  struct WithdrawContext *wc = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  bool found = false;
+  bool balance_ok = false;
+  bool nonce_ok = false;
+  bool age_ok = false;
+  uint16_t allowed_maximum_age = 0;
+  uint64_t ruuid;
+  const struct TALER_CsNonce *nonce;
+  const struct TALER_BlindedPlanchet *bp;
+  struct TALER_PaytoHashP reserve_h_payto;
+
+  wc->now = GNUNET_TIME_timestamp_get ();
+  /* Do AML check: compute total merged amount and check
+     against applicable AML threshold */
+  {
+    char *reserve_payto;
+
+    reserve_payto = TALER_reserve_make_payto (TEH_base_url,
+                                              &wc->collectable.reserve_pub);
+    TALER_payto_hash (reserve_payto,
+                      &reserve_h_payto);
+    GNUNET_free (reserve_payto);
+  }
+  {
+    struct TALER_Amount merge_amount;
+    struct TALER_Amount threshold;
+    struct GNUNET_TIME_Absolute now_minus_one_month;
+
+    now_minus_one_month
+      = GNUNET_TIME_absolute_subtract (wc->now.abs_time,
+                                       GNUNET_TIME_UNIT_MONTHS);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_amount_set_zero (TEH_currency,
+                                          &merge_amount));
+    qs = TEH_plugin->select_merge_amounts_for_kyc_check (TEH_plugin->cls,
+                                                         &reserve_h_payto,
+                                                         now_minus_one_month,
+                                                         &aml_amount_cb,
+                                                         &merge_amount);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               
"select_merge_amounts_for_kyc_check");
+      return qs;
+    }
+    qs = TEH_plugin->select_aml_threshold (TEH_plugin->cls,
+                                           &reserve_h_payto,
+                                           &wc->aml_decision,
+                                           &wc->kyc,
+                                           &threshold);
+    if (qs < 0)
+    {
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "select_aml_threshold");
+      return qs;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      threshold = TEH_aml_threshold; /* use default */
+      wc->aml_decision = TALER_AML_NORMAL;
+    }
+
+    switch (wc->aml_decision)
+    {
+    case TALER_AML_NORMAL:
+      if (0 >= TALER_amount_cmp (&merge_amount,
+                                 &threshold))
+      {
+        /* merge_amount <= threshold, continue withdraw below */
+        break;
+      }
+      wc->aml_decision = TALER_AML_PENDING;
+      qs = TEH_plugin->trigger_aml_process (TEH_plugin->cls,
+                                            &reserve_h_payto,
+                                            &merge_amount);
+      if (qs <= 0)
+      {
+        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+        if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+          *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                                 
MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                                 
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                                 "trigger_aml_process");
+        return qs;
+      }
+      return qs;
+    case TALER_AML_PENDING:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "AML already pending, doing nothing\n");
+      return qs;
+    case TALER_AML_FROZEN:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Account frozen, doing nothing\n");
+      return qs;
+    }
+  }
+
+  /* Check if the money came from a wire transfer */
+  qs = TEH_plugin->reserves_get_origin (TEH_plugin->cls,
+                                        &wc->collectable.reserve_pub,
+                                        &wc->h_account_payto);
+  if (qs < 0)
+    return qs;
+  /* If no results, reserve was created by merge, in which case no KYC check
+     is required as the merge already did that. */
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+  {
+    char *kyc_required;
+
+    qs = TALER_KYCLOGIC_kyc_test_required (
+      TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
+      &wc->h_account_payto,
+      TEH_plugin->select_satisfied_kyc_processes,
+      TEH_plugin->cls,
+      &withdraw_amount_cb,
+      wc,
+      &kyc_required);
+    if (qs < 0)
+    {
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      {
+        GNUNET_break (0);
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                               "kyc_test_required");
+      }
+      return qs;
+    }
+    if (NULL != kyc_required)
+    {
+      /* insert KYC requirement into DB! */
+      wc->kyc.ok = false;
+      qs = TEH_plugin->insert_kyc_requirement_for_account (
+        TEH_plugin->cls,
+        kyc_required,
+        &wc->h_account_payto,
+        &wc->collectable.reserve_pub,
+        &wc->kyc.requirement_row);
+      GNUNET_free (kyc_required);
+      if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      {
+        GNUNET_break (0);
+        *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                               
TALER_EC_GENERIC_DB_STORE_FAILED,
+                                               
"insert_kyc_requirement_for_account");
+      }
+      return qs;
+    }
+  }
+  wc->kyc.ok = true;
+  bp = &wc->blinded_planchet;
+  nonce = (TALER_DENOMINATION_CS == bp->cipher)
+    ? &bp->details.cs_blinded_planchet.nonce
+    : NULL;
+  qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
+                                nonce,
+                                &wc->collectable,
+                                wc->now,
+                                TEH_age_restriction_enabled,
+                                &found,
+                                &balance_ok,
+                                &nonce_ok,
+                                &age_ok,
+                                &allowed_maximum_age,
+                                &ruuid);
+  if (0 > qs)
+  {
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+    {
+      GNUNET_break (0);
+      *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                             "do_withdraw");
+    }
+    return qs;
+  }
+  if (! found)
+  {
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           
TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+                                           NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (! age_ok)
+  {
+    /* We respond with the lowest age in the corresponding age group
+     * of the required age */
+    uint16_t lowest_age = TALER_get_lowest_age (
+      &TEH_age_restriction_config.mask,
+      allowed_maximum_age);
+
+    TEH_plugin->rollback (TEH_plugin->cls);
+    *mhd_ret = TEH_RESPONSE_reply_reserve_age_restriction_required (
+      connection,
+      lowest_age);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (! balance_ok)
+  {
+    TEH_plugin->rollback (TEH_plugin->cls);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Balance insufficient for /withdraw\n");
+    *mhd_ret = TEH_RESPONSE_reply_reserve_insufficient_balance (
+      connection,
+      TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
+      &wc->collectable.amount_with_fee,
+      &wc->collectable.reserve_pub);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (! nonce_ok)
+  {
+    TEH_plugin->rollback (TEH_plugin->cls);
+    *mhd_ret = TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_CONFLICT,
+                                           
TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
+                                           NULL);
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+    TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
+  return qs;
+}
+
+
+/**
+ * Check if the @a rc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param rc request context
+ * @param[in,out] wc parsed request data
+ * @param[out] mret HTTP status, set if we return true
+ * @return true if the request is idempotent with an existing request
+ *    false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+check_request_idempotent (struct TEH_RequestContext *rc,
+                          struct WithdrawContext *wc,
+                          MHD_RESULT *mret)
+{
+  enum GNUNET_DB_QueryStatus qs;
+
+  qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
+                                      &wc->h_coin_envelope,
+                                      &wc->collectable);
+  if (0 > qs)
+  {
+    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+      *mret = TALER_MHD_reply_with_error (rc->connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                          "get_withdraw_info");
+    return true; /* well, kind-of */
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    return false;
+  /* generate idempotent reply */
+  TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
+  *mret = TALER_MHD_REPLY_JSON_PACK (
+    rc->connection,
+    MHD_HTTP_OK,
+    TALER_JSON_pack_blinded_denom_sig ("ev_sig",
+                                       &wc->collectable.sig));
+  TALER_blinded_denom_sig_free (&wc->collectable.sig);
+  return true;
+}
+
+
+MHD_RESULT
+TEH_handler_withdraw (struct TEH_RequestContext *rc,
+                      const struct TALER_ReservePublicKeyP *reserve_pub,
+                      const json_t *root)
+{
+  struct WithdrawContext wc;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &wc.collectable.reserve_sig),
+    GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+                                 &wc.collectable.denom_pub_hash),
+    TALER_JSON_spec_blinded_planchet ("coin_ev",
+                                      &wc.blinded_planchet),
+    GNUNET_JSON_spec_end ()
+  };
+  enum TALER_ErrorCode ec;
+  struct TEH_DenominationKey *dk;
+
+  memset (&wc,
+          0,
+          sizeof (wc));
+  wc.collectable.reserve_pub = *reserve_pub;
+  {
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (rc->connection,
+                                     root,
+                                     spec);
+    if (GNUNET_OK != res)
+      return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
+  }
+  {
+    MHD_RESULT mret;
+    struct TEH_KeyStateHandle *ksh;
+
+    ksh = TEH_keys_get_state ();
+    if (NULL == ksh)
+    {
+      if (! check_request_idempotent (rc,
+                                      &wc,
+                                      &mret))
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TALER_MHD_reply_with_error (rc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+                                           NULL);
+      }
+      GNUNET_JSON_parse_free (spec);
+      return mret;
+    }
+
+    dk = TEH_keys_denomination_by_hash_from_state (
+      ksh,
+      &wc.collectable.denom_pub_hash,
+      NULL,
+      NULL);
+
+    if (NULL == dk)
+    {
+      if (! check_request_idempotent (rc,
+                                      &wc,
+                                      &mret))
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+          rc->connection,
+          &wc.collectable.denom_pub_hash);
+      }
+      GNUNET_JSON_parse_free (spec);
+      return mret;
+    }
+    if (GNUNET_TIME_absolute_is_past (dk->meta.expire_withdraw.abs_time))
+    {
+      /* This denomination is past the expiration time for withdraws */
+      if (! check_request_idempotent (rc,
+                                      &wc,
+                                      &mret))
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TEH_RESPONSE_reply_expired_denom_pub_hash (
+          rc->connection,
+          &wc.collectable.denom_pub_hash,
+          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+          "WITHDRAW");
+      }
+      GNUNET_JSON_parse_free (spec);
+      return mret;
+    }
+    if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+    {
+      /* This denomination is not yet valid, no need to check
+         for idempotency! */
+      GNUNET_JSON_parse_free (spec);
+      return TEH_RESPONSE_reply_expired_denom_pub_hash (
+        rc->connection,
+        &wc.collectable.denom_pub_hash,
+        TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+        "WITHDRAW");
+    }
+    if (dk->recoup_possible)
+    {
+      /* This denomination has been revoked */
+      if (! check_request_idempotent (rc,
+                                      &wc,
+                                      &mret))
+      {
+        GNUNET_JSON_parse_free (spec);
+        return TEH_RESPONSE_reply_expired_denom_pub_hash (
+          rc->connection,
+          &wc.collectable.denom_pub_hash,
+          TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+          "WITHDRAW");
+      }
+      GNUNET_JSON_parse_free (spec);
+      return mret;
+    }
+    if (dk->denom_pub.cipher != wc.blinded_planchet.cipher)
+    {
+      /* denomination cipher and blinded planchet cipher not the same */
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (rc->connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         
TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+                                         NULL);
+    }
+  }
+
+  if (0 >
+      TALER_amount_add (&wc.collectable.amount_with_fee,
+                        &dk->meta.value,
+                        &dk->meta.fees.withdraw))
+  {
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+                                       NULL);
+  }
+
+  if (GNUNET_OK !=
+      TALER_coin_ev_hash (&wc.blinded_planchet,
+                          &wc.collectable.denom_pub_hash,
+                          &wc.collectable.h_coin_envelope))
+  {
+    GNUNET_break (0);
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                       
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+                                       NULL);
+  }
+
+  TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+  if (GNUNET_OK !=
+      TALER_wallet_withdraw_verify (&wc.collectable.denom_pub_hash,
+                                    &wc.collectable.amount_with_fee,
+                                    &wc.collectable.h_coin_envelope,
+                                    &wc.collectable.reserve_pub,
+                                    &wc.collectable.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_error (rc->connection,
+                                       MHD_HTTP_FORBIDDEN,
+                                       
TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+                                       NULL);
+  }
+
+  {
+    struct TEH_CoinSignData csd = {
+      .h_denom_pub = &wc.collectable.denom_pub_hash,
+      .bp = &wc.blinded_planchet
+    };
+
+    /* Sign before transaction! */
+    ec = TEH_keys_denomination_sign (
+      &csd,
+      false,
+      &wc.collectable.sig);
+  }
+  if (TALER_EC_NONE != ec)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to sign coin: %d\n",
+                ec);
+    GNUNET_JSON_parse_free (spec);
+    return TALER_MHD_reply_with_ec (rc->connection,
+                                    ec,
+                                    NULL);
+  }
+
+  /* run transaction */
+  {
+    MHD_RESULT mhd_ret;
+
+    if (GNUNET_OK !=
+        TEH_DB_run_transaction (rc->connection,
+                                "run withdraw",
+                                TEH_MT_REQUEST_WITHDRAW,
+                                &mhd_ret,
+                                &withdraw_transaction,
+                                &wc))
+    {
+      /* Even if #withdraw_transaction() failed, it may have created a 
signature
+         (or we might have done it optimistically above). */
+      TALER_blinded_denom_sig_free (&wc.collectable.sig);
+      GNUNET_JSON_parse_free (spec);
+      return mhd_ret;
+    }
+  }
+
+  /* Clean up and send back final response */
+  GNUNET_JSON_parse_free (spec);
+
+  if (! wc.kyc.ok)
+    return TEH_RESPONSE_reply_kyc_required (rc->connection,
+                                            &wc.h_account_payto,
+                                            &wc.kyc);
+
+  if (TALER_AML_NORMAL != wc.aml_decision)
+    return TEH_RESPONSE_reply_aml_blocked (rc->connection,
+                                           wc.aml_decision);
+
+  {
+    MHD_RESULT ret;
+
+    ret = TALER_MHD_REPLY_JSON_PACK (
+      rc->connection,
+      MHD_HTTP_OK,
+      TALER_JSON_pack_blinded_denom_sig ("ev_sig",
+                                         &wc.collectable.sig));
+    TALER_blinded_denom_sig_free (&wc.collectable.sig);
+    return ret;
+  }
+}
+
+
+/* end of taler-exchange-httpd_withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_withdraw.h 
b/src/exchange/taler-exchange-httpd_withdraw.h
new file mode 100644
index 0000000..2ec76bb
--- /dev/null
+++ b/src/exchange/taler-exchange-httpd_withdraw.h
@@ -0,0 +1,47 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU Affero General Public License as published by the Free 
Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+  You should have received a copy of the GNU Affero General Public License 
along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-exchange-httpd_withdraw.h
+ * @brief Handle /reserve/withdraw requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the requested 
"denom_pub" which
+ * specifies the key/value of the coin to be withdrawn, and checks that the
+ * signature "reserve_sig" makes this a valid withdrawal request from the
+ * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
+ * passed down to execute the withdrawal operation.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param reserve_pub public key of the reserve
+ * @return MHD result code
+  */
+MHD_RESULT
+TEH_handler_withdraw (struct TEH_RequestContext *rc,
+                      const struct TALER_ReservePublicKeyP *reserve_pub,
+                      const json_t *root);
+
+#endif
diff --git a/src/exchange/test_taler_exchange_httpd.conf 
b/src/exchange/test_taler_exchange_httpd.conf
new file mode 100644
index 0000000..80cf623
--- /dev/null
+++ b/src/exchange/test_taler_exchange_httpd.conf
@@ -0,0 +1,142 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_taler_exchange_httpd_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[auditor]
+TINY_AMOUNT = EUR:0.01
+
+[exchange]
+
+AML_THRESHOLD = EUR:1000000
+
+# Directory with our terms of service.
+TERMS_DIR = ../../contrib/tos
+
+# Etag / filename for the terms of service.
+TERMS_ETAG = 0
+
+SIGNKEY_LEGAL_DURATION = 2 years
+
+# Directory with our privacy policy.
+PRIVACY_DIR = ../../contrib/pp
+
+# Etag / filename for the privacy policy.
+PRIVACY_ETAG = 0
+
+# MAX_REQUESTS = 2
+# how long is one signkey valid?
+SIGNKEY_DURATION = 4 weeks
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+LOOKAHEAD_SIGN = 32 weeks 1 day
+
+# HTTP port the exchange listens to
+PORT = 8081
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+
+[exchangedb]
+# After how long do we close idle reserves?  The exchange
+# and the auditor must agree on this value.  We currently
+# expect it to be globally defined for the whole system,
+# as there is no way for wallets to query this value.  Thus,
+# it is only configurable for testing, and should be treated
+# as constant in production.
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/3"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/3/";
+
+# Coins for the tests.
+[coin_eur_ct_1_rsa]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_1_cs]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10_rsa]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_10_cs]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1_rsa]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_1_cs]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/exchange/test_taler_exchange_httpd.sh 
b/src/exchange/test_taler_exchange_httpd.sh
new file mode 100755
index 0000000..0fe71f3
--- /dev/null
+++ b/src/exchange/test_taler_exchange_httpd.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# Copyright (C) 2015-2020, 2023 Taler Systems SA
+#
+#  TALER is free software; you can redistribute it and/or modify it under the
+#  terms of the GNU Affero General Public License as published by the Free 
Software
+#  Foundation; either version 3, or (at your option) any later version.
+#
+#  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+#  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+#  A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+#
+#  You should have received a copy of the GNU Affero General Public License 
along with
+#  TALER; see the file COPYING.  If not, If not, see 
<http://www.gnu.org/licenses/>
+#
+#
+# This script uses 'curl' to POST various ill-formed requests to the
+# taler-exchange-httpd.  Basically, the goal is to make sure that the
+# HTTP server survives (and produces the 'correct' error code).
+#
+#
+# Clear environment from variables that override config.
+unset XDG_DATA_HOME
+unset XDG_CONFIG_HOME
+
+set -eu
+#
+echo -n "Launching exchange ..."
+PREFIX=
+# Uncomment this line to run with valgrind...
+#PREFIX="valgrind --leak-check=yes --track-fds=yes --error-exitcode=1 
--log-file=valgrind.%p"
+
+# Setup database
+taler-exchange-dbinit -c test_taler_exchange_httpd.conf &> /dev/null || exit 77
+# Run Exchange HTTPD (in background)
+$PREFIX taler-exchange-httpd -c test_taler_exchange_httpd.conf 2> 
test-exchange.log &
+
+# Give HTTP time to start
+
+for n in `seq 1 100`
+do
+    echo -n "."
+    sleep 0.1
+    OK=1
+    wget http://localhost:8081/seed -o /dev/null -O /dev/null >/dev/null && 
break
+    OK=0
+done
+if [ 1 != $OK ]
+then
+    echo "Failed to launch exchange"
+    kill -TERM $!
+    wait $!
+    echo Process status: $?
+    exit 77
+fi
+echo " DONE"
+
+# Finally run test...
+echo -n "Running tests ..."
+# We read the JSON snippets to POST from test_taler_exchange_httpd.post
+cat test_taler_exchange_httpd.post | grep -v ^\# | awk '{ print "curl -d \47"  
$2 "\47 http://localhost:8081"; $1 }' | bash &> /dev/null
+echo -n .
+# We read the JSON snippets to GET from test_taler_exchange_httpd.get
+cat test_taler_exchange_httpd.get | grep -v ^\# | awk '{ print "curl 
http://localhost:8081"; $1 }' | bash &> /dev/null
+echo -n .
+# Also try them with various headers: Language
+cat test_taler_exchange_httpd.get | grep -v ^\# | awk '{ print "curl -H 
\"Accept-Language: fr,en;q=0.4,de\" http://localhost:8081"; $1 }' | bash &> 
/dev/null
+echo -n .
+# Also try them with various headers: Accept encoding (wildcard #1)
+cat test_taler_exchange_httpd.get | grep -v ^\# | awk '{ print "curl -H 
\"Accept: text/*\" http://localhost:8081"; $1 }' | bash &> /dev/null
+echo -n .
+# Also try them with various headers: Accept encoding (wildcard #2)
+cat test_taler_exchange_httpd.get | grep -v ^\# | awk '{ print "curl -H 
\"Accept: */plain\" http://localhost:8081"; $1 }' | bash &> /dev/null
+
+echo " DONE"
+# $! is the last backgrounded process, hence the exchange
+kill -TERM $!
+wait $!
+# Return status code from exchange for this script
+exit $?
diff --git a/src/exchange/test_taler_exchange_unix.conf 
b/src/exchange/test_taler_exchange_unix.conf
new file mode 100644
index 0000000..e96bfba
--- /dev/null
+++ b/src/exchange/test_taler_exchange_unix.conf
@@ -0,0 +1,140 @@
+[PATHS]
+# Persistent data storage for the testcase
+TALER_TEST_HOME = test_taler_exchange_httpd_home/
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/
+
+[taler]
+# Currency supported by the exchange (can only be one)
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[exchange]
+
+# Directory with our terms of service.
+TERMS_DIR = ../../contrib/tos
+
+# Etag / filename for the terms of service.
+TERMS_ETAG = 0
+
+# how long are the signatures with the signkey valid?
+SIGNKEY_LEGAL_DURATION = 2 years
+
+# Directory with our privacy policy.
+PRIVACY_DIR = ../../contrib/pp
+
+# Etag / filename for the privacy policy.
+PRIVACY_ETAG = 0
+
+# MAX_REQUESTS = 2
+# how long is one signkey valid?
+SIGNKEY_DURATION = 4 weeks
+
+# how long do we generate denomination and signing keys
+# ahead of time?
+LOOKAHEAD_SIGN = 32 weeks 1 day
+
+# HTTP port the exchange listens to (we want to use UNIX domain sockets,
+# so we use a port that just won't work on GNU/Linux without root rights)
+PORT = 999
+
+# Here we say we want to use a UNIX domain socket (to test that logic).
+SERVE = unix
+
+# Master public key used to sign the exchange's various keys
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+
+# How to access our database
+DB = postgres
+
+
+[exchangedb]
+# After how long do we close idle reserves?  The exchange
+# and the auditor must agree on this value.  We currently
+# expect it to be globally defined for the whole system,
+# as there is no way for wallets to query this value.  Thus,
+# it is only configurable for testing, and should be treated
+# as constant in production.
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost:8082/3"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+TALER_BANK_AUTH_METHOD = NONE
+
+
+# Coins for the tests.
+[coin_eur_ct_1_rsa]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_1_cs]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10_rsa]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_10_cs]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1_rsa]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_1_cs]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore
new file mode 100644
index 0000000..6e67fad
--- /dev/null
+++ b/src/exchangedb/.gitignore
@@ -0,0 +1,16 @@
+test-exchangedb-postgres
+bench-db-postgres
+perf_deposits_get_ready-postgres
+perf_get_link_data-postgres
+perf_reserves_in_insert-postgres
+perf_select_refunds_by_coin-postgres
+exchange-0002.sql
+procedures.sql
+exchange-0003.sql
+perf-exchangedb-reserves-in-insert-postgres
+test-exchangedb-batch-reserves-in-insert-postgres
+test-exchangedb-by-j-postgres
+test-exchangedb-populate-link-data-postgres
+test-exchangedb-populate-ready-deposit-postgres
+test-exchangedb-populate-select-refunds-by-coin-postgres
+exchange-0004.sql
diff --git a/src/exchangedb/0002-account_merges.sql 
b/src/exchangedb/0002-account_merges.sql
new file mode 100644
index 0000000..1ea9e92
--- /dev/null
+++ b/src/exchangedb/0002-account_merges.sql
@@ -0,0 +1,133 @@
+--
+-- 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 FUNCTION create_table_account_merges(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'account_merges';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE IF NOT EXISTS %I '
+      '(account_merge_request_serial_id BIGINT GENERATED BY DEFAULT AS 
IDENTITY'
+      ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+      ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+      ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+      ',wallet_h_payto BYTEA NOT NULL CHECK (LENGTH(wallet_h_payto)=32)'
+      ',PRIMARY KEY (purse_pub)'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (purse_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'Merge requests where a purse- and account-owner requested merging the 
purse into the account'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'public key of the target reserve'
+    ,'reserve_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'public key of the purse'
+    ,'purse_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'signature by the reserve private key affirming the merge, of type 
TALER_SIGNATURE_WALLET_ACCOUNT_MERGE'
+    ,'reserve_sig'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_account_merges(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'account_merges';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+
+  -- FIXME: change to materialized index by reserve_pub!?
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_reserve_pub '
+    'ON ' || table_name || ' '
+    '(reserve_pub);'
+  );
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_account_merge_request_serial_id_key'
+    ' UNIQUE (account_merge_request_serial_id) '
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_account_merges()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'account_merges';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+    ' FOREIGN KEY (reserve_pub) '
+    ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE'
+    ',ADD CONSTRAINT ' || table_name || '_foreign_purse_pub'
+    ' FOREIGN KEY (purse_pub) '
+    ' REFERENCES purse_requests (purse_pub)'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('account_merges'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('account_merges'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('account_merges'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-batch_deposits.sql 
b/src/exchangedb/0002-batch_deposits.sql
new file mode 100644
index 0000000..af0764a
--- /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-coin_deposits.sql 
b/src/exchangedb/0002-coin_deposits.sql
new file mode 100644
index 0000000..c3eef6e
--- /dev/null
+++ b/src/exchangedb/0002-coin_deposits.sql
@@ -0,0 +1,157 @@
+--
+-- 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_coin_deposits(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(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)'
+      ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+      ',amount_with_fee taler_amount NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (coin_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+    'Coins which have been deposited with the respective per-coin signatures.'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Link to information about the batch deposit this coin was used for'
+    ,'batch_deposit_serial_id'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_coin_deposits(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    '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 (
+    'CREATE INDEX ' || table_name || '_by_batch '
+    'ON ' || table_name || ' '
+    '(batch_deposit_serial_id);'
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_coin_deposits()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  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_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
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('coin_deposits'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('coin_deposits'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('coin_deposits'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE),
+    ('coin_deposits'
+    ,'exchange-0002'
+    ,'master'
+    ,TRUE
+    ,FALSE)
+    ;
diff --git a/src/exchangedb/0002-cs_nonce_locks.sql 
b/src/exchangedb/0002-cs_nonce_locks.sql
new file mode 100644
index 0000000..36c0c7a
--- /dev/null
+++ b/src/exchangedb/0002-cs_nonce_locks.sql
@@ -0,0 +1,97 @@
+--
+-- 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 FUNCTION create_table_cs_nonce_locks(
+  partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(cs_nonce_lock_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)'
+      ',op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)'
+      ',max_denomination_serial INT8 NOT NULL'
+    ') %s ;'
+    ,'cs_nonce_locks'
+    ,'PARTITION BY HASH (nonce)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'ensures a Clause Schnorr client nonce is locked for use with an 
operation identified by a hash'
+    ,'cs_nonce_locks'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'actual nonce submitted by the client'
+    ,'nonce'
+    ,'cs_nonce_locks'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'hash (RC for refresh, blind coin hash for withdraw) the nonce may be 
used with'
+    ,'op_hash'
+    ,'cs_nonce_locks'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Maximum number of a CS denomination serial the nonce could be used with, 
for GC'
+    ,'max_denomination_serial'
+    ,'cs_nonce_locks'
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_cs_nonce_locks(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'cs_nonce_locks';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_cs_nonce_lock_serial_id_key'
+    ' UNIQUE (cs_nonce_lock_serial_id)'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('cs_nonce_locks'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('cs_nonce_locks'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-denomination_revocations.sql 
b/src/exchangedb/0002-denomination_revocations.sql
new file mode 100644
index 0000000..96e13cd
--- /dev/null
+++ b/src/exchangedb/0002-denomination_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- 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 TABLE IF NOT EXISTS denomination_revocations
+  (denom_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+  ,denominations_serial INT8 PRIMARY KEY REFERENCES denominations 
(denominations_serial) ON DELETE CASCADE
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  );
+COMMENT ON TABLE denomination_revocations
+  IS 'remembering which denomination keys have been revoked';
diff --git a/src/exchangedb/0002-denominations.sql 
b/src/exchangedb/0002-denominations.sql
new file mode 100644
index 0000000..a3de2b1
--- /dev/null
+++ b/src/exchangedb/0002-denominations.sql
@@ -0,0 +1,45 @@
+--
+-- 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 TABLE denominations
+  (denominations_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+  ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
+  ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default 
later!)
+  ,age_mask INT4 NOT NULL DEFAULT (0)
+  ,denom_pub BYTEA NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  ,valid_from INT8 NOT NULL
+  ,expire_withdraw INT8 NOT NULL
+  ,expire_deposit INT8 NOT NULL
+  ,expire_legal INT8 NOT NULL
+  ,coin taler_amount NOT NULL
+  ,fee_withdraw taler_amount NOT NULL
+  ,fee_deposit taler_amount NOT NULL
+  ,fee_refresh taler_amount NOT NULL
+  ,fee_refund taler_amount NOT NULL
+  );
+COMMENT ON TABLE denominations
+  IS 'Main denominations table. All the valid denominations the exchange knows 
about.';
+COMMENT ON COLUMN denominations.denom_type
+  IS 'determines cipher type for blind signatures used with this denomination; 
0 is for RSA';
+COMMENT ON COLUMN denominations.age_mask
+  IS 'bitmask with the age restrictions that are being used for this 
denomination; 0 if denomination does not support the use of age restrictions';
+COMMENT ON COLUMN denominations.denominations_serial
+  IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX denominations_by_expire_legal_index
+  ON denominations
+  (expire_legal);
diff --git a/src/exchangedb/0002-exchange_sign_keys.sql 
b/src/exchangedb/0002-exchange_sign_keys.sql
new file mode 100644
index 0000000..d6acc6b
--- /dev/null
+++ b/src/exchangedb/0002-exchange_sign_keys.sql
@@ -0,0 +1,36 @@
+--
+-- 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 TABLE exchange_sign_keys
+  (esk_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+  ,exchange_pub BYTEA PRIMARY KEY CHECK (LENGTH(exchange_pub)=32)
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  ,valid_from INT8 NOT NULL
+  ,expire_sign INT8 NOT NULL
+  ,expire_legal INT8 NOT NULL
+  );
+COMMENT ON TABLE exchange_sign_keys
+  IS 'Table with master public key signatures on exchange online signing 
keys.';
+COMMENT ON COLUMN exchange_sign_keys.exchange_pub
+  IS 'Public online signing key of the exchange.';
+COMMENT ON COLUMN exchange_sign_keys.master_sig
+  IS 'Signature affirming the validity of the signing key of purpose 
TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
+COMMENT ON COLUMN exchange_sign_keys.valid_from
+  IS 'Time when this online signing key will first be used to sign messages.';
+COMMENT ON COLUMN exchange_sign_keys.expire_sign
+  IS 'Time when this online signing key will no longer be used to sign.';
+COMMENT ON COLUMN exchange_sign_keys.expire_legal
+  IS 'Time when this online signing key legally expires.';
diff --git a/src/exchangedb/0002-known_coins.sql 
b/src/exchangedb/0002-known_coins.sql
new file mode 100644
index 0000000..a13beff
--- /dev/null
+++ b/src/exchangedb/0002-known_coins.sql
@@ -0,0 +1,136 @@
+--
+-- 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 FUNCTION create_table_known_coins(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'known_coins';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',denominations_serial INT8 NOT NULL'
+      ',coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)'
+      ',age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)'
+      ',denom_sig BYTEA NOT NULL'
+      ',remaining taler_amount NOT NULL DEFAULT(0,0)'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (coin_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'information about coins and their signatures, so we do not have to store 
the signatures more than once if a coin is involved in multiple operations'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Denomination of the coin, determines the value of the original coin and 
applicable fees for coin-specific operations.'
+    ,'denominations_serial'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'EdDSA public key of the coin'
+    ,'coin_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Value of the coin that remains to be spent'
+    ,'remaining'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Optional hash of the age commitment for age restrictions as per DD 24 
(active if denom_type has the respective bit set)'
+    ,'age_commitment_hash'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'This is the signature of the exchange that affirms that the coin is a 
valid coin. The specific signature type depends on denom_type of the 
denomination.'
+    ,'denom_sig'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_known_coins(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'known_coins';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_known_coin_id_key'
+    ' UNIQUE (known_coin_id)'
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_known_coins()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'known_coins';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_denominations'
+    ' FOREIGN KEY (denominations_serial) '
+    ' REFERENCES denominations (denominations_serial) ON DELETE CASCADE'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('known_coins'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('known_coins'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('known_coins'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-reserves.sql b/src/exchangedb/0002-reserves.sql
new file mode 100644
index 0000000..d710dd0
--- /dev/null
+++ b/src/exchangedb/0002-reserves.sql
@@ -0,0 +1,152 @@
+--
+-- 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 FUNCTION create_table_reserves(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'reserves';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(reserve_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)'
+      ',current_balance taler_amount NOT NULL DEFAULT (0, 0)'
+      ',purses_active INT8 NOT NULL DEFAULT(0)'
+      ',purses_allowed INT8 NOT NULL DEFAULT(0)'
+      ',birthday INT4 NOT NULL DEFAULT(0)'
+      ',expiration_date INT8 NOT NULL'
+      ',gc_date INT8 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (reserve_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'Summarizes the balance of a reserve. Updated when new funds are added or 
withdrawn.'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'EdDSA public key of the reserve. Knowledge of the private key implies 
ownership over the balance.'
+    ,'reserve_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Current balance remaining with the reserve.'
+    ,'current_balance'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Number of purses that were created by this reserve that are not expired 
and not fully paid.'
+    ,'purses_active'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Number of purses that this reserve is allowed to have active at most.'
+    ,'purses_allowed'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Used to trigger closing of reserves that have not been drained after 
some time'
+    ,'expiration_date'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Used to forget all information about a reserve during garbage collection'
+    ,'gc_date'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Birthday of the user in days after 1970, or 0 if user is an adult and is 
not subject to age restrictions'
+    ,'birthday'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'reserves';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_unique_uuid'
+    ' UNIQUE (reserve_uuid)'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_expiration_index '
+    'ON ' || table_name || ' '
+    '(expiration_date'
+    ',current_balance'
+    ');'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_expiration_index '
+    'IS ' || quote_literal('used in get_expired_reserves') || ';'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_reserve_uuid_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid);'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_gc_date_index '
+    'ON ' || table_name || ' '
+    '(gc_date);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_by_gc_date_index '
+    'IS ' || quote_literal('for reserve garbage collection') || ';'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('reserves'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('reserves'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-reserves_in.sql 
b/src/exchangedb/0002-reserves_in.sql
new file mode 100644
index 0000000..7fc2811
--- /dev/null
+++ b/src/exchangedb/0002-reserves_in.sql
@@ -0,0 +1,142 @@
+--
+-- 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 FUNCTION create_table_reserves_in(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'reserves_in';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',reserve_pub BYTEA PRIMARY KEY'
+      ',wire_reference INT8 NOT NULL'
+      ',credit taler_amount NOT NULL'
+      ',wire_source_h_payto BYTEA CHECK (LENGTH(wire_source_h_payto)=32)'
+      ',exchange_account_section TEXT NOT NULL'
+      ',execution_date INT8 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (reserve_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'list of transfers of funds into the reserves, one per incoming wire 
transfer'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Identifies the debited bank account and KYC status'
+    ,'wire_source_h_payto'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Public key of the reserve. Private key signifies ownership of the 
remaining balance.'
+    ,'reserve_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Amount that was transferred into the reserve'
+    ,'credit'
+    ,table_name
+    ,partition_suffix
+  );
+END $$;
+
+
+CREATE FUNCTION constrain_table_reserves_in(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'reserves_in';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_reserve_in_serial_id_key'
+    ' UNIQUE (reserve_in_serial_id)'
+  );
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_reserve_in_serial_id_index '
+    'ON ' || table_name || ' '
+    '(reserve_in_serial_id);'
+  );
+  -- FIXME: where do we need this index? Can we do better?
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || 
'_by_exch_accnt_section_execution_date_idx '
+    'ON ' || table_name || ' '
+    '(exchange_account_section '
+    ',execution_date'
+    ');'
+  );
+  -- FIXME: where do we need this index? Can we do better?
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_exch_accnt_reserve_in_serial_id_idx '
+    'ON ' || table_name || ' '
+    '(exchange_account_section'
+    ',reserve_in_serial_id DESC'
+    ');'
+  );
+END
+$$;
+
+CREATE FUNCTION foreign_table_reserves_in()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'reserves_in';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+    ' FOREIGN KEY (reserve_pub) '
+    ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE'
+  );
+END $$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('reserves_in'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('reserves_in'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('reserves_in'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-reserves_out.sql 
b/src/exchangedb/0002-reserves_out.sql
new file mode 100644
index 0000000..7c5cf55
--- /dev/null
+++ b/src/exchangedb/0002-reserves_out.sql
@@ -0,0 +1,239 @@
+--
+-- 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 FUNCTION create_table_reserves_out(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'reserves_out';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I'
+      '(reserve_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE'
+      ',denominations_serial INT8 NOT NULL'
+      ',denom_sig BYTEA NOT NULL'
+      ',reserve_uuid INT8 NOT NULL'
+      ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+      ',execution_date INT8 NOT NULL'
+      ',amount_with_fee taler_amount NOT NULL'
+    ') %s ;'
+    ,'reserves_out'
+    ,'PARTITION BY HASH (h_blind_ev)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table (
+     'Withdraw operations performed on reserves.'
+    ,'reserves_out'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column (
+     'Hash of the blinded coin, used as primary key here so that broken 
clients that use a non-random coin or blinding factor fail to withdraw 
(otherwise they would fail on deposit when the coin is not unique there).'
+    ,'h_blind_ev'
+    ,'reserves_out'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column (
+     'We do not CASCADE ON DELETE for the foreign constrain here, as we may 
keep the denomination data alive'
+    ,'denominations_serial'
+    ,'reserves_out'
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_out(
+  IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'reserves_out';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_reserve_out_serial_id_key'
+    ' UNIQUE (reserve_out_serial_id)'
+  );
+  -- FIXME: change query to use reserves_out_by_reserve instead and 
materialize execution_date there as well???
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || 
'_by_reserve_uuid_and_execution_date_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid, execution_date);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || 
'_by_reserve_uuid_and_execution_date_index '
+    'IS ' || quote_literal('for get_reserves_out and 
exchange_do_withdraw_limit_check') || ';'
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT default 'reserves_out';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_denom'
+    ' FOREIGN KEY (denominations_serial)'
+    ' REFERENCES denominations (denominations_serial)'
+    ',ADD CONSTRAINT ' || table_name || '_foreign_reserve '
+    ' FOREIGN KEY (reserve_uuid)'
+    ' REFERENCES reserves (reserve_uuid) ON DELETE CASCADE'
+  );
+END
+$$;
+
+
+CREATE FUNCTION create_table_reserves_out_by_reserve(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'reserves_out_by_reserve';
+BEGIN
+  PERFORM create_partitioned_table(
+  'CREATE TABLE %I'
+    '(reserve_uuid INT8 NOT NULL' -- REFERENCES reserves (reserve_uuid) ON 
DELETE CASCADE
+    ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64)'
+    ') %s '
+    ,table_name
+    ,'PARTITION BY HASH (reserve_uuid)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table (
+     'Information in this table is strictly redundant with that of 
reserves_out, but saved by a different primary key for fast lookups by reserve 
public key/uuid.'
+    ,table_name
+    ,partition_suffix
+  );
+END $$;
+
+
+CREATE FUNCTION constrain_table_reserves_out_by_reserve(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'reserves_out_by_reserve';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_main_index '
+    'ON ' || table_name || ' '
+    '(reserve_uuid);'
+  );
+END $$;
+
+
+CREATE FUNCTION reserves_out_by_reserve_insert_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  INSERT INTO exchange.reserves_out_by_reserve
+    (reserve_uuid
+    ,h_blind_ev)
+  VALUES
+    (NEW.reserve_uuid
+    ,NEW.h_blind_ev);
+  RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_out_by_reserve_insert_trigger()
+  IS 'Replicate reserve_out inserts into reserve_out_by_reserve table.';
+
+
+CREATE FUNCTION reserves_out_by_reserve_delete_trigger()
+  RETURNS trigger
+  LANGUAGE plpgsql
+  AS $$
+BEGIN
+  DELETE FROM exchange.reserves_out_by_reserve
+   WHERE reserve_uuid = OLD.reserve_uuid;
+  RETURN OLD;
+END $$;
+COMMENT ON FUNCTION reserves_out_by_reserve_delete_trigger()
+  IS 'Replicate reserve_out deletions into reserve_out_by_reserve table.';
+
+
+CREATE FUNCTION master_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  CREATE TRIGGER reserves_out_on_insert
+  AFTER INSERT
+   ON reserves_out
+   FOR EACH ROW EXECUTE FUNCTION reserves_out_by_reserve_insert_trigger();
+  CREATE TRIGGER reserves_out_on_delete
+  AFTER DELETE
+    ON reserves_out
+   FOR EACH ROW EXECUTE FUNCTION reserves_out_by_reserve_delete_trigger();
+END $$;
+COMMENT ON FUNCTION master_table_reserves_out()
+  IS 'Setup triggers to replicate reserve_out into reserve_out_by_reserve.';
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('reserves_out'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('reserves_out'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('reserves_out'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE),
+    ('reserves_out_by_reserve'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('reserves_out_by_reserve'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('reserves_out'
+    ,'exchange-0002'
+    ,'master'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-signkey_revocations.sql 
b/src/exchangedb/0002-signkey_revocations.sql
new file mode 100644
index 0000000..37ab32c
--- /dev/null
+++ b/src/exchangedb/0002-signkey_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- 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 TABLE signkey_revocations
+  (signkey_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+  ,esk_serial INT8 PRIMARY KEY REFERENCES exchange_sign_keys (esk_serial) ON 
DELETE CASCADE
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  );
+COMMENT ON TABLE signkey_revocations
+  IS 'Table storing which online signing keys have been revoked';
diff --git a/src/exchangedb/0002-wad_in_entries.sql 
b/src/exchangedb/0002-wad_in_entries.sql
new file mode 100644
index 0000000..000f5c6
--- /dev/null
+++ b/src/exchangedb/0002-wad_in_entries.sql
@@ -0,0 +1,185 @@
+--
+-- 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 FUNCTION create_table_wad_in_entries(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I '
+      '(wad_in_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',wad_in_serial_id INT8'
+      ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+      ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+      ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+      ',purse_expiration INT8 NOT NULL'
+      ',merge_timestamp INT8 NOT NULL'
+      ',amount_with_fee taler_amount NOT NULL'
+      ',wad_fee taler_amount NOT NULL'
+      ',deposit_fees taler_amount NOT NULL'
+      ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+      ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (purse_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'list of purses aggregated in a wad according to the sending exchange'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'wad for which the given purse was included in the aggregation'
+    ,'wad_in_serial_id'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'target account of the purse (must be at the local exchange)'
+    ,'reserve_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'public key of the purse that was merged'
+    ,'purse_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+    'hash of the contract terms of the purse'
+    ,'h_contract'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the purse was set to expire'
+    ,'purse_expiration'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the merge was approved'
+    ,'merge_timestamp'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Total amount in the purse'
+    ,'amount_with_fee'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Total wad fees paid by the purse'
+    ,'wad_fee'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Total deposit fees paid when depositing coins into the purse'
+    ,'deposit_fees'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Signature by the receiving reserve, of purpose 
TALER_SIGNATURE_ACCOUNT_MERGE'
+    ,'reserve_sig'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+    ,'purse_sig'
+    ,table_name
+    ,partition_suffix
+  );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wad_in_entries(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+
+  -- FIXME: change to materialized index by reserve_pub!
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_reserve_pub '
+    'ON ' || table_name || ' '
+    '(reserve_pub);'
+  );
+  EXECUTE FORMAT (
+    'COMMENT ON INDEX ' || table_name || '_reserve_pub '
+    'IS ' || quote_literal('needed in reserve history computation') || ';'
+  );
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_wad_in_entry_serial_id_key'
+    ' UNIQUE (wad_in_entry_serial_id) '
+  );
+END $$;
+
+
+CREATE FUNCTION foreign_table_wad_in_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_wad_in'
+    ' FOREIGN KEY(wad_in_serial_id)'
+    ' REFERENCES wads_in (wad_in_serial_id) ON DELETE CASCADE'
+  );
+END $$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('wad_in_entries'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('wad_in_entries'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('wad_in_entries'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-wad_out_entries.sql 
b/src/exchangedb/0002-wad_out_entries.sql
new file mode 100644
index 0000000..8da0f04
--- /dev/null
+++ b/src/exchangedb/0002-wad_out_entries.sql
@@ -0,0 +1,185 @@
+--
+-- 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 FUNCTION create_table_wad_out_entries(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+  PERFORM create_partitioned_table(
+     'CREATE TABLE %I '
+     '(wad_out_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+     ',wad_out_serial_id INT8'
+     ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+     ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+     ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+     ',purse_expiration INT8 NOT NULL'
+     ',merge_timestamp INT8 NOT NULL'
+     ',amount_with_fee taler_amount NOT NULL'
+     ',wad_fee taler_amount NOT NULL'
+     ',deposit_fees taler_amount NOT NULL'
+     ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+     ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+     ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (purse_pub)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+    'Purses combined into a wad'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Wad the purse was part of'
+    ,'wad_out_serial_id'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Target reserve for the purse'
+    ,'reserve_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Public key of the purse'
+    ,'purse_pub'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Hash of the contract associated with the purse'
+    ,'h_contract'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the purse expires'
+    ,'purse_expiration'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the merge was approved'
+    ,'merge_timestamp'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Total amount in the purse'
+    ,'amount_with_fee'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+    'Wad fee charged to the purse'
+    ,'wad_fee'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Total deposit fees charged to the purse'
+    ,'deposit_fees'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Signature by the receiving reserve, of purpose 
TALER_SIGNATURE_ACCOUNT_MERGE'
+    ,'reserve_sig'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+    ,'purse_sig'
+    ,table_name
+    ,partition_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wad_out_entries(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+
+  -- FIXME: change to materialized index by reserve_pub!
+  EXECUTE FORMAT (
+    'CREATE INDEX ' || table_name || '_by_reserve_pub '
+    'ON ' || table_name || ' '
+    '(reserve_pub);'
+  );
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_wad_out_entry_serial_id_key'
+    ' UNIQUE (wad_out_entry_serial_id) '
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wad_out_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_wad_out'
+    ' FOREIGN KEY(wad_out_serial_id)'
+    ' REFERENCES wads_out (wad_out_serial_id) ON DELETE CASCADE'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('wad_out_entries'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('wad_out_entries'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('wad_out_entries'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-wads_in.sql b/src/exchangedb/0002-wads_in.sql
new file mode 100644
index 0000000..479589b
--- /dev/null
+++ b/src/exchangedb/0002-wads_in.sql
@@ -0,0 +1,107 @@
+--
+-- 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 FUNCTION create_table_wads_in(
+  IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wads_in';
+BEGIN
+  PERFORM create_partitioned_table(
+     'CREATE TABLE %I '
+     '(wad_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+     ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+     ',origin_exchange_url TEXT NOT NULL'
+     ',amount taler_amount NOT NULL'
+     ',arrival_time INT8 NOT NULL'
+     ',UNIQUE (wad_id, origin_exchange_url)'
+     ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (wad_id)'
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'Incoming exchange-to-exchange wad wire transfers'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Unique identifier of the wad, part of the wire transfer subject'
+    ,'wad_id'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Base URL of the originating URL, also part of the wire transfer subject'
+    ,'origin_exchange_url'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Actual amount that was received by our exchange'
+    ,'amount'
+    ,table_name
+    ,partition_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the wad was received'
+    ,'arrival_time'
+    ,table_name
+    ,partition_suffix
+  );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wads_in(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wads_in';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_wad_in_serial_id_key'
+    ' UNIQUE (wad_in_serial_id) '
+    ',ADD CONSTRAINT ' || table_name || '_wad_is_origin_exchange_url_key'
+    ' UNIQUE (wad_id, origin_exchange_url) '
+  );
+END $$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('wads_in'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('wads_in'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/0002-wads_out.sql b/src/exchangedb/0002-wads_out.sql
new file mode 100644
index 0000000..e52010e
--- /dev/null
+++ b/src/exchangedb/0002-wads_out.sql
@@ -0,0 +1,128 @@
+--
+-- 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 FUNCTION create_table_wads_out(
+  IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wads_out';
+BEGIN
+  PERFORM create_partitioned_table(
+    'CREATE TABLE %I '
+      '(wad_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+      ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+      ',partner_serial_id INT8 NOT NULL'
+      ',amount taler_amount NOT NULL'
+      ',execution_time INT8 NOT NULL'
+    ') %s ;'
+    ,table_name
+    ,'PARTITION BY HASH (wad_id)'
+    ,shard_suffix
+  );
+  PERFORM comment_partitioned_table(
+     'Wire transfers made to another exchange to transfer purse funds'
+    ,table_name
+    ,shard_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Unique identifier of the wad, part of the wire transfer subject'
+    ,'wad_id'
+    ,table_name
+    ,shard_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'target exchange of the wad'
+    ,'partner_serial_id'
+    ,table_name
+    ,shard_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Amount that was wired'
+    ,'amount'
+    ,table_name
+    ,shard_suffix
+  );
+  PERFORM comment_partitioned_column(
+     'Time when the wire transfer was scheduled'
+    ,'execution_time'
+    ,table_name
+    ,shard_suffix
+  );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wads_out(
+  IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wads_out';
+BEGIN
+  table_name = concat_ws('_', table_name, partition_suffix);
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_wad_out_serial_id_key'
+    ' UNIQUE (wad_out_serial_id) '
+  );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wads_out()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  table_name TEXT DEFAULT 'wads_out';
+BEGIN
+  EXECUTE FORMAT (
+    'ALTER TABLE ' || table_name ||
+    ' ADD CONSTRAINT ' || table_name || '_foreign_partner'
+    ' FOREIGN KEY(partner_serial_id)'
+    ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+  );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+    (name
+    ,version
+    ,action
+    ,partitioned
+    ,by_range)
+  VALUES
+    ('wads_out'
+    ,'exchange-0002'
+    ,'create'
+    ,TRUE
+    ,FALSE),
+    ('wads_out'
+    ,'exchange-0002'
+    ,'constrain'
+    ,TRUE
+    ,FALSE),
+    ('wads_out'
+    ,'exchange-0002'
+    ,'foreign'
+    ,TRUE
+    ,FALSE);
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
new file mode 100644
index 0000000..4ffc574
--- /dev/null
+++ b/src/exchangedb/Makefile.am
@@ -0,0 +1,378 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ 
$(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+  exchangedb.conf \
+  exchangedb-postgres.conf
+
+sqldir = $(prefix)/share/taler/sql/exchange/
+
+sqlinputs = \
+  exchange_do_*.sql \
+  procedures.sql.in \
+  0002-*.sql \
+  exchange-0002.sql.in
+
+sql_DATA = \
+  benchmark-0001.sql \
+  versioning.sql \
+  exchange-0001.sql \
+  exchange-0002.sql \
+  drop.sql \
+  procedures.sql
+
+BUILT_SOURCES = \
+  benchmark-0001.sql \
+  drop.sql \
+  exchange-0001.sql \
+  procedures.sql
+
+CLEANFILES = \
+  exchange-0002.sql
+
+procedures.sql: procedures.sql.in exchange_do_*.sql
+       chmod +w $@ || true
+       gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" 
| awk 'NF' - >$@
+       chmod ugo-w $@
+
+exchange-0002.sql: exchange-0002.sql.in 0002-*.sql
+       chmod +w $@ || true
+       gcc -E -P -undef - < exchange-0002.sql.in 2>/dev/null | sed -e 
"s/--.*//" | awk 'NF' - >$@
+       chmod ugo-w $@
+
+check_SCRIPTS = \
+  test_idempotency.sh
+
+EXTRA_DIST = \
+  exchangedb.conf \
+  exchangedb-postgres.conf \
+  bench-db-postgres.conf \
+  test-exchange-db-postgres.conf \
+  $(sqlinputs) \
+  $(sql_DATA) \
+  $(check_SCRIPTS) \
+  pg_template.h pg_template.c \
+  pg_template.sh
+
+plugindir = $(libdir)/taler
+
+if HAVE_POSTGRESQL
+plugin_LTLIBRARIES = \
+  libtaler_plugin_exchangedb_postgres.la
+endif
+
+libtaler_plugin_exchangedb_postgres_la_SOURCES = \
+  plugin_exchangedb_common.c plugin_exchangedb_common.h \
+  pg_setup_wire_target.h pg_setup_wire_target.c \
+  pg_compute_shard.h pg_compute_shard.c \
+  plugin_exchangedb_postgres.c pg_helper.h \
+  pg_reserves_update.h pg_reserves_update.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 \
+  pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \
+  pg_lookup_kyc_requirement_by_row.h pg_lookup_kyc_requirement_by_row.c \
+  pg_insert_kyc_requirement_for_account.h 
pg_insert_kyc_requirement_for_account.c \
+  pg_lookup_kyc_process_by_account.h pg_lookup_kyc_process_by_account.c \
+  pg_update_kyc_process_by_row.h pg_update_kyc_process_by_row.c \
+  pg_insert_kyc_requirement_process.h pg_insert_kyc_requirement_process.c \
+  pg_select_withdraw_amounts_for_kyc_check.h 
pg_select_withdraw_amounts_for_kyc_check.c \
+  pg_select_merge_amounts_for_kyc_check.h 
pg_select_merge_amounts_for_kyc_check.c \
+  pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \
+  pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
+  pg_get_drain_profit.h pg_get_drain_profit.c \
+  pg_get_purse_deposit.h pg_get_purse_deposit.c \
+  pg_insert_contract.h pg_insert_contract.c \
+  pg_select_aml_threshold.h pg_select_aml_threshold.c \
+  pg_select_contract.h pg_select_contract.c \
+  pg_select_purse_merge.h pg_select_purse_merge.c \
+  pg_select_contract_by_purse.h pg_select_contract_by_purse.c \
+  pg_insert_drain_profit.h pg_insert_drain_profit.c \
+  pg_create_tables.h pg_create_tables.c \
+  pg_event_listen.h pg_event_listen.c \
+  pg_event_listen_cancel.h pg_event_listen_cancel.c \
+  pg_event_notify.h pg_event_notify.c \
+  pg_get_denomination_info.h pg_get_denomination_info.c \
+  pg_iterate_denomination_info.h pg_iterate_denomination_info.c \
+  pg_iterate_denominations.h pg_iterate_denominations.c \
+  pg_iterate_active_auditors.h pg_iterate_active_auditors.c \
+  pg_iterate_auditor_denominations.h pg_iterate_auditor_denominations.c \
+  pg_reserves_get.h pg_reserves_get.c \
+  pg_reserves_get_origin.h pg_reserves_get_origin.c \
+  pg_drain_kyc_alert.h pg_drain_kyc_alert.c \
+  pg_reserves_in_insert.h pg_reserves_in_insert.c \
+  pg_get_withdraw_info.h pg_get_withdraw_info.c \
+  pg_do_age_withdraw.h pg_do_age_withdraw.c \
+  pg_get_age_withdraw.h pg_get_age_withdraw.c \
+  pg_batch_ensure_coin_known.h pg_batch_ensure_coin_known.c \
+  pg_do_batch_withdraw.h pg_do_batch_withdraw.c \
+  pg_get_policy_details.h pg_get_policy_details.c \
+  pg_persist_policy_details.h pg_persist_policy_details.c \
+  pg_do_deposit.h pg_do_deposit.c \
+  pg_add_policy_fulfillment_proof.h pg_add_policy_fulfillment_proof.c \
+  pg_do_melt.h pg_do_melt.c \
+  pg_do_refund.h pg_do_refund.c \
+  pg_do_recoup.h pg_do_recoup.c \
+  pg_do_recoup_refresh.h pg_do_recoup_refresh.c \
+  pg_get_reserve_balance.h pg_get_reserve_balance.c \
+  pg_count_known_coins.h pg_count_known_coins.c \
+  pg_ensure_coin_known.h pg_ensure_coin_known.c \
+  pg_get_known_coin.h pg_get_known_coin.c \
+  pg_get_coin_denomination.h pg_get_coin_denomination.c \
+  pg_have_deposit2.h pg_have_deposit2.c \
+  pg_aggregate.h pg_aggregate.c \
+  pg_create_aggregation_transient.h pg_create_aggregation_transient.c \
+  pg_insert_kyc_attributes.h pg_insert_kyc_attributes.c \
+  pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \
+  pg_select_kyc_attributes.h pg_select_kyc_attributes.c \
+  pg_insert_aml_officer.h pg_insert_aml_officer.c \
+  pg_test_aml_officer.h pg_test_aml_officer.c \
+  pg_lookup_aml_officer.h pg_lookup_aml_officer.c \
+  pg_trigger_aml_process.h pg_trigger_aml_process.c \
+  pg_select_aml_process.h pg_select_aml_process.c \
+  pg_select_aml_history.h pg_select_aml_history.c \
+  pg_insert_aml_decision.h pg_insert_aml_decision.c \
+  pg_select_aggregation_transient.h pg_select_aggregation_transient.c \
+  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_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 \
+  pg_insert_refresh_reveal.h pg_insert_refresh_reveal.c \
+  pg_get_refresh_reveal.h pg_get_refresh_reveal.c \
+  pg_lookup_wire_transfer.h pg_lookup_wire_transfer.c \
+  pg_lookup_transfer_by_deposit.h pg_lookup_transfer_by_deposit.c \
+  pg_insert_wire_fee.h pg_insert_wire_fee.c \
+  pg_insert_global_fee.h pg_insert_global_fee.c \
+  pg_get_wire_fee.h pg_get_wire_fee.c \
+  pg_get_global_fee.h pg_get_global_fee.c \
+  pg_get_global_fees.h pg_get_global_fees.c \
+  pg_insert_reserve_closed.h pg_insert_reserve_closed.c \
+  pg_wire_prepare_data_insert.h pg_wire_prepare_data_insert.c \
+  pg_wire_prepare_data_mark_finished.h pg_wire_prepare_data_mark_finished.c \
+  pg_wire_prepare_data_mark_failed.h pg_wire_prepare_data_mark_failed.c \
+  pg_wire_prepare_data_get.h pg_wire_prepare_data_get.c \
+  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_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 \
+  pg_select_refreshes_above_serial_id.h pg_select_refreshes_above_serial_id.c \
+  pg_select_refunds_above_serial_id.h pg_select_refunds_above_serial_id.c \
+  pg_select_reserves_in_above_serial_id.h 
pg_select_reserves_in_above_serial_id.c \
+  pg_select_reserves_in_above_serial_id_by_account.h 
pg_select_reserves_in_above_serial_id_by_account.c \
+  pg_select_withdrawals_above_serial_id.h 
pg_select_withdrawals_above_serial_id.c \
+  pg_select_wire_out_above_serial_id.h pg_select_wire_out_above_serial_id.c \
+  pg_select_wire_out_above_serial_id_by_account.h 
pg_select_wire_out_above_serial_id_by_account.c \
+  pg_select_recoup_above_serial_id.h pg_select_recoup_above_serial_id.c \
+  pg_select_recoup_refresh_above_serial_id.h 
pg_select_recoup_refresh_above_serial_id.c \
+  pg_get_reserve_by_h_blind.h pg_get_reserve_by_h_blind.c \
+  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_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 \
+  pg_lookup_wire_timestamp.h pg_lookup_wire_timestamp.c \
+  pg_insert_wire.h pg_insert_wire.c \
+  pg_update_wire.h pg_update_wire.c \
+  pg_get_wire_accounts.h pg_get_wire_accounts.c \
+  pg_get_wire_fees.h pg_get_wire_fees.c \
+  pg_insert_signkey_revocation.h pg_insert_signkey_revocation.c \
+  pg_lookup_signkey_revocation.h pg_lookup_signkey_revocation.c \
+  pg_lookup_denomination_key.h pg_lookup_denomination_key.c \
+  pg_insert_auditor_denom_sig.h pg_insert_auditor_denom_sig.c \
+  pg_select_auditor_denom_sig.h pg_select_auditor_denom_sig.c \
+  pg_add_denomination_key.h pg_add_denomination_key.c \
+  pg_lookup_signing_key.h pg_lookup_signing_key.c \
+  pg_begin_shard.h pg_begin_shard.c \
+  pg_abort_shard.h pg_abort_shard.c \
+  pg_complete_shard.h pg_complete_shard.c \
+  pg_release_revolving_shard.h pg_release_revolving_shard.c \
+  pg_delete_shard_locks.h pg_delete_shard_locks.c \
+  pg_set_extension_manifest.h pg_set_extension_manifest.c \
+  pg_insert_partner.h pg_insert_partner.c \
+  pg_expire_purse.h pg_expire_purse.c \
+  pg_select_purse_by_merge_pub.h pg_select_purse_by_merge_pub.c \
+  pg_set_purse_balance.h pg_set_purse_balance.c \
+  pg_do_reserve_purse.h pg_do_reserve_purse.c \
+  pg_lookup_global_fee_by_time.h pg_lookup_global_fee_by_time.c \
+  pg_do_purse_deposit.h pg_do_purse_deposit.c \
+  pg_activate_signing_key.h pg_activate_signing_key.c \
+  pg_update_auditor.h pg_update_auditor.c \
+  pg_begin_revolving_shard.h pg_begin_revolving_shard.c \
+  pg_get_extension_manifest.h pg_get_extension_manifest.c \
+  pg_insert_history_request.h pg_insert_history_request.c \
+  pg_do_purse_merge.h pg_do_purse_merge.c \
+  pg_start_read_committed.h pg_start_read_committed.c \
+  pg_start_read_only.h pg_start_read_only.c \
+  pg_insert_denomination_info.h pg_insert_denomination_info.c \
+  pg_do_batch_withdraw_insert.h pg_do_batch_withdraw_insert.c \
+  pg_do_reserve_open.c pg_do_reserve_open.h \
+  pg_do_purse_delete.c pg_do_purse_delete.h \
+  pg_do_withdraw.h pg_do_withdraw.c \
+  pg_preflight.h pg_preflight.c \
+  pg_iterate_active_signkeys.h pg_iterate_active_signkeys.c \
+  pg_commit.h pg_commit.c \
+  pg_get_coin_transactions.c pg_get_coin_transactions.h \
+  pg_get_expired_reserves.c pg_get_expired_reserves.h \
+  pg_start.h pg_start.c \
+  pg_rollback.h pg_rollback.c \
+  pg_get_purse_request.c pg_get_purse_request.h \
+  pg_get_reserve_history.c pg_get_reserve_history.h \
+  pg_get_unfinished_close_requests.c pg_get_unfinished_close_requests.h \
+  pg_insert_close_request.c pg_insert_close_request.h \
+  pg_delete_aggregation_transient.h pg_delete_aggregation_transient.c \
+  pg_get_link_data.h pg_get_link_data.c \
+  pg_drop_tables.h pg_drop_tables.c \
+  pg_insert_purse_request.h pg_insert_purse_request.c \
+  pg_insert_records_by_table.c pg_insert_records_by_table.h \
+  pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h \
+  pg_iterate_kyc_reference.c pg_iterate_kyc_reference.h \
+  pg_iterate_reserve_close_info.c pg_iterate_reserve_close_info.h \
+  pg_lookup_records_by_table.c pg_lookup_records_by_table.h \
+  pg_lookup_serial_by_table.c pg_lookup_serial_by_table.h \
+  pg_select_reserve_close_info.c pg_select_reserve_close_info.h \
+  pg_select_reserve_closed_above_serial_id.c 
pg_select_reserve_closed_above_serial_id.h \
+  pg_select_purse.h pg_select_purse.c \
+  pg_select_purse_requests_above_serial_id.h 
pg_select_purse_requests_above_serial_id.c \
+  pg_select_purse_merges_above_serial_id.h 
pg_select_purse_merges_above_serial_id.c \
+  pg_select_purse_deposits_above_serial_id.h 
pg_select_purse_deposits_above_serial_id.c \
+  pg_select_account_merges_above_serial_id.h 
pg_select_account_merges_above_serial_id.c \
+  pg_select_all_purse_decisions_above_serial_id.h 
pg_select_all_purse_decisions_above_serial_id.c \
+  pg_select_reserve_open_above_serial_id.c 
pg_select_reserve_open_above_serial_id.h
+libtaler_plugin_exchangedb_postgres_la_LIBADD = \
+  $(LTLIBINTL)
+libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \
+  $(TALER_PLUGIN_LDFLAGS) \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lpq \
+  -lpthread \
+  -lgnunetpq \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+lib_LTLIBRARIES = \
+  libtalerexchangedb.la
+
+libtalerexchangedb_la_SOURCES = \
+  exchangedb_accounts.c \
+  exchangedb_plugin.c \
+  exchangedb_transactions.c
+libtalerexchangedb_la_LIBADD = \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil \
+  $(XLIB)
+libtalerexchangedb_la_LDFLAGS = \
+  $(POSTGRESQL_LDFLAGS) \
+  -version-info 1:0:0 \
+  -no-undefined
+
+
+check_PROGRAMS = \
+  test-exchangedb-postgres
+
+noinst_PROGRAMS = \
+  bench-db-postgres\
+  perf_get_link_data-postgres\
+  perf_select_refunds_by_coin-postgres\
+  perf_reserves_in_insert-postgres \
+  perf_deposits_get_ready-postgres
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+TESTS = \
+  $(check_SCRIPTS) \
+  $(check_PROGRAMS)
+
+test_exchangedb_postgres_SOURCES = \
+  test_exchangedb.c
+test_exchangedb_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -ljansson \
+  -lgnunetjson \
+  -lgnunetutil \
+  $(XLIB)
+
+bench_db_postgres_SOURCES = \
+  bench_db.c
+bench_db_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -lgnunetpq \
+  -lgnunetutil \
+  $(XLIB)
+
+perf_reserves_in_insert_postgres_SOURCES = \
+  perf_reserves_in_insert.c
+perf_reserves_in_insert_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -ljansson \
+  -lgnunetjson \
+  -lgnunetutil \
+  -lm \
+  $(XLIB)
+
+perf_select_refunds_by_coin_postgres_SOURCES = \
+  perf_select_refunds_by_coin.c
+perf_select_refunds_by_coin_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -ljansson \
+  -lgnunetjson \
+  -lgnunetutil \
+  -lm \
+  $(XLIB)
+
+perf_get_link_data_postgres_SOURCES = \
+  perf_get_link_data.c
+perf_get_link_data_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -ljansson \
+  -lgnunetjson \
+  -lgnunetutil \
+  -lm \
+  $(XLIB)
+
+perf_deposits_get_ready_postgres_SOURCES = \
+  perf_deposits_get_ready.c
+perf_deposits_get_ready_postgres_LDADD = \
+  libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/pq/libtalerpq.la \
+  -ljansson \
+  -lgnunetjson \
+  -lgnunetutil \
+  -lm \
+  $(XLIB)
+
+
+EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
+  libtaler_plugin_exchangedb_postgres.la
diff --git a/src/exchangedb/bench-db-postgres.conf 
b/src/exchangedb/bench-db-postgres.conf
new file mode 100644
index 0000000..d51cf91
--- /dev/null
+++ b/src/exchangedb/bench-db-postgres.conf
@@ -0,0 +1,14 @@
+# This file is in the public domain.
+#
+# Database-backend independent specification for the exchangedb module.
+#
+[bench-db-postgres]
+CONFIG = postgres:///talercheck
+
+# Where are the SQL files to setup our tables?
+# Important: this MUST end with a "/"!
+SQL_DIR = $DATADIR/sql/exchange/
+
+[exchangedb]
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1
\ No newline at end of file
diff --git a/src/exchangedb/benchmark-0001.sql 
b/src/exchangedb/benchmark-0001.sql
new file mode 100644
index 0000000..34fed6a
--- /dev/null
+++ b/src/exchangedb/benchmark-0001.sql
@@ -0,0 +1,55 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2021 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('benchmark-0001', NULL, NULL);
+
+-- Naive, btree version
+CREATE TABLE IF NOT EXISTS benchmap
+  (uuid BIGSERIAL PRIMARY KEY
+  ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+  ,expiration_date INT8 NOT NULL
+  );
+
+-- Replace btree with hash-based index
+CREATE TABLE IF NOT EXISTS benchhmap
+  (uuid BIGSERIAL PRIMARY KEY
+  ,hc BYTEA NOT NULL CHECK(LENGTH(hc)=64)
+  ,expiration_date INT8 NOT NULL
+  );
+CREATE INDEX IF NOT EXISTS benchhmap_index
+  ON benchhmap
+  USING HASH (hc);
+ALTER TABLE benchhmap
+  ADD CONSTRAINT pk
+  EXCLUDE USING HASH (hc with =);
+
+-- Keep btree, also add 32-bit hash-based index on top
+CREATE TABLE IF NOT EXISTS benchemap
+  (uuid BIGSERIAL PRIMARY KEY
+  ,ihc INT4 NOT NULL
+  ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+  ,expiration_date INT8 NOT NULL
+  );
+CREATE INDEX IF NOT EXISTS benchemap_index
+  ON benchemap
+  USING HASH (ihc);
+
+-- Complete transaction
+COMMIT;
diff --git a/src/exchangedb/drop.sql b/src/exchangedb/drop.sql
new file mode 100644
index 0000000..ff383d7
--- /dev/null
+++ b/src/exchangedb/drop.sql
@@ -0,0 +1,26 @@
+--
+-- 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+
+SELECT _v.unregister_patch('exchange-0001');
+SELECT _v.unregister_patch('exchange-0002');
+
+DROP SCHEMA exchange CASCADE;
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
new file mode 100644
index 0000000..a4b1c8b
--- /dev/null
+++ b/src/exchangedb/exchange-0001.sql
@@ -0,0 +1,296 @@
+--
+-- 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0001', NULL, NULL);
+
+CREATE SCHEMA exchange;
+COMMENT ON SCHEMA exchange IS 'taler-exchange data';
+
+SET search_path TO exchange;
+
+---------------------------------------------------------------------------
+--                   General procedures for DB setup
+---------------------------------------------------------------------------
+
+CREATE TABLE exchange_tables
+  (table_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+  ,name TEXT NOT NULL
+  ,version TEXT NOT NULL
+  ,action TEXT NOT NULL
+  ,partitioned BOOL NOT NULL
+  ,by_range BOOL NOT NULL
+  ,finished BOOL NOT NULL DEFAULT(FALSE));
+COMMENT ON TABLE exchange_tables
+  IS 'Tables of the exchange and their status';
+COMMENT ON COLUMN exchange_tables.name
+  IS 'Base name of the table (without partition/shard)';
+COMMENT ON COLUMN exchange_tables.version
+  IS 'Version of the DB in which the given action happened';
+COMMENT ON COLUMN exchange_tables.action
+  IS 'Action to take on the table (e.g. create, constrain, or foreign). Create 
is done for the master table and each partition; constrain is only for 
partitions or for master if there are no partitions; master only on master 
(takes no argument); foreign only on master if there are no partitions.';
+COMMENT ON COLUMN exchange_tables.partitioned
+  IS 'TRUE if the table is partitioned';
+COMMENT ON COLUMN exchange_tables.by_range
+  IS 'TRUE if the table is partitioned by range';
+COMMENT ON COLUMN exchange_tables.finished
+  IS 'TRUE if the respective migration has been run';
+
+
+CREATE FUNCTION create_partitioned_table(
+   IN table_definition TEXT -- SQL template for table creation
+  ,IN table_name TEXT -- base name of the table
+  ,IN main_table_partition_str TEXT -- declaration for how to partition the 
table
+  ,IN partition_suffix TEXT DEFAULT NULL -- NULL: no partitioning, 0: yes 
partitioning, no sharding, >0: sharding
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  IF (partition_suffix IS NULL)
+  THEN
+    -- no partitioning, disable option
+    main_table_partition_str = '';
+  ELSE
+    IF (partition_suffix::int > 0)
+    THEN
+      -- sharding, add shard name
+      table_name=table_name || '_' || partition_suffix;
+    END IF;
+  END IF;
+  EXECUTE FORMAT(
+    table_definition,
+    table_name,
+    main_table_partition_str
+  );
+END $$;
+
+COMMENT ON FUNCTION create_partitioned_table
+  IS 'Generic function to create a table that is partitioned or sharded.';
+
+
+CREATE FUNCTION comment_partitioned_table(
+   IN table_comment TEXT
+  ,IN table_name TEXT
+  ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  IF ( (partition_suffix IS NOT NULL) AND
+       (partition_suffix::int > 0) )
+  THEN
+    -- sharding, add shard name
+    table_name=table_name || '_' || partition_suffix;
+  END IF;
+  EXECUTE FORMAT(
+     'COMMENT ON TABLE %s IS %s'
+    ,table_name
+    ,quote_literal(table_comment)
+  );
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_table
+  IS 'Generic function to create a comment on table that is partitioned.';
+
+
+CREATE FUNCTION comment_partitioned_column(
+   IN table_comment TEXT
+  ,IN column_name TEXT
+  ,IN table_name TEXT
+  ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  IF ( (partition_suffix IS NOT NULL) AND
+       (partition_suffix::int > 0) )
+  THEN
+    -- sharding, add shard name
+    table_name=table_name || '_' || partition_suffix;
+  END IF;
+  EXECUTE FORMAT(
+     'COMMENT ON COLUMN %s.%s IS %s'
+    ,table_name
+    ,column_name
+    ,quote_literal(table_comment)
+  );
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_column
+  IS 'Generic function to create a comment on column of a table that is 
partitioned.';
+
+
+---------------------------------------------------------------------------
+--                   Main DB setup loop
+---------------------------------------------------------------------------
+
+CREATE FUNCTION do_create_tables(
+  num_partitions INTEGER
+-- NULL: no partitions, add foreign constraints
+-- 0: no partitions, no foreign constraints
+-- 1: only 1 default partition
+-- > 1: normal partitions
+)
+  RETURNS VOID
+  LANGUAGE plpgsql
+AS $$
+DECLARE
+  tc CURSOR FOR
+    SELECT table_serial_id
+          ,name
+          ,action
+          ,partitioned
+          ,by_range
+      FROM exchange.exchange_tables
+     WHERE NOT finished
+     ORDER BY table_serial_id ASC;
+BEGIN
+  FOR rec IN tc
+  LOOP
+    CASE rec.action
+    -- "create" actions apply to master and partitions
+    WHEN 'create'
+    THEN
+      IF (rec.partitioned AND
+          (num_partitions IS NOT NULL))
+      THEN
+        -- Create master table with partitioning.
+        EXECUTE FORMAT(
+          'SELECT exchange.%s_table_%s (%s)'::text
+          ,rec.action
+          ,rec.name
+          ,quote_literal('0')
+        );
+        IF (rec.by_range OR
+            (num_partitions = 0))
+        THEN
+          -- Create default partition.
+          IF (rec.by_range)
+          THEN
+            -- Range partition
+            EXECUTE FORMAT(
+              'CREATE TABLE exchange.%s_default'
+              ' PARTITION OF %s'
+              ' DEFAULT'
+             ,rec.name
+             ,rec.name
+            );
+          ELSE
+            -- Hash partition
+            EXECUTE FORMAT(
+              'CREATE TABLE exchange.%s_default'
+              ' PARTITION OF %s'
+              ' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
+             ,rec.name
+             ,rec.name
+            );
+          END IF;
+        ELSE
+          FOR i IN 1..num_partitions LOOP
+            -- Create num_partitions
+            EXECUTE FORMAT(
+               'CREATE TABLE exchange.%I'
+               ' PARTITION OF %I'
+               ' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
+              ,rec.name || '_' || i
+              ,rec.name
+              ,num_partitions
+              ,i-1
+            );
+          END LOOP;
+        END IF;
+      ELSE
+        -- Only create master table. No partitions.
+        EXECUTE FORMAT(
+          'SELECT exchange.%s_table_%s ()'::text
+          ,rec.action
+          ,rec.name
+        );
+      END IF;
+    -- Constrain action apply to master OR each partition
+    WHEN 'constrain'
+    THEN
+      ASSERT rec.partitioned, 'constrain action only applies to partitioned 
tables';
+      IF (num_partitions IS NULL)
+      THEN
+        -- Constrain master table
+        EXECUTE FORMAT(
+           'SELECT exchange.%s_table_%s (NULL)'::text
+          ,rec.action
+          ,rec.name
+        );
+      ELSE
+        IF ( (num_partitions = 0) OR
+             (rec.by_range) )
+        THEN
+          -- Constrain default table
+          EXECUTE FORMAT(
+             'SELECT exchange.%s_table_%s (%s)'::text
+            ,rec.action
+            ,rec.name
+            ,quote_literal('default')
+          );
+        ELSE
+          -- Constrain each partition
+          FOR i IN 1..num_partitions LOOP
+            EXECUTE FORMAT(
+              'SELECT exchange.%s_table_%s (%s)'::text
+              ,rec.action
+              ,rec.name
+              ,quote_literal(i)
+            );
+          END LOOP;
+        END IF;
+      END IF;
+    -- Foreign actions only apply if partitioning is off
+    WHEN 'foreign'
+    THEN
+      IF (num_partitions IS NULL)
+      THEN
+        -- Add foreign constraints
+        EXECUTE FORMAT(
+          'SELECT exchange.%s_table_%s (%s)'::text
+          ,rec.action
+          ,rec.name
+          ,NULL
+        );
+      END IF;
+    WHEN 'master'
+    THEN
+      EXECUTE FORMAT(
+        'SELECT exchange.%s_table_%s ()'::text
+        ,rec.action
+        ,rec.name
+      );
+    ELSE
+      ASSERT FALSE, 'unsupported action type: ' || rec.action;
+    END CASE;  -- END CASE (rec.action)
+    -- Mark as finished
+    UPDATE exchange.exchange_tables
+       SET finished=TRUE
+     WHERE table_serial_id=rec.table_serial_id;
+  END LOOP; -- create/alter/drop actions
+END $$;
+
+COMMENT ON FUNCTION do_create_tables
+  IS 'Creates all tables for the given number of partitions that need 
creating. Does NOT support sharding.';
+
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0002.sql.in 
b/src/exchangedb/exchange-0002.sql.in
new file mode 100644
index 0000000..9a810aa
--- /dev/null
+++ b/src/exchangedb/exchange-0002.sql.in
@@ -0,0 +1,101 @@
+--
+-- 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/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0002', NULL, NULL);
+SET search_path TO exchange;
+
+CREATE TYPE taler_amount
+  AS
+  (val INT8
+  ,frac INT4
+  );
+COMMENT ON TYPE taler_amount
+  IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
+CREATE TYPE exchange_do_array_reserve_insert_return_type
+  AS
+  (transaction_duplicate BOOLEAN
+  ,ruuid INT8
+  );
+COMMENT ON TYPE exchange_do_array_reserve_insert_return_type
+  IS 'Return type for exchange_do_array_reserves_insert() stored procedure';
+
+#include "0002-denominations.sql"
+#include "0002-denomination_revocations.sql"
+#include "0002-wire_targets.sql"
+#include "0002-kyc_alerts.sql"
+#include "0002-wire_fee.sql"
+#include "0002-global_fee.sql"
+#include "0002-wire_accounts.sql"
+#include "0002-auditors.sql"
+#include "0002-auditor_denom_sigs.sql"
+#include "0002-exchange_sign_keys.sql"
+#include "0002-signkey_revocations.sql"
+#include "0002-extensions.sql"
+#include "0002-profit_drains.sql"
+#include "0002-legitimization_processes.sql"
+#include "0002-legitimization_requirements.sql"
+#include "0002-reserves.sql"
+#include "0002-reserves_in.sql"
+#include "0002-reserves_close.sql"
+#include "0002-close_requests.sql"
+#include "0002-reserves_open_deposits.sql"
+#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-batch_deposits.sql"
+#include "0002-coin_deposits.sql"
+#include "0002-refunds.sql"
+#include "0002-wire_out.sql"
+#include "0002-aggregation_transient.sql"
+#include "0002-aggregation_tracking.sql"
+#include "0002-recoup.sql"
+#include "0002-recoup_refresh.sql"
+#include "0002-prewire.sql"
+#include "0002-cs_nonce_locks.sql"
+#include "0002-purse_requests.sql"
+#include "0002-purse_merges.sql"
+#include "0002-account_merges.sql"
+#include "0002-purse_decision.sql"
+#include "0002-contracts.sql"
+#include "0002-history_requests.sql"
+#include "0002-purse_deposits.sql"
+#include "0002-wads_in.sql"
+#include "0002-wad_in_entries.sql"
+#include "0002-wads_out.sql"
+#include "0002-wad_out_entries.sql"
+#include "0002-policy_fulfillments.sql"
+#include "0002-policy_details.sql"
+#include "0002-work_shards.sql"
+#include "0002-revolving_work_shards.sql"
+#include "0002-partners.sql"
+#include "0002-partner_accounts.sql"
+#include "0002-purse_actions.sql"
+#include "0002-purse_deletion.sql"
+#include "0002-kyc_attributes.sql"
+#include "0002-aml_status.sql"
+#include "0002-aml_staff.sql"
+#include "0002-aml_history.sql"
+#include "0002-age_withdraw.sql"
+
+
+COMMIT;
diff --git a/src/exchangedb/exchange_do_account_merge.sql 
b/src/exchangedb/exchange_do_account_merge.sql
new file mode 100644
index 0000000..723154f
--- /dev/null
+++ b/src/exchangedb/exchange_do_account_merge.sql
@@ -0,0 +1,15 @@
+--
+-- 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/>
+--
diff --git a/src/exchangedb/exchange_do_amount_specific.sql 
b/src/exchangedb/exchange_do_amount_specific.sql
new file mode 100644
index 0000000..e8f60f4
--- /dev/null
+++ b/src/exchangedb/exchange_do_amount_specific.sql
@@ -0,0 +1,78 @@
+--------------------------------------------------------------
+-- Taler amounts and helper functions
+-------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION amount_normalize(
+    IN amount taler_amount
+  ,OUT normalized taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  normalized.val = amount.val + amount.frac / 100000000;
+  normalized.frac = amount.frac % 100000000;
+END $$;
+
+COMMENT ON FUNCTION amount_normalize
+  IS 'Returns the normalized amount by adding to the .val the value of (.frac 
/ 100000000) and removing the modulus 100000000 from .frac.';
+
+CREATE OR REPLACE FUNCTION amount_add(
+   IN a taler_amount
+  ,IN b taler_amount
+  ,OUT sum taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  sum = (a.val + b.val, a.frac + b.frac);
+  CALL amount_normalize(sum ,sum);
+
+  IF (sum.val > (1<<52))
+  THEN
+    RAISE EXCEPTION 'addition overflow';
+  END IF;
+END $$;
+
+COMMENT ON FUNCTION amount_add
+  IS 'Returns the normalized sum of two amounts. It raises an exception when 
the resulting .val is larger than 2^52';
+
+CREATE OR REPLACE FUNCTION amount_left_minus_right(
+  IN  l taler_amount
+ ,IN  r taler_amount
+ ,OUT diff taler_amount
+ ,OUT ok BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+IF (l.val > r.val)
+THEN
+  ok = TRUE;
+  IF (l.frac >= r.frac)
+  THEN
+    diff.val  = l.val  - r.val;
+    diff.frac = l.frac - r.frac;
+  ELSE
+    diff.val  = l.val  - r.val - 1;
+    diff.frac = l.frac + 100000000 - r.frac;
+  END IF;
+ELSE
+  IF (l.val = r.val) AND (l.frac >= r.frac)
+  THEN
+    diff.val  = 0;
+    diff.frac = l.frac - r.frac;
+    ok = TRUE;
+  ELSE
+    diff = (-1, -1);
+    ok = FALSE;
+  END IF;
+END IF;
+
+RETURN;
+END $$;
+
+COMMENT ON FUNCTION amount_left_minus_right
+  IS 'Subtracts the right amount from the left and returns the difference and 
TRUE, if the left amount is larger than the right, or an invalid amount and 
FALSE otherwise.';
+
+
diff --git a/src/exchangedb/exchange_do_batch_coin_known.sql 
b/src/exchangedb/exchange_do_batch_coin_known.sql
new file mode 100644
index 0000000..db96cb0
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_coin_known.sql
@@ -0,0 +1,469 @@
+--
+-- 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_batch4_known_coin(
+  IN in_coin_pub1 BYTEA,
+  IN in_denom_pub_hash1 BYTEA,
+  IN in_h_age_commitment1 BYTEA,
+  IN in_denom_sig1 BYTEA,
+  IN in_coin_pub2 BYTEA,
+  IN in_denom_pub_hash2 BYTEA,
+  IN in_h_age_commitment2 BYTEA,
+  IN in_denom_sig2 BYTEA,
+  IN in_coin_pub3 BYTEA,
+  IN in_denom_pub_hash3 BYTEA,
+  IN in_h_age_commitment3 BYTEA,
+  IN in_denom_sig3 BYTEA,
+  IN in_coin_pub4 BYTEA,
+  IN in_denom_pub_hash4 BYTEA,
+  IN in_h_age_commitment4 BYTEA,
+  IN in_denom_sig4 BYTEA,
+  OUT existed1 BOOLEAN,
+  OUT existed2 BOOLEAN,
+  OUT existed3 BOOLEAN,
+  OUT existed4 BOOLEAN,
+  OUT known_coin_id1 INT8,
+  OUT known_coin_id2 INT8,
+  OUT known_coin_id3 INT8,
+  OUT known_coin_id4 INT8,
+  OUT denom_pub_hash1 BYTEA,
+  OUT denom_pub_hash2 BYTEA,
+  OUT denom_pub_hash3 BYTEA,
+  OUT denom_pub_hash4 BYTEA,
+  OUT age_commitment_hash1 BYTEA,
+  OUT age_commitment_hash2 BYTEA,
+  OUT age_commitment_hash3 BYTEA,
+  OUT age_commitment_hash4 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+  denominations_serial,
+  coin
+  FROM denominations
+    WHERE denom_pub_hash
+    IN
+     (in_denom_pub_hash1,
+      in_denom_pub_hash2,
+      in_denom_pub_hash3,
+      in_denom_pub_hash4)
+     ),--dd
+     input_rows AS (
+     VALUES
+      (in_coin_pub1,
+      in_denom_pub_hash1,
+      in_h_age_commitment1,
+      in_denom_sig1),
+      (in_coin_pub2,
+      in_denom_pub_hash2,
+      in_h_age_commitment2,
+      in_denom_sig2),
+      (in_coin_pub3,
+      in_denom_pub_hash3,
+      in_h_age_commitment3,
+      in_denom_sig3),
+      (in_coin_pub4,
+      in_denom_pub_hash4,
+      in_h_age_commitment4,
+      in_denom_sig4)
+      ),--ir
+      ins AS (
+      INSERT INTO known_coins (
+      coin_pub,
+      denominations_serial,
+      age_commitment_hash,
+      denom_sig,
+      remaining
+      )
+      SELECT
+        ir.coin_pub,
+        dd.denominations_serial,
+        ir.age_commitment_hash,
+        ir.denom_sig,
+        dd.coin
+        FROM input_rows ir
+        JOIN dd
+          ON dd.denom_pub_hash = ir.denom_pub_hash
+          ON CONFLICT DO NOTHING
+          RETURNING known_coin_id
+      ),--kc
+       exists AS (
+         SELECT
+         CASE
+           WHEN
+             ins.known_coin_id IS NOT NULL
+             THEN
+               FALSE
+             ELSE
+               TRUE
+         END AS existed,
+         ins.known_coin_id,
+         dd.denom_pub_hash,
+         kc.age_commitment_hash
+         FROM input_rows ir
+         LEFT JOIN ins
+           ON ins.coin_pub = ir.coin_pub
+         LEFT JOIN known_coins kc
+           ON kc.coin_pub = ir.coin_pub
+         LEFT JOIN dd
+           ON dd.denom_pub_hash = ir.denom_pub_hash
+         )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1,
+ (
+   SELECT exists.existed
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS existed2,
+ (
+   SELECT exists.known_coin_id
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS known_coin_id2,
+ (
+   SELECT exists.denom_pub_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS denom_pub_hash2,
+ (
+   SELECT exists.age_commitment_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ )AS age_commitment_hash2,
+ (
+   SELECT exists.existed
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS existed3,
+ (
+   SELECT exists.known_coin_id
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS known_coin_id3,
+ (
+   SELECT exists.denom_pub_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS denom_pub_hash3,
+ (
+   SELECT exists.age_commitment_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ )AS age_commitment_hash3,
+ (
+   SELECT exists.existed
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS existed4,
+ (
+   SELECT exists.known_coin_id
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS known_coin_id4,
+ (
+   SELECT exists.denom_pub_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS denom_pub_hash4,
+ (
+   SELECT exists.age_commitment_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ )AS age_commitment_hash4
+FROM exists;
+
+RETURN;
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
+  IN in_coin_pub1 BYTEA,
+  IN in_denom_pub_hash1 BYTEA,
+  IN in_h_age_commitment1 BYTEA,
+  IN in_denom_sig1 BYTEA,
+  IN in_coin_pub2 BYTEA,
+  IN in_denom_pub_hash2 BYTEA,
+  IN in_h_age_commitment2 BYTEA,
+  IN in_denom_sig2 BYTEA,
+  OUT existed1 BOOLEAN,
+  OUT existed2 BOOLEAN,
+  OUT known_coin_id1 INT8,
+  OUT known_coin_id2 INT8,
+  OUT denom_pub_hash1 BYTEA,
+  OUT denom_pub_hash2 BYTEA,
+  OUT age_commitment_hash1 BYTEA,
+  OUT age_commitment_hash2 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+  denominations_serial,
+  coin
+  FROM denominations
+    WHERE denom_pub_hash
+    IN
+     (in_denom_pub_hash1,
+      in_denom_pub_hash2)
+     ),--dd
+     input_rows AS (
+     VALUES
+      (in_coin_pub1,
+      in_denom_pub_hash1,
+      in_h_age_commitment1,
+      in_denom_sig1),
+      (in_coin_pub2,
+      in_denom_pub_hash2,
+      in_h_age_commitment2,
+      in_denom_sig2)
+      ),--ir
+      ins AS (
+      INSERT INTO known_coins (
+      coin_pub,
+      denominations_serial,
+      age_commitment_hash,
+      denom_sig,
+      remaining
+      )
+      SELECT
+        ir.coin_pub,
+        dd.denominations_serial,
+        ir.age_commitment_hash,
+        ir.denom_sig,
+        dd.coin
+        FROM input_rows ir
+        JOIN dd
+          ON dd.denom_pub_hash = ir.denom_pub_hash
+          ON CONFLICT DO NOTHING
+          RETURNING known_coin_id
+      ),--kc
+       exists AS (
+       SELECT
+        CASE
+          WHEN ins.known_coin_id IS NOT NULL
+          THEN
+            FALSE
+          ELSE
+            TRUE
+        END AS existed,
+        ins.known_coin_id,
+        dd.denom_pub_hash,
+        kc.age_commitment_hash
+        FROM input_rows ir
+        LEFT JOIN ins
+          ON ins.coin_pub = ir.coin_pub
+        LEFT JOIN known_coins kc
+          ON kc.coin_pub = ir.coin_pub
+        LEFT JOIN dd
+          ON dd.denom_pub_hash = ir.denom_pub_hash
+     )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1,
+ (
+   SELECT exists.existed
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS existed2,
+ (
+   SELECT exists.known_coin_id
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS known_coin_id2,
+ (
+   SELECT exists.denom_pub_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS denom_pub_hash2,
+ (
+   SELECT exists.age_commitment_hash
+   FROM exists
+   WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ )AS age_commitment_hash2
+FROM exists;
+
+RETURN;
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_batch1_known_coin(
+  IN in_coin_pub1 BYTEA,
+  IN in_denom_pub_hash1 BYTEA,
+  IN in_h_age_commitment1 BYTEA,
+  IN in_denom_sig1 BYTEA,
+  OUT existed1 BOOLEAN,
+  OUT known_coin_id1 INT8,
+  OUT denom_pub_hash1 BYTEA,
+  OUT age_commitment_hash1 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+  denominations_serial,
+  coin
+  FROM denominations
+    WHERE denom_pub_hash
+    IN
+     (in_denom_pub_hash1,
+      in_denom_pub_hash2)
+     ),--dd
+     input_rows AS (
+     VALUES
+      (in_coin_pub1,
+      in_denom_pub_hash1,
+      in_h_age_commitment1,
+      in_denom_sig1)
+      ),--ir
+      ins AS (
+      INSERT INTO known_coins (
+      coin_pub,
+      denominations_serial,
+      age_commitment_hash,
+      denom_sig,
+      remaining
+      )
+      SELECT
+        ir.coin_pub,
+        dd.denominations_serial,
+        ir.age_commitment_hash,
+        ir.denom_sig,
+        dd.coin
+        FROM input_rows ir
+        JOIN dd
+          ON dd.denom_pub_hash = ir.denom_pub_hash
+          ON CONFLICT DO NOTHING
+          RETURNING known_coin_id
+      ),--kc
+       exists AS (
+       SELECT
+        CASE
+          WHEN ins.known_coin_id IS NOT NULL
+          THEN
+            FALSE
+          ELSE
+            TRUE
+        END AS existed,
+        ins.known_coin_id,
+        dd.denom_pub_hash,
+        kc.age_commitment_hash
+        FROM input_rows ir
+        LEFT JOIN ins
+          ON ins.coin_pub = ir.coin_pub
+        LEFT JOIN known_coins kc
+          ON kc.coin_pub = ir.coin_pub
+        LEFT JOIN dd
+          ON dd.denom_pub_hash = ir.denom_pub_hash
+       )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1
+FROM exists;
+
+RETURN;
+END $$;
+
+/*** Experiment using a loop ***/
+/*
+CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
+  IN in_coin_pub1 BYTEA,
+  IN in_denom_pub_hash1 TEXT,
+  IN in_h_age_commitment1 TEXT,
+  IN in_denom_sig1 TEXT,
+  IN in_coin_pub2 BYTEA,
+  IN in_denom_pub_hash2 TEXT,
+  IN in_h_age_commitment2 TEXT,
+  IN in_denom_sig2 TEXT,
+  OUT existed1 BOOLEAN,
+  OUT existed2 BOOLEAN,
+  OUT known_coin_id1 INT8,
+  OUT known_coin_id2 INT8,
+  OUT denom_pub_hash1 TEXT,
+  OUT denom_pub_hash2 TEXT,
+  OUT age_commitment_hash1 TEXT,
+  OUT age_commitment_hash2 TEXT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  ins_values RECORD;
+BEGIN
+  FOR i IN 1..2 LOOP
+    ins_values := (
+      SELECT
+        in_coin_pub1 AS coin_pub,
+        in_denom_pub_hash1 AS denom_pub_hash,
+        in_h_age_commitment1 AS age_commitment_hash,
+        in_denom_sig1 AS denom_sig
+      WHERE i = 1
+      UNION
+      SELECT
+        in_coin_pub2 AS coin_pub,
+        in_denom_pub_hash2 AS denom_pub_hash,
+        in_h_age_commitment2 AS age_commitment_hash,
+        in_denom_sig2 AS denom_sig
+      WHERE i = 2
+    );
+    WITH dd (denominations_serial, coin) AS (
+      SELECT denominations_serial, coin
+      FROM denominations
+      WHERE denom_pub_hash = ins_values.denom_pub_hash
+    ),
+    input_rows(coin_pub) AS (
+      VALUES (ins_values.coin_pub)
+    ),
+    ins AS (
+      INSERT INTO known_coins (
+        coin_pub,
+        denominations_serial,
+        age_commitment_hash,
+        denom_sig,
+        remaining
+      ) SELECT
+        input_rows.coin_pub,
+        dd.denominations_serial,
+        ins_values.age_commitment_hash,
+        ins_values.denom_sig,
+        coin
+      FROM dd
+      CROSS JOIN input_rows
+      ON CONFLICT DO NOTHING
+      RETURNING known_coin_id, denom_pub_hash
+    )
+    SELECT
+      CASE i
+        WHEN 1 THEN
+          COALESCE(ins.known_coin_id, 0) <> 0 AS existed1,
+          ins.known_coin_id AS known_coin_id1,
+          ins.denom_pub_hash AS denom_pub_hash1,
+          ins.age_commitment_hash AS age_commitment_hash1
+        WHEN 2 THEN
+          COALESCE(ins.known_coin_id, 0) <> 0 AS existed2,
+          ins.known_coin_id AS known_coin_id2,
+          ins.denom_pub_hash AS denom_pub_hash2,
+          ins.age_commitment_hash AS age_commitment_hash2
+      END
+    FROM ins;
+  END LOOP;
+END;
+$$;*/
diff --git a/src/exchangedb/exchange_do_batch_reserves_update.sql 
b/src/exchangedb/exchange_do_batch_reserves_update.sql
new file mode 100644
index 0000000..ebb58a1
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_reserves_update.sql
@@ -0,0 +1,72 @@
+--
+-- 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_batch_reserves_update(
+  IN in_reserve_pub BYTEA,
+  IN in_expiration_date INT8,
+  IN in_wire_ref INT8,
+  IN in_credit taler_amount,
+  IN in_exchange_account_name TEXT,
+  IN in_wire_source_h_payto BYTEA,
+  IN in_notify text,
+  OUT out_duplicate BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  INSERT INTO reserves_in
+    (reserve_pub
+    ,wire_reference
+    ,credit
+    ,exchange_account_section
+    ,wire_source_h_payto
+    ,execution_date)
+    VALUES
+    (in_reserve_pub
+    ,in_wire_ref
+    ,in_credit
+    ,in_exchange_account_name
+    ,in_wire_source_h_payto
+    ,in_expiration_date)
+    ON CONFLICT DO NOTHING;
+  IF FOUND
+  THEN
+    --IF THE INSERTION WAS A SUCCESS IT MEANS NO DUPLICATED TRANSACTION
+    out_duplicate = FALSE;
+    UPDATE reserves rs
+      SET
+         current_balance.frac = (rs.current_balance).frac+in_credit.frac
+           - CASE
+             WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+               THEN 100000000
+             ELSE 1
+             END
+        ,current_balance.val = (rs.current_balance).val+in_credit.val
+           + CASE
+             WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+               THEN 1
+             ELSE 0
+             END
+             ,expiration_date=GREATEST(expiration_date,in_expiration_date)
+             ,gc_date=GREATEST(gc_date,in_expiration_date)
+               WHERE reserve_pub=in_reserve_pub;
+    EXECUTE FORMAT (
+      'NOTIFY %s'
+      ,in_notify);
+  ELSE
+    out_duplicate = TRUE;
+  END IF;
+  RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_batch_withdraw.sql 
b/src/exchangedb/exchange_do_batch_withdraw.sql
new file mode 100644
index 0000000..b896e04
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw.sql
@@ -0,0 +1,123 @@
+--
+-- 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/>
+--
+-- @author Christian Grothoff
+-- @author Özgür Kesim
+
+CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
+  IN amount taler_amount,
+  IN rpub BYTEA,
+  IN now INT8,
+  IN min_reserve_gc INT8,
+  IN do_age_check BOOLEAN,
+  OUT reserve_found BOOLEAN,
+  OUT balance_ok BOOLEAN,
+  OUT age_ok BOOLEAN,
+  OUT allowed_maximum_age INT2, -- in years
+  OUT ruuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  reserve RECORD;
+  balance taler_amount;
+  not_before date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+--         reserves_out (INSERT, with CONFLICT detection) by wih
+--         reserves by reserve_pub (UPDATE)
+--         reserves_in by reserve_pub (SELECT)
+--         wire_targets by wire_target_h_payto
+
+
+SELECT *
+  INTO reserve
+  FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+  -- reserve unknown
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  age_ok=FALSE;
+  allowed_maximum_age=0;
+  ruuid=2;
+  RETURN;
+END IF;
+
+ruuid = reserve.reserve_uuid;
+
+-- Check if age requirements are present
+IF ((NOT do_age_check) OR (reserve.birthday = 0))
+THEN
+  age_ok = TRUE;
+  allowed_maximum_age = -1;
+ELSE
+  -- Age requirements are formally not met:  The exchange is setup to support
+  -- age restrictions (do_age_check == TRUE) and the reserve has a
+  -- birthday set (reserve_birthday != 0), but the client called the
+  -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it
+  -- should have.
+  not_before=date '1970-01-01' + reserve.birthday;
+  allowed_maximum_age = extract(year from age(current_date, not_before));
+
+  reserve_found=TRUE;
+  balance_ok=FALSE;
+  age_ok = FALSE;
+  RETURN;
+END IF;
+
+balance = reserve.current_balance;
+
+-- Check reserve balance is sufficient.
+IF (balance.val > amount.val)
+THEN
+  IF (balance.frac >= amount.frac)
+  THEN
+    balance.val=balance.val - amount.val;
+    balance.frac=balance.frac - amount.frac;
+  ELSE
+    balance.val=balance.val - amount.val - 1;
+    balance.frac=balance.frac + 100000000 - amount.frac;
+  END IF;
+ELSE
+  IF (balance.val = amount.val) AND (balance.frac >= amount.frac)
+  THEN
+    balance.val=0;
+    balance.frac=balance.frac - amount.frac;
+  ELSE
+    balance_ok=FALSE;
+    RETURN;
+  END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+  gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+  reserves.reserve_pub=rpub;
+
+reserve_found=TRUE;
+balance_ok=TRUE;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw(taler_amount, BYTEA, INT8, 
INT8, BOOLEAN)
+  IS 'Checks whether the reserve has sufficient balance for a withdraw 
operation (or the request is repeated and was previously approved) and that age 
requirements are formally met. If so updates the database with the result. 
Excludes storing the planchets.';
+
diff --git a/src/exchangedb/exchange_do_batch_withdraw_insert.sql 
b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
new file mode 100644
index 0000000..d36181a
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
@@ -0,0 +1,120 @@
+--
+-- 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_batch_withdraw_insert(
+  IN cs_nonce BYTEA,
+  IN amount taler_amount,
+  IN h_denom_pub BYTEA, -- FIXME: denom_serials should really be a parameter 
to this FUNCTION.
+  IN ruuid INT8,
+  IN reserve_sig BYTEA,
+  IN h_coin_envelope BYTEA,
+  IN denom_sig BYTEA,
+  IN now INT8,
+  OUT out_denom_unknown BOOLEAN,
+  OUT out_nonce_reuse BOOLEAN,
+  OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  denom_serial INT8;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+--         reserves_out (INSERT, with CONFLICT detection) by wih
+--         reserves by reserve_pub (UPDATE)
+--         reserves_in by reserve_pub (SELECT)
+--         wire_targets by wire_target_h_payto
+
+out_denom_unknown=TRUE;
+out_conflict=TRUE;
+out_nonce_reuse=TRUE;
+
+-- FIXME: denom_serials should really be a parameter to this FUNCTION.
+
+SELECT denominations_serial
+  INTO denom_serial
+  FROM exchange.denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+  -- denomination unknown, should be impossible!
+  out_denom_unknown=TRUE;
+  ASSERT false, 'denomination unknown';
+  RETURN;
+END IF;
+out_denom_unknown=FALSE;
+
+INSERT INTO exchange.reserves_out
+  (h_blind_ev
+  ,denominations_serial
+  ,denom_sig
+  ,reserve_uuid
+  ,reserve_sig
+  ,execution_date
+  ,amount_with_fee)
+VALUES
+  (h_coin_envelope
+  ,denom_serial
+  ,denom_sig
+  ,ruuid
+  ,reserve_sig
+  ,now
+  ,amount)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+  out_conflict=TRUE;
+  RETURN;
+END IF;
+out_conflict=FALSE;
+
+-- Special actions needed for a CS withdraw?
+out_nonce_reuse=FALSE;
+IF NOT NULL cs_nonce
+THEN
+  -- Cache CS signature to prevent replays in the future
+  -- (and check if cached signature exists at the same time).
+  INSERT INTO exchange.cs_nonce_locks
+    (nonce
+    ,max_denomination_serial
+    ,op_hash)
+  VALUES
+    (cs_nonce
+    ,denom_serial
+    ,h_coin_envelope)
+  ON CONFLICT DO NOTHING;
+
+  IF NOT FOUND
+  THEN
+    -- See if the existing entry is identical.
+    SELECT 1
+      FROM exchange.cs_nonce_locks
+     WHERE nonce=cs_nonce
+       AND op_hash=h_coin_envelope;
+    IF NOT FOUND
+    THEN
+      out_nonce_reuse=TRUE;
+      ASSERT false, 'nonce reuse attempted by client';
+      RETURN;
+    END IF;
+  END IF;
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw_insert(BYTEA, taler_amount, 
BYTEA, INT8, BYTEA, BYTEA, BYTEA, INT8)
+  IS 'Stores information about a planchet for a batch withdraw operation. 
Checks if the planchet already exists, and in that case indicates a conflict';
diff --git a/src/exchangedb/exchange_do_deposit.sql 
b/src/exchangedb/exchange_do_deposit.sql
new file mode 100644
index 0000000..1156c7d
--- /dev/null
+++ b/src/exchangedb/exchange_do_deposit.sql
@@ -0,0 +1,207 @@
+--
+-- 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 OR REPLACE FUNCTION exchange_do_deposit(
+  -- 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_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,
+  -- 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_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 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)
+
+
+-- First, get or create the 'wtsi'
+INSERT INTO wire_targets
+    (wire_target_h_payto
+    ,payto_uri)
+  VALUES
+    (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 wire_targets
+  WHERE
+    wire_target_h_payto=in_wire_target_h_payto;
+END IF;
+
+
+-- Second, create the batch_deposits entry
+INSERT INTO batch_deposits
+  (shard
+  ,merchant_pub
+  ,wallet_timestamp
+  ,exchange_timestamp
+  ,refund_deadline
+  ,wire_deadline
+  ,h_contract_terms
+  ,wallet_data_hash
+  ,wire_salt
+  ,wire_target_h_payto
+  ,policy_details_serial_id
+  ,policy_blocked
+  )
+  VALUES
+  (in_shard
+  ,in_merchant_pub
+  ,in_wallet_timestamp
+  ,in_exchange_timestamp
+  ,in_refund_deadline
+  ,in_wire_deadline
+  ,in_h_contract_terms
+  ,in_wallet_data_hash
+  ,in_wire_salt
+  ,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.
+  -- 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
+     ,batch_deposit_serial_id
+   INTO
+      out_exchange_timestamp
+     ,bdsi
+   FROM batch_deposits
+   WHERE shard=in_shard
+     AND merchant_pub=in_merchant_pub
+     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 *strange* differences. Not allowed.
+    out_conflict=TRUE;
+    RETURN;
+  END IF;
+END IF;
+
+out_conflict=FALSE;
+
+-- 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];
+
+  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;
+
+  IF FOUND
+  THEN
+    -- Insert did happen, update balance in known_coins!
+
+    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) ) );
+
+    IF NOT FOUND
+    THEN
+      -- Insufficient balance.
+      -- Note: C arrays are 0 indexed, but i started at 1
+      out_insufficient_balance_coin_index=i-1;
+      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
new file mode 100644
index 0000000..5758cb2
--- /dev/null
+++ b/src/exchangedb/exchange_do_gc.sql
@@ -0,0 +1,138 @@
+--
+-- 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 PROCEDURE exchange_do_gc(
+  IN in_ancient_date INT8,
+  IN in_now INT8)
+LANGUAGE plpgsql
+AS $$
+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
+  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
+
+DELETE FROM exchange.prewire
+  WHERE finished=TRUE;
+
+DELETE FROM exchange.wire_fee
+  WHERE end_date < in_ancient_date;
+
+-- TODO: use closing fee as threshold?
+DELETE FROM exchange.reserves
+  WHERE gc_date < in_now
+    AND current_balance = (0, 0);
+
+SELECT
+     reserve_out_serial_id
+  INTO
+     reserve_out_min
+  FROM exchange.reserves_out
+  ORDER BY reserve_out_serial_id ASC
+  LIMIT 1;
+
+DELETE FROM exchange.recoup
+  WHERE reserve_out_serial_id < reserve_out_min;
+-- FIXME: recoup_refresh lacks GC!
+
+SELECT
+     reserve_uuid
+  INTO
+     reserve_uuid_min
+  FROM exchange.reserves
+  ORDER BY reserve_uuid ASC
+  LIMIT 1;
+
+DELETE FROM exchange.reserves_out
+  WHERE reserve_uuid < reserve_uuid_min;
+
+-- FIXME: this query will be horribly slow;
+-- need to find another way to formulate it...
+DELETE FROM exchange.denominations
+  WHERE expire_legal < in_now
+    AND denominations_serial NOT IN
+      (SELECT DISTINCT denominations_serial
+         FROM exchange.reserves_out)
+    AND denominations_serial NOT IN
+      (SELECT DISTINCT denominations_serial
+         FROM exchange.known_coins
+        WHERE coin_pub IN
+          (SELECT DISTINCT coin_pub
+             FROM exchange.recoup))
+    AND denominations_serial NOT IN
+      (SELECT DISTINCT denominations_serial
+         FROM exchange.known_coins
+        WHERE coin_pub IN
+          (SELECT DISTINCT coin_pub
+             FROM exchange.recoup_refresh));
+
+SELECT
+     melt_serial_id
+  INTO
+     melt_min
+  FROM exchange.refresh_commitments
+  ORDER BY melt_serial_id ASC
+  LIMIT 1;
+
+DELETE FROM exchange.refresh_revealed_coins
+  WHERE melt_serial_id < melt_min;
+
+DELETE FROM exchange.refresh_transfer_keys
+  WHERE melt_serial_id < melt_min;
+
+SELECT
+     known_coin_id
+  INTO
+     coin_min
+  FROM exchange.known_coins
+  ORDER BY known_coin_id ASC
+  LIMIT 1;
+
+DELETE FROM exchange.batch_deposits
+  WHERE wire_deadline < in_ancient_date;
+
+SELECT
+     batch_deposit_serial_id
+  INTO
+     batch_deposit_min
+  FROM exchange.coin_deposits
+  ORDER BY batch_deposit_serial_id ASC
+  LIMIT 1;
+
+DELETE FROM exchange.refunds
+  WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM exchange.aggregation_tracking
+  WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM exchange.coin_deposits
+  WHERE batch_deposit_serial_id < batch_deposit_min;
+
+
+
+SELECT
+     denominations_serial
+  INTO
+     denom_min
+  FROM exchange.denominations
+  ORDER BY denominations_serial ASC
+  LIMIT 1;
+
+DELETE FROM exchange.cs_nonce_locks
+  WHERE max_denomination_serial <= denom_min;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_reserves_in_insert.sql 
b/src/exchangedb/exchange_do_reserves_in_insert.sql
new file mode 100644
index 0000000..d2347bd
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserves_in_insert.sql
@@ -0,0 +1,123 @@
+--
+-- 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 OR REPLACE FUNCTION exchange_do_array_reserves_insert(
+  IN in_gc_date INT8,
+  IN in_reserve_expiration INT8,
+  IN ina_reserve_pub BYTEA[],
+  IN ina_wire_ref INT8[],
+  IN ina_credit taler_amount[],
+  IN ina_exchange_account_name TEXT[],
+  IN ina_execution_date INT8[],
+  IN ina_wire_source_h_payto BYTEA[],
+  IN ina_payto_uri TEXT[],
+  IN ina_notify TEXT[])
+RETURNS SETOF exchange_do_array_reserve_insert_return_type
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  curs REFCURSOR;
+  conflict BOOL;
+  dup BOOL;
+  uuid INT8;
+  i INT4;
+  ini_reserve_pub BYTEA;
+  ini_wire_ref INT8;
+  ini_credit taler_amount;
+  ini_exchange_account_name TEXT;
+  ini_execution_date INT8;
+  ini_wire_source_h_payto BYTEA;
+  ini_payto_uri TEXT;
+  ini_notify TEXT;
+BEGIN
+
+  FOR i IN 1..array_length(ina_reserve_pub,1)
+  LOOP
+    ini_reserve_pub = ina_reserve_pub[i];
+    ini_wire_ref = ina_wire_ref[i];
+    ini_credit = ina_credit[i];
+    ini_exchange_account_name = ina_exchange_account_name[i];
+    ini_execution_date = ina_execution_date[i];
+    ini_wire_source_h_payto = ina_wire_source_h_payto[i];
+    ini_payto_uri = ina_payto_uri[i];
+    ini_notify = ina_notify[i];
+
+--    RAISE WARNING 'Starting loop on %', ini_notify;
+
+    INSERT INTO wire_targets
+      (wire_target_h_payto
+      ,payto_uri
+      ) VALUES (
+        ini_wire_source_h_payto
+       ,ini_payto_uri
+      )
+    ON CONFLICT DO NOTHING;
+
+    INSERT INTO reserves
+      (reserve_pub
+      ,current_balance
+      ,expiration_date
+      ,gc_date
+    ) VALUES (
+      ini_reserve_pub
+     ,ini_credit
+     ,in_reserve_expiration
+     ,in_gc_date
+    )
+    ON CONFLICT DO NOTHING
+    RETURNING reserve_uuid
+      INTO uuid;
+    conflict = NOT FOUND;
+
+    INSERT INTO reserves_in
+      (reserve_pub
+      ,wire_reference
+      ,credit
+      ,exchange_account_section
+      ,wire_source_h_payto
+      ,execution_date
+    ) VALUES (
+      ini_reserve_pub
+     ,ini_wire_ref
+     ,ini_credit
+     ,ini_exchange_account_name
+     ,ini_wire_source_h_payto
+     ,ini_execution_date
+    )
+    ON CONFLICT DO NOTHING;
+
+    IF NOT FOUND
+    THEN
+      IF conflict
+      THEN
+        dup = TRUE;
+      else
+        dup = FALSE;
+      END IF;
+    ELSE
+      IF NOT conflict
+      THEN
+        EXECUTE FORMAT (
+          'NOTIFY %s'
+          ,ini_notify);
+      END IF;
+      dup = FALSE;
+    END IF;
+    RETURN NEXT (dup,uuid);
+  END LOOP;
+  RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_withdraw.sql 
b/src/exchangedb/exchange_do_withdraw.sql
new file mode 100644
index 0000000..c25267b
--- /dev/null
+++ b/src/exchangedb/exchange_do_withdraw.sql
@@ -0,0 +1,213 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+-- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION exchange_do_withdraw(
+  IN cs_nonce BYTEA,
+  IN amount taler_amount,
+  IN h_denom_pub BYTEA,
+  IN rpub BYTEA,
+  IN reserve_sig BYTEA,
+  IN h_coin_envelope BYTEA,
+  IN denom_sig BYTEA,
+  IN now INT8,
+  IN min_reserve_gc INT8,
+  IN do_age_check BOOLEAN,
+  OUT reserve_found BOOLEAN,
+  OUT balance_ok BOOLEAN,
+  OUT nonce_ok BOOLEAN,
+  OUT age_ok BOOLEAN,
+  OUT allowed_maximum_age INT2, -- in years
+  OUT ruuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  reserve RECORD;
+  denom_serial INT8;
+  balance taler_amount;
+  not_before date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+--         reserves_out (INSERT, with CONFLICT detection) by wih
+--         reserves by reserve_pub (UPDATE)
+--         reserves_in by reserve_pub (SELECT)
+--         wire_targets by wire_target_h_payto
+
+SELECT denominations_serial
+  INTO denom_serial
+  FROM exchange.denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+  -- denomination unknown, should be impossible!
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  age_ok=FALSE;
+  allowed_maximum_age=0;
+  ruuid=0;
+  ASSERT false, 'denomination unknown';
+  RETURN;
+END IF;
+
+
+SELECT *
+  INTO reserve
+  FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+  -- reserve unknown
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  nonce_ok=TRUE;
+  age_ok=FALSE;
+  allowed_maximum_age=0;
+  ruuid=2;
+  RETURN;
+END IF;
+
+balance = reserve.current_balance;
+ruuid = reserve.reserve_uuid;
+
+-- Check if age requirements are present
+IF ((NOT do_age_check) OR (reserve.birthday = 0))
+THEN
+  age_ok = TRUE;
+  allowed_maximum_age = -1;
+ELSE
+  -- Age requirements are formally not met:  The exchange is setup to support
+  -- age restrictions (do_age_check == TRUE) and the reserve has a
+  -- birthday set (reserve_birthday != 0), but the client called the
+  -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it
+  -- should have.
+  not_before=date '1970-01-01' + reserve.birthday;
+  allowed_maximum_age = extract(year from age(current_date, not_before));
+
+  reserve_found=TRUE;
+  nonce_ok=TRUE; -- we do not really know
+  balance_ok=TRUE;-- we do not really know
+  age_ok = FALSE;
+  RETURN;
+END IF;
+
+-- We optimistically insert, and then on conflict declare
+-- the query successful due to idempotency.
+INSERT INTO exchange.reserves_out
+  (h_blind_ev
+  ,denominations_serial
+  ,denom_sig
+  ,reserve_uuid
+  ,reserve_sig
+  ,execution_date
+  ,amount_with_fee)
+VALUES
+  (h_coin_envelope
+  ,denom_serial
+  ,denom_sig
+  ,ruuid
+  ,reserve_sig
+  ,now
+  ,amount)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+  -- idempotent query, all constraints must be satisfied
+  reserve_found=TRUE;
+  balance_ok=TRUE;
+  nonce_ok=TRUE;
+  RETURN;
+END IF;
+
+-- Check reserve balance is sufficient.
+IF (balance.val > amount.val)
+THEN
+  IF (balance.frac >= amount.frac)
+  THEN
+    balance.val=balance.val - amount.val;
+    balance.frac=balance.frac - amount.frac;
+  ELSE
+    balance.val=balance.val - amount.val - 1;
+    balance.frac=balance.frac + 100000000 - amount.frac;
+  END IF;
+ELSE
+  IF (balance.val = amount.val) AND (balance.frac >= amount.frac)
+  THEN
+    balance.val=0;
+    balance.frac=balance.frac - amount.frac;
+  ELSE
+    reserve_found=TRUE;
+    nonce_ok=TRUE; -- we do not really know
+    balance_ok=FALSE;
+    RETURN;
+  END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+  gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+  reserves.reserve_pub=rpub;
+
+reserve_found=TRUE;
+balance_ok=TRUE;
+
+
+
+-- Special actions needed for a CS withdraw?
+IF NOT NULL cs_nonce
+THEN
+  -- Cache CS signature to prevent replays in the future
+  -- (and check if cached signature exists at the same time).
+  INSERT INTO exchange.cs_nonce_locks
+    (nonce
+    ,max_denomination_serial
+    ,op_hash)
+  VALUES
+    (cs_nonce
+    ,denom_serial
+    ,h_coin_envelope)
+  ON CONFLICT DO NOTHING;
+
+  IF NOT FOUND
+  THEN
+    -- See if the existing entry is identical.
+    SELECT 1
+      FROM exchange.cs_nonce_locks
+     WHERE nonce=cs_nonce
+       AND op_hash=h_coin_envelope;
+    IF NOT FOUND
+    THEN
+      reserve_found=FALSE;
+      balance_ok=FALSE;
+      nonce_ok=FALSE;
+      RETURN;
+    END IF;
+  END IF;
+ELSE
+  nonce_ok=TRUE; -- no nonce, hence OK!
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, taler_amount, BYTEA, BYTEA, 
BYTEA, BYTEA, BYTEA, INT8, INT8, BOOLEAN)
+  IS 'Checks whether the reserve has sufficient balance for a withdraw 
operation (or the request is repeated and was previously approved) and if the 
age requirements are formally met.  If so updates the database with the result';
diff --git a/src/exchangedb/exchange_do_withdraw.sql~ 
b/src/exchangedb/exchange_do_withdraw.sql~
new file mode 100644
index 0000000..243ef98
--- /dev/null
+++ b/src/exchangedb/exchange_do_withdraw.sql~
@@ -0,0 +1,197 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+-- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+--
+CREATE OR REPLACE FUNCTION exchange_do_withdraw(
+  IN cs_nonce BYTEA,
+  IN amount_val INT8,
+  IN amount_frac INT4,
+  IN h_denom_pub BYTEA,
+  IN rpub BYTEA,
+  IN reserve_sig BYTEA,
+  IN h_coin_envelope BYTEA,
+  IN denom_sig BYTEA,
+  IN now INT8,
+  IN min_reserve_gc INT8,
+  OUT reserve_found BOOLEAN,
+  OUT balance_ok BOOLEAN,
+  OUT nonce_ok BOOLEAN,
+  OUT ruuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+  reserve_gc INT8;
+DECLARE
+  denom_serial INT8;
+DECLARE
+  reserve_val INT8;
+DECLARE
+  reserve_frac INT4;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+--         reserves_out (INSERT, with CONFLICT detection) by wih
+--         reserves by reserve_pub (UPDATE)
+--         reserves_in by reserve_pub (SELECT)
+--         wire_targets by wire_target_h_payto
+
+SELECT denominations_serial
+  INTO denom_serial
+  FROM exchange.denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+  -- denomination unknown, should be impossible!
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  ruuid=0;
+  ASSERT false, 'denomination unknown';
+  RETURN;
+END IF;
+
+
+SELECT
+   current_balance_val
+  ,current_balance_frac
+  ,gc_date
+  ,reserve_uuid
+ INTO
+   reserve_val
+  ,reserve_frac
+  ,reserve_gc
+  ,ruuid
+  FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+  -- reserve unknown
+  reserve_found=FALSE;
+  balance_ok=FALSE;
+  nonce_ok=TRUE;
+  ruuid=2;
+  RETURN;
+END IF;
+
+-- We optimistically insert, and then on conflict declare
+-- the query successful due to idempotency.
+INSERT INTO exchange.reserves_out
+  (h_blind_ev
+  ,denominations_serial
+  ,denom_sig
+  ,reserve_uuid
+  ,reserve_sig
+  ,execution_date
+  ,amount_with_fee_val
+  ,amount_with_fee_frac)
+VALUES
+  (h_coin_envelope
+  ,denom_serial
+  ,denom_sig
+  ,ruuid
+  ,reserve_sig
+  ,now
+  ,amount_val
+  ,amount_frac)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+  -- idempotent query, all constraints must be satisfied
+  reserve_found=TRUE;
+  balance_ok=TRUE;
+  nonce_ok=TRUE;
+  RETURN;
+END IF;
+
+-- Check reserve balance is sufficient.
+IF (reserve_val > amount_val)
+THEN
+  IF (reserve_frac >= amount_frac)
+  THEN
+    reserve_val=reserve_val - amount_val;
+    reserve_frac=reserve_frac - amount_frac;
+  ELSE
+    reserve_val=reserve_val - amount_val - 1;
+    reserve_frac=reserve_frac + 100000000 - amount_frac;
+  END IF;
+ELSE
+  IF (reserve_val = amount_val) AND (reserve_frac >= amount_frac)
+  THEN
+    reserve_val=0;
+    reserve_frac=reserve_frac - amount_frac;
+  ELSE
+    reserve_found=TRUE;
+    nonce_ok=TRUE; -- we do not really know
+    balance_ok=FALSE;
+    RETURN;
+  END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve_gc);
+
+-- Update reserve balance.
+UPDATE reserves SET
+  gc_date=min_reserve_gc
+ ,current_balance_val=reserve_val
+ ,current_balance_frac=reserve_frac
+WHERE
+  reserves.reserve_pub=rpub;
+
+reserve_found=TRUE;
+balance_ok=TRUE;
+
+
+
+-- Special actions needed for a CS withdraw?
+IF NOT NULL cs_nonce
+THEN
+  -- Cache CS signature to prevent replays in the future
+  -- (and check if cached signature exists at the same time).
+  INSERT INTO exchange.cs_nonce_locks
+    (nonce
+    ,max_denomination_serial
+    ,op_hash)
+  VALUES
+    (cs_nonce
+    ,denom_serial
+    ,h_coin_envelope)
+  ON CONFLICT DO NOTHING;
+
+  IF NOT FOUND
+  THEN
+    -- See if the existing entry is identical.
+    SELECT 1
+      FROM exchange.cs_nonce_locks
+     WHERE nonce=cs_nonce
+       AND op_hash=h_coin_envelope;
+    IF NOT FOUND
+    THEN
+      reserve_found=FALSE;
+      balance_ok=FALSE;
+      nonce_ok=FALSE;
+      RETURN;
+    END IF;
+  END IF;
+ELSE
+  nonce_ok=TRUE; -- no nonce, hence OK!
+END IF;
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_withdraw(BYTEA, INT8, INT4, BYTEA, BYTEA, 
BYTEA, BYTEA, BYTEA, INT8, INT8)
+  IS 'Checks whether the reserve has sufficient balance for a withdraw 
operation (or the request is repeated and was previously approved) and if so 
updates the database with the result';
+
diff --git a/src/exchangedb/exchangedb-postgres.conf 
b/src/exchangedb/exchangedb-postgres.conf
new file mode 100644
index 0000000..e481940
--- /dev/null
+++ b/src/exchangedb/exchangedb-postgres.conf
@@ -0,0 +1,9 @@
+[exchangedb-postgres]
+CONFIG = "postgres:///taler"
+
+# Where are the SQL files to setup our tables?
+# Important: this MUST end with a "/"!
+SQL_DIR = $DATADIR/sql/exchange/
+
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1
\ No newline at end of file
diff --git a/src/exchangedb/exchangedb.conf b/src/exchangedb/exchangedb.conf
new file mode 100644
index 0000000..2bfcb2c
--- /dev/null
+++ b/src/exchangedb/exchangedb.conf
@@ -0,0 +1,36 @@
+# This file is in the public domain.
+#
+# Database-backend independent specification for the exchangedb module.
+#
+[exchangedb]
+# Where do we expect to find information about auditors?
+AUDITOR_BASE_DIR = ${TALER_DATA_HOME}/auditors/
+
+# Where do we expect to find information about wire transfer fees
+# for aggregate payments?  These are the amounts we charge (subtract)
+# the merchant per wire transfer.  The directory is expected to
+# contain files "$METHOD.fee" with the cost structure, where
+# $METHOD corresponds to a wire transfer method.
+WIREFEE_BASE_DIR = ${TALER_DATA_HOME}/exchange/wirefees/
+
+
+# After how long do we close idle reserves?  The exchange
+# and the auditor must agree on this value.  We currently
+# expect it to be globally defined for the whole system,
+# as there is no way for wallets to query this value.  Thus,
+# it is only configurable for testing, and should be treated
+# as constant in production.
+IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
+
+
+# After how long do we forget about reserves?  Should be above
+# the legal expiration timeframe of withdrawn coins.
+LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+
+# What is the desired delay between a transaction being ready and the
+# aggregator triggering on it?
+AGGREGATOR_SHIFT = 1 s
+
+# How many concurrent purses may be opened by a reserve
+# if the reserve is paid for a year?
+DEFAULT_PURSE_LIMIT = 1
\ No newline at end of file
diff --git a/src/exchangedb/exchangedb_plugin.c 
b/src/exchangedb/exchangedb_plugin.c
new file mode 100644
index 0000000..5dc41d9
--- /dev/null
+++ b/src/exchangedb/exchangedb_plugin.c
@@ -0,0 +1,73 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015 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/exchangedb_plugin.c
+ * @brief Logic to load database plugin
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#include "platform.h"
+#include "taler_exchangedb_plugin.h"
+#include <ltdl.h>
+
+
+struct TALER_EXCHANGEDB_Plugin *
+TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *plugin_name;
+  char *lib_name;
+  struct TALER_EXCHANGEDB_Plugin *plugin;
+
+  if (GNUNET_SYSERR ==
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             "exchange",
+                                             "db",
+                                             &plugin_name))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "db");
+    return NULL;
+  }
+  GNUNET_asprintf (&lib_name,
+                   "libtaler_plugin_exchangedb_%s",
+                   plugin_name);
+  GNUNET_free (plugin_name);
+  plugin = GNUNET_PLUGIN_load (lib_name,
+                               (void *) cfg);
+  if (NULL != plugin)
+    plugin->library_name = lib_name;
+  else
+    GNUNET_free (lib_name);
+  return plugin;
+}
+
+
+void
+TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin)
+{
+  char *lib_name;
+
+  if (NULL == plugin)
+    return;
+  lib_name = plugin->library_name;
+  GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
+                                               plugin));
+  GNUNET_free (lib_name);
+}
+
+
+/* end of exchangedb_plugin.c */
diff --git a/src/exchangedb/exchangedb_transactions.c 
b/src/exchangedb/exchangedb_transactions.c
new file mode 100644
index 0000000..f783937
--- /dev/null
+++ b/src/exchangedb/exchangedb_transactions.c
@@ -0,0 +1,190 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2017-2020 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/exchangedb_transactions.c
+ * @brief Logic to compute transaction totals of a transaction list for a coin
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGEDB_calculate_transaction_list_totals (
+  struct TALER_EXCHANGEDB_TransactionList *tl,
+  const struct TALER_Amount *off,
+  struct TALER_Amount *ret)
+{
+  struct TALER_Amount spent = *off;
+  struct TALER_Amount refunded;
+  struct TALER_Amount deposit_fee;
+  bool have_refund;
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (spent.currency,
+                                        &refunded));
+  have_refund = false;
+  for (struct TALER_EXCHANGEDB_TransactionList *pos = tl;
+       NULL != pos;
+       pos = pos->next)
+  {
+    switch (pos->type)
+    {
+    case TALER_EXCHANGEDB_TT_DEPOSIT:
+      /* spent += pos->amount_with_fee */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.deposit->amount_with_fee))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      deposit_fee = pos->details.deposit->deposit_fee;
+      break;
+    case TALER_EXCHANGEDB_TT_MELT:
+      /* spent += pos->amount_with_fee */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.melt->amount_with_fee))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_REFUND:
+      /* refunded += pos->refund_amount - pos->refund_fee */
+      if (0 >
+          TALER_amount_add (&refunded,
+                            &refunded,
+                            &pos->details.refund->refund_amount))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.refund->refund_fee))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      have_refund = true;
+      break;
+    case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+      /* refunded += pos->value */
+      if (0 >
+          TALER_amount_add (&refunded,
+                            &refunded,
+                            &pos->details.old_coin_recoup->value))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_RECOUP:
+      /* spent += pos->value */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.recoup->value))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+      /* spent += pos->value */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.recoup_refresh->value))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+      /* spent += pos->amount_with_fee */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.purse_deposit->amount))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      deposit_fee = pos->details.purse_deposit->deposit_fee;
+      break;
+    case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+      /* refunded += pos->refund_amount - pos->refund_fee */
+      if (0 >
+          TALER_amount_add (&refunded,
+                            &refunded,
+                            &pos->details.purse_refund->refund_amount))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.purse_refund->refund_fee))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      have_refund = true;
+      break;
+    case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+      /* spent += pos->amount_with_fee */
+      if (0 >
+          TALER_amount_add (&spent,
+                            &spent,
+                            &pos->details.reserve_open->coin_contribution))
+      {
+        GNUNET_break (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    }
+  }
+  if (have_refund)
+  {
+    /* If we gave any refund, also discount ONE deposit fee */
+    if (0 >
+        TALER_amount_add (&refunded,
+                          &refunded,
+                          &deposit_fee))
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  /* spent = spent - refunded */
+  if (0 >
+      TALER_amount_subtract (&spent,
+                             &spent,
+                             &refunded))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  *ret = spent;
+  return GNUNET_OK;
+}
diff --git a/src/exchangedb/list.txt b/src/exchangedb/list.txt
new file mode 100644
index 0000000..dc920f6
--- /dev/null
+++ b/src/exchangedb/list.txt
@@ -0,0 +1,20 @@
+0002-recoup.sql
+0002-reserves_out.sql
+exchange-0002.sql
+exchange_do_batch_withdraw_insert.sql
+exchange_do_batch_withdraw.sql
+exchange_do_gc.sql
+exchange_do_recoup_by_reserve.sql
+exchange_do_withdraw.sql
+pg_get_coin_transactions.c
+pg_get_reserve_by_h_blind.c
+pg_get_reserve_history.c
+pg_get_withdraw_info.c
+pg_insert_records_by_table.c
+pg_lookup_records_by_table.c
+pg_lookup_serial_by_table.c
+pg_select_recoup_above_serial_id.c
+pg_select_withdrawals_above_serial_id.c
+pg_select_withdraw_amounts_for_kyc_check.c
+procedures.sql
+shard-0001.sql
diff --git a/src/exchangedb/pg_activate_signing_key.c 
b/src/exchangedb/pg_activate_signing_key.c
new file mode 100644
index 0000000..fab2a0f
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.c
@@ -0,0 +1,58 @@
+/*
+   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_activate_signing_key.c
+ * @brief Implementation of the activate_signing_key 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_activate_signing_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam iparams[] = {
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_timestamp (&meta->start),
+    GNUNET_PQ_query_param_timestamp (&meta->expire_sign),
+    GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  PREPARE (pg,
+           "insert_signkey",
+           "INSERT INTO exchange_sign_keys "
+           "(exchange_pub"
+           ",valid_from"
+           ",expire_sign"
+           ",expire_legal"
+           ",master_sig"
+           ") VALUES "
+           "($1, $2, $3, $4, $5);");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_signkey",
+                                             iparams);
+}
diff --git a/src/exchangedb/pg_activate_signing_key.h 
b/src/exchangedb/pg_activate_signing_key.h
new file mode 100644
index 0000000..2d4df06
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.h
@@ -0,0 +1,44 @@
+/*
+   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_activate_signing_key.h
+ * @brief implementation of the activate_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ACTIVATE_SIGNING_KEY_H
+#define PG_ACTIVATE_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Add signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param meta meta data about @a exchange_pub
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_add_denomination_key.c 
b/src/exchangedb/pg_add_denomination_key.c
new file mode 100644
index 0000000..eb50304
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.c
@@ -0,0 +1,86 @@
+/*
+   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_add_denomination_key.c
+ * @brief Implementation of the add_denomination_key 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_add_denomination_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+  void *cls,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam iparams[] = {
+    GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+    TALER_PQ_query_param_denom_pub (denom_pub),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_timestamp (&meta->start),
+    GNUNET_PQ_query_param_timestamp (&meta->expire_withdraw),
+    GNUNET_PQ_query_param_timestamp (&meta->expire_deposit),
+    GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &meta->value),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &meta->fees.withdraw),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &meta->fees.deposit),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &meta->fees.refresh),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &meta->fees.refund),
+    GNUNET_PQ_query_param_uint32 (&meta->age_mask.bits),
+    GNUNET_PQ_query_param_end
+  };
+
+  /* Sanity check: ensure fees match coin currency */
+  GNUNET_assert (GNUNET_YES ==
+                 TALER_denom_fee_check_currency (meta->value.currency,
+                                                 &meta->fees));
+  PREPARE (pg,
+           "denomination_insert",
+           "INSERT INTO denominations "
+           "(denom_pub_hash"
+           ",denom_pub"
+           ",master_sig"
+           ",valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin"     /* value of this denom */
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",age_mask"
+           ") VALUES "
+           "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+           " $11, $12, $13);");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "denomination_insert",
+                                             iparams);
+}
diff --git a/src/exchangedb/pg_add_denomination_key.h 
b/src/exchangedb/pg_add_denomination_key.h
new file mode 100644
index 0000000..d131679
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.h
@@ -0,0 +1,46 @@
+/*
+   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_add_denomination_key.h
+ * @brief implementation of the add_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ADD_DENOMINATION_KEY_H
+#define PG_ADD_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Activate denomination key, turning it into a "current" or "valid"
+ * denomination key by adding the master signature.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param denom_pub the actual denomination key
+ * @param meta meta data about the denomination
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+  void *cls,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.c 
b/src/exchangedb/pg_batch_ensure_coin_known.c
new file mode 100644
index 0000000..e498130
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.c
@@ -0,0 +1,470 @@
+/*
+   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_batch_ensure_coin_known.c
+ * @brief Implementation of the batch_ensure_coin_known 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_batch_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+static enum GNUNET_DB_QueryStatus
+insert1 (struct PostgresClosure *pg,
+         const struct TALER_CoinPublicInfo coin[1],
+         struct TALER_EXCHANGEDB_CoinInfo result[1])
+{
+  enum GNUNET_DB_QueryStatus qs;
+  bool is_denom_pub_hash_null = false;
+  bool is_age_hash_null = false;
+  PREPARE (pg,
+           "batch1_known_coin",
+           "SELECT"
+           " existed1 AS existed"
+           ",known_coin_id1 AS known_coin_id"
+           ",denom_pub_hash1 AS denom_hash"
+           ",age_commitment_hash1 AS h_age_commitment"
+           " FROM exchange_do_batch1_known_coin"
+           "  ($1, $2, $3, $4);"
+           );
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("existed",
+                                &result[0].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  &result[0].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                            &result[0].denom_hash),
+      &is_denom_pub_hash_null),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            &result[0].h_age_commitment),
+      &is_age_hash_null),
+    GNUNET_PQ_result_spec_end
+  };
+
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "batch1_known_coin",
+                                                 params,
+                                                 rs);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    return qs;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_break (0); /* should be impossible */
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break; /* continued below */
+  }
+
+  if ( (! is_denom_pub_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].denom_hash,
+                            &coin->denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[0].denom_conflict = true;
+  }
+
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].h_age_commitment,
+                            &coin->h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[0].h_age_commitment));
+    GNUNET_break_op (0);
+    result[0].age_conflict = true;
+  }
+  return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert2 (struct PostgresClosure *pg,
+         const struct TALER_CoinPublicInfo coin[2],
+         struct TALER_EXCHANGEDB_CoinInfo result[2])
+{
+  enum GNUNET_DB_QueryStatus qs;
+  bool is_denom_pub_hash_null = false;
+  bool is_age_hash_null = false;
+  bool is_denom_pub_hash_null2 = false;
+  bool is_age_hash_null2 = false;
+
+  PREPARE (pg,
+           "batch2_known_coin",
+           "SELECT"
+           " existed1 AS existed"
+           ",known_coin_id1 AS known_coin_id"
+           ",denom_pub_hash1 AS denom_hash"
+           ",age_commitment_hash1 AS h_age_commitment"
+           ",existed2 AS existed2"
+           ",known_coin_id2 AS known_coin_id2"
+           ",denom_pub_hash2 AS denom_hash2"
+           ",age_commitment_hash2 AS h_age_commitment2"
+           " FROM exchange_do_batch2_known_coin"
+           "  ($1, $2, $3, $4, $5, $6, $7, $8);"
+           );
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("existed",
+                                &result[0].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  &result[0].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                            &result[0].denom_hash),
+      &is_denom_pub_hash_null),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            &result[0].h_age_commitment),
+      &is_age_hash_null),
+    GNUNET_PQ_result_spec_bool ("existed2",
+                                &result[1].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+                                  &result[1].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+                                            &result[1].denom_hash),
+      &is_denom_pub_hash_null2),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+                                            &result[1].h_age_commitment),
+      &is_age_hash_null2),
+    GNUNET_PQ_result_spec_end
+  };
+
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "batch2_known_coin",
+                                                 params,
+                                                 rs);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    return qs;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_break (0); /* should be impossible */
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break; /* continued below */
+  }
+
+  if ( (! is_denom_pub_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].denom_hash,
+                            &coin[0].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[0].denom_conflict = true;
+  }
+
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].h_age_commitment,
+                            &coin[0].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[0].h_age_commitment));
+    GNUNET_break_op (0);
+    result[0].age_conflict = true;
+  }
+  if ( (! is_denom_pub_hash_null2) &&
+       (0 != GNUNET_memcmp (&result[1].denom_hash,
+                            &coin[1].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[1].denom_conflict = true;
+  }
+
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (&result[1].h_age_commitment,
+                            &coin[1].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[1].h_age_commitment));
+    GNUNET_break_op (0);
+    result[1].age_conflict = true;
+  }
+  return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert4 (struct PostgresClosure *pg,
+         const struct TALER_CoinPublicInfo coin[4],
+         struct TALER_EXCHANGEDB_CoinInfo result[4])
+{
+  enum GNUNET_DB_QueryStatus qs;
+  bool is_denom_pub_hash_null = false;
+  bool is_age_hash_null = false;
+  bool is_denom_pub_hash_null2 = false;
+  bool is_age_hash_null2 = false;
+  bool is_denom_pub_hash_null3 = false;
+  bool is_age_hash_null3 = false;
+  bool is_denom_pub_hash_null4 = false;
+  bool is_age_hash_null4 = false;
+  PREPARE (pg,
+           "batch4_known_coin",
+           "SELECT"
+           " existed1 AS existed"
+           ",known_coin_id1 AS known_coin_id"
+           ",denom_pub_hash1 AS denom_hash"
+           ",age_commitment_hash1 AS h_age_commitment"
+           ",existed2 AS existed2"
+           ",known_coin_id2 AS known_coin_id2"
+           ",denom_pub_hash2 AS denom_hash2"
+           ",age_commitment_hash2 AS h_age_commitment2"
+           ",existed3 AS existed3"
+           ",known_coin_id3 AS known_coin_id3"
+           ",denom_pub_hash3 AS denom_hash3"
+           ",age_commitment_hash3 AS h_age_commitment3"
+           ",existed4 AS existed4"
+           ",known_coin_id4 AS known_coin_id4"
+           ",denom_pub_hash4 AS denom_hash4"
+           ",age_commitment_hash4 AS h_age_commitment4"
+           " FROM exchange_do_batch2_known_coin"
+           "  ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, 
$15, $16);"
+           );
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+    GNUNET_PQ_query_param_auto_from_type (&coin[2].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[2].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[2].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[2].denom_sig),
+
+    GNUNET_PQ_query_param_auto_from_type (&coin[3].coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin[3].denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&coin[3].h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin[3].denom_sig),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("existed",
+                                &result[0].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  &result[0].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                            &result[0].denom_hash),
+      &is_denom_pub_hash_null),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            &result[0].h_age_commitment),
+      &is_age_hash_null),
+    GNUNET_PQ_result_spec_bool ("existed2",
+                                &result[1].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+                                  &result[1].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+                                            &result[1].denom_hash),
+      &is_denom_pub_hash_null2),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+                                            &result[1].h_age_commitment),
+      &is_age_hash_null2),
+    GNUNET_PQ_result_spec_bool ("existed3",
+                                &result[2].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id3",
+                                  &result[2].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash3",
+                                            &result[2].denom_hash),
+      &is_denom_pub_hash_null3),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash3",
+                                            &result[2].h_age_commitment),
+      &is_age_hash_null3),
+    GNUNET_PQ_result_spec_bool ("existed4",
+                                &result[3].existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id4",
+                                  &result[3].known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash4",
+                                            &result[3].denom_hash),
+      &is_denom_pub_hash_null4),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash4",
+                                            &result[3].h_age_commitment),
+      &is_age_hash_null4),
+    GNUNET_PQ_result_spec_end
+  };
+
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "batch4_known_coin",
+                                                 params,
+                                                 rs);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    return qs;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return qs;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_break (0); /* should be impossible */
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    break; /* continued below */
+  }
+
+  if ( (! is_denom_pub_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].denom_hash,
+                            &coin[0].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[0].denom_conflict = true;
+  }
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (&result[0].h_age_commitment,
+                            &coin[0].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[0].h_age_commitment));
+    GNUNET_break_op (0);
+    result[0].age_conflict = true;
+  }
+
+  if ( (! is_denom_pub_hash_null2) &&
+       (0 != GNUNET_memcmp (&result[1].denom_hash,
+                            &coin[1].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[1].denom_conflict = true;
+  }
+  if ( (! is_age_hash_null2) &&
+       (0 != GNUNET_memcmp (&result[1].h_age_commitment,
+                            &coin[1].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[1].h_age_commitment));
+    GNUNET_break_op (0);
+    result[1].age_conflict = true;
+  }
+
+  if ( (! is_denom_pub_hash_null3) &&
+       (0 != GNUNET_memcmp (&result[2].denom_hash,
+                            &coin[2].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[2].denom_conflict = true;
+  }
+  if ( (! is_age_hash_null3) &&
+       (0 != GNUNET_memcmp (&result[2].h_age_commitment,
+                            &coin[2].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[2].h_age_commitment));
+    GNUNET_break_op (0);
+    result[2].age_conflict = true;
+  }
+
+  if ( (! is_denom_pub_hash_null4) &&
+       (0 != GNUNET_memcmp (&result[3].denom_hash,
+                            &coin[3].denom_pub_hash)) )
+  {
+    GNUNET_break_op (0);
+    result[3].denom_conflict = true;
+  }
+  if ( (! is_age_hash_null4) &&
+       (0 != GNUNET_memcmp (&result[3].h_age_commitment,
+                            &coin[3].h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (&result[3].h_age_commitment));
+    GNUNET_break_op (0);
+    result[3].age_conflict = true;
+  }
+  return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+  void *cls,
+  const struct TALER_CoinPublicInfo *coin,
+  struct TALER_EXCHANGEDB_CoinInfo *result,
+  unsigned int coin_length,
+  unsigned int batch_size)
+{
+  struct PostgresClosure *pg = cls;
+  enum GNUNET_DB_QueryStatus qs = 0;
+  unsigned int i = 0;
+
+  while ( (qs >= 0) &&
+          (i < coin_length) )
+  {
+    unsigned int bs = GNUNET_MIN (batch_size,
+                                  coin_length - i);
+    if (bs >= 4)
+    {
+      qs = insert4 (pg,
+                    &coin[i],
+                    &result[i]);
+      i += 4;
+      continue;
+    }
+    switch (bs)
+    {
+    case 3:
+    case 2:
+      qs = insert2 (pg,
+                    &coin[i],
+                    &result[i]);
+      i += 2;
+      break;
+    case 1:
+      qs = insert1 (pg,
+                    &coin[i],
+                    &result[i]);
+      i += 1;
+      break;
+    case 0:
+      GNUNET_assert (0);
+      break;
+    }
+  } /* end while */
+  if (qs < 0)
+    return qs;
+  return i;
+}
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.h 
b/src/exchangedb/pg_batch_ensure_coin_known.h
new file mode 100644
index 0000000..2c53676
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.h
@@ -0,0 +1,47 @@
+/*
+   This file is part of TALER
+   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
+   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_batch_ensure_coin_known.h
+ * @brief implementation of the batch_ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BATCH_ENSURE_COIN_KNOWN_H
+#define PG_BATCH_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Make sure the array of given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin array of coins that must be made known
+ * @param[out] result array where to store information about each coin
+ * @param coin_length length of the @a coin and @a result arraysf
+ * @param batch_size desired (maximum) batch size
+ * @return database transaction status, non-negative on success
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+  void *cls,
+  const struct TALER_CoinPublicInfo *coin,
+  struct TALER_EXCHANGEDB_CoinInfo *result,
+  unsigned int coin_length,
+  unsigned int batch_size);
+
+#endif
diff --git a/src/exchangedb/pg_commit.c b/src/exchangedb/pg_commit.c
new file mode 100644
index 0000000..8c4f87c
--- /dev/null
+++ b/src/exchangedb/pg_commit.c
@@ -0,0 +1,58 @@
+/*
+   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_commit.c
+ * @brief Implementation of the commit 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_commit.h"
+#include "pg_helper.h"
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_break (NULL != pg->transaction_name);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Committing transaction `%s'\n",
+              pg->transaction_name);
+  /* used in #postgres_commit */
+  PREPARE (pg,
+           "do_commit",
+           "COMMIT");
+
+  qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                           "do_commit",
+                                           params);
+  pg->transaction_name = NULL;
+  return qs;
+}
diff --git a/src/exchangedb/pg_commit.h b/src/exchangedb/pg_commit.h
new file mode 100644
index 0000000..b1f4f96
--- /dev/null
+++ b/src/exchangedb/pg_commit.h
@@ -0,0 +1,37 @@
+/*
+   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_commit.h
+ * @brief implementation of the commit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMMIT_H
+#define PG_COMMIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_count_known_coins.c 
b/src/exchangedb/pg_count_known_coins.c
new file mode 100644
index 0000000..872965a
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.c
@@ -0,0 +1,63 @@
+/*
+   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_count_known_coins.c
+ * @brief Implementation of the count_known_coins 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_count_known_coins.h"
+#include "pg_helper.h"
+
+long long
+TEH_PG_count_known_coins (void *cls,
+                          const struct
+                          TALER_DenominationHashP *denom_pub_hash)
+{
+  struct PostgresClosure *pg = cls;
+  uint64_t count;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_uint64 ("count",
+                                  &count),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+
+  PREPARE (pg,
+           "count_known_coins",
+           "SELECT"
+           " COUNT(*) AS count"
+           " FROM known_coins"
+           " WHERE denominations_serial="
+           "  (SELECT denominations_serial"
+           "    FROM denominations"
+           "    WHERE denom_pub_hash=$1);");
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "count_known_coins",
+                                                 params,
+                                                 rs);
+  if (0 > qs)
+    return (long long) qs;
+  return (long long) count;
+}
diff --git a/src/exchangedb/pg_count_known_coins.h 
b/src/exchangedb/pg_count_known_coins.h
new file mode 100644
index 0000000..69f07bd
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.h
@@ -0,0 +1,39 @@
+/*
+   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_count_known_coins.h
+ * @brief implementation of the count_known_coins function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COUNT_KNOWN_COINS_H
+#define PG_COUNT_KNOWN_COINS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Count the number of known coins by denomination.
+ *
+ * @param cls database connection plugin state
+ * @param denom_pub_hash denomination to count by
+ * @return number of coins if non-negative, otherwise an `enum 
GNUNET_DB_QueryStatus`
+ */
+long long
+TEH_PG_count_known_coins (void *cls,
+                          const struct
+                          TALER_DenominationHashP *denom_pub_hash);
+
+#endif
diff --git a/src/exchangedb/pg_create_tables.c 
b/src/exchangedb/pg_create_tables.c
new file mode 100644
index 0000000..1d5728d
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.c
@@ -0,0 +1,73 @@
+/*
+   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_create_tables.c
+ * @brief Implementation of the create_tables 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_create_tables.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+                      bool support_partitions,
+                      uint32_t num_partitions)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_Context *conn;
+  enum GNUNET_GenericReturnValue ret = GNUNET_OK;
+  struct GNUNET_PQ_QueryParam params[] = {
+    support_partitions
+    ? GNUNET_PQ_query_param_uint32 (&num_partitions)
+    : GNUNET_PQ_query_param_null (),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_PreparedStatement ps[] = {
+    GNUNET_PQ_make_prepare ("create_tables",
+                            "SELECT"
+                            " exchange.do_create_tables"
+                            " ($1);"),
+    GNUNET_PQ_PREPARED_STATEMENT_END
+  };
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+
+  conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+                                     "exchangedb-postgres",
+                                     "exchange-",
+                                     es,
+                                     ps);
+  if (NULL == conn)
+    return GNUNET_SYSERR;
+  if (0 >
+      GNUNET_PQ_eval_prepared_non_select (conn,
+                                          "create_tables",
+                                          params))
+    ret = GNUNET_SYSERR;
+  if (GNUNET_OK == ret)
+    ret = GNUNET_PQ_exec_sql (conn,
+                              "procedures");
+  GNUNET_PQ_disconnect (conn);
+  return ret;
+}
diff --git a/src/exchangedb/pg_create_tables.h 
b/src/exchangedb/pg_create_tables.h
new file mode 100644
index 0000000..58f5aae
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.h
@@ -0,0 +1,44 @@
+/*
+   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_create_tables.h
+ * @brief implementation of the create_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_CREATE_TABLES_H
+#define PG_CREATE_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Create the necessary tables if they are not present
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param support_partitions true to enable partitioning support (disables 
foreign key constraints)
+ * @param num_partitions number of partitions to create,
+ *     (0 to not actually use partitions, 1 to only
+ *      setup a default partition, >1 for real partitions)
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+                      bool support_partitions,
+                      uint32_t num_partitions);
+
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw.c 
b/src/exchangedb/pg_do_batch_withdraw.c
new file mode 100644
index 0000000..f89f327
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.c
@@ -0,0 +1,85 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.c
+ * @brief Implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+  void *cls,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *amount,
+  bool do_age_check,
+  bool *found,
+  bool *balance_ok,
+  bool *age_ok,
+  uint16_t *allowed_maximum_age,
+  uint64_t *ruuid)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Timestamp gc;
+  struct GNUNET_PQ_QueryParam params[] = {
+    TALER_PQ_query_param_amount (pg->conn,
+                                 amount),
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_timestamp (&now),
+    GNUNET_PQ_query_param_timestamp (&gc),
+    GNUNET_PQ_query_param_bool (do_age_check),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("reserve_found",
+                                found),
+    GNUNET_PQ_result_spec_bool ("balance_ok",
+                                balance_ok),
+    GNUNET_PQ_result_spec_bool ("age_ok",
+                                age_ok),
+    GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
+                                  allowed_maximum_age),
+    GNUNET_PQ_result_spec_uint64 ("ruuid",
+                                  ruuid),
+    GNUNET_PQ_result_spec_end
+  };
+
+  gc = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (now.abs_time,
+                              pg->legal_reserve_expiration_time));
+  PREPARE (pg,
+           "call_batch_withdraw",
+           "SELECT "
+           " reserve_found"
+           ",balance_ok"
+           ",age_ok"
+           ",allowed_maximum_age"
+           ",ruuid"
+           " FROM exchange_do_batch_withdraw"
+           " ($1,$2,$3,$4,$5);");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_batch_withdraw",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw.h 
b/src/exchangedb/pg_do_batch_withdraw.h
new file mode 100644
index 0000000..d0b8657
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.h
@@ -0,0 +1,57 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.h
+ * @brief implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_H
+#define PG_DO_BATCH_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform reserve update as part of a batch withdraw operation, checking
+ * for sufficient balance. Persisting the withdrawal details is done
+ * separately!
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param now current time (rounded)
+ * @param reserve_pub public key of the reserve to debit
+ * @param amount total amount to withdraw
+ * @param age_check_required if true, fail if age requirements are set on the 
reserve
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] age_ok set to true if no age requirements are present on the 
reserve
+ * @param[out] allowed_maximum_age if @e age_ok is false, set to the maximum 
allowed age when withdrawing from this reserve (client needs to call 
age-withdraw)
+ * @param[out] ruuid set to the reserve's UUID (reserves table row)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+  void *cls,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *amount,
+  bool age_check_required,
+  bool *found,
+  bool *balance_ok,
+  bool *age_ok,
+  uint16_t *allowed_maximum_age,
+  uint64_t *ruuid);
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.c 
b/src/exchangedb/pg_do_batch_withdraw_insert.c
new file mode 100644
index 0000000..b95a179
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.c
@@ -0,0 +1,77 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.c
+ * @brief Implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+  void *cls,
+  const struct TALER_CsNonce *nonce,
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  struct GNUNET_TIME_Timestamp now,
+  uint64_t ruuid,
+  bool *denom_unknown,
+  bool *conflict,
+  bool *nonce_reuse)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    NULL == nonce
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (nonce),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &collectable->amount_with_fee),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
+    GNUNET_PQ_query_param_uint64 (&ruuid),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+    TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
+    GNUNET_PQ_query_param_timestamp (&now),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("denom_unknown",
+                                denom_unknown),
+    GNUNET_PQ_result_spec_bool ("conflict",
+                                conflict),
+    GNUNET_PQ_result_spec_bool ("nonce_reuse",
+                                nonce_reuse),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "call_batch_withdraw_insert",
+           "SELECT "
+           " out_denom_unknown AS denom_unknown"
+           ",out_conflict AS conflict"
+           ",out_nonce_reuse AS nonce_reuse"
+           " FROM exchange_do_batch_withdraw_insert"
+           " ($1,$2,$3,$4,$5,$6,$7,$8);");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"call_batch_withdraw_insert",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.h 
b/src/exchangedb/pg_do_batch_withdraw_insert.h
new file mode 100644
index 0000000..6bc1a9a
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.h
@@ -0,0 +1,52 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.h
+ * @brief implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_INSERT_H
+#define PG_DO_BATCH_WITHDRAW_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform insert as part of a batch withdraw operation, and persisting the
+ * withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+ * @param collectable corresponding collectable coin (blind signature)
+ * @param now current time (rounded)
+ * @param ruuid reserve UUID
+ * @param[out] denom_unknown set if the denomination is unknown in the DB
+ * @param[out] conflict if the envelope was already in the DB
+ * @param[out] nonce_reuse if @a nonce was non-NULL and reused
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+  void *cls,
+  const struct TALER_CsNonce *nonce,
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  struct GNUNET_TIME_Timestamp now,
+  uint64_t ruuid,
+  bool *denom_unknown,
+  bool *conflict,
+  bool *nonce_reuse);
+
+#endif
diff --git a/src/exchangedb/pg_do_deposit.c b/src/exchangedb/pg_do_deposit.c
new file mode 100644
index 0000000..1e2d1c3
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.c
@@ -0,0 +1,116 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_deposit.c
+ * @brief Implementation of the do_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_do_deposit.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_deposit (
+  void *cls,
+  const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+  struct GNUNET_TIME_Timestamp *exchange_timestamp,
+  bool *balance_ok,
+  uint32_t *bad_balance_index,
+  bool *ctr_conflict)
+{
+  struct PostgresClosure *pg = cls;
+  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[] = {
+    /* data for batch_deposits */
+    GNUNET_PQ_query_param_uint64 (&deposit_shard),
+    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),
+    (bd->no_wallet_data_hash)
+    ? GNUNET_PQ_query_param_null ()
+    : 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_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",
+                                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;
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Do deposit %u = %s\n",
+                i,
+                TALER_B2S (&cdi->coin.coin_pub));
+  }
+  PREPARE (pg,
+           "call_deposit",
+           "SELECT "
+           " out_exchange_timestamp AS exchange_timestamp"
+           ",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);");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_deposit",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_do_deposit.h b/src/exchangedb/pg_do_deposit.h
new file mode 100644
index 0000000..449ec04
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.h
@@ -0,0 +1,51 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_deposit.h
+ * @brief implementation of the do_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_DEPOSIT_H
+#define PG_DO_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Perform deposit operation, checking for sufficient balance
+ * of the coins and possibly persisting the deposit details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @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_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_do_withdraw.c b/src/exchangedb/pg_do_withdraw.c
new file mode 100644
index 0000000..e32afcd
--- /dev/null
+++ b/src/exchangedb/pg_do_withdraw.c
@@ -0,0 +1,95 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_withdraw.c
+ * @brief Implementation of the do_withdraw function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_withdraw (
+  void *cls,
+  const struct TALER_CsNonce *nonce,
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  struct GNUNET_TIME_Timestamp now,
+  bool do_age_check,
+  bool *found,
+  bool *balance_ok,
+  bool *nonce_ok,
+  bool *age_ok,
+  uint16_t *allowed_maximum_age,
+  uint64_t *ruuid)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Timestamp gc;
+  struct GNUNET_PQ_QueryParam params[] = {
+    NULL == nonce
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (nonce),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &collectable->amount_with_fee),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+    GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+    TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
+    GNUNET_PQ_query_param_timestamp (&now),
+    GNUNET_PQ_query_param_timestamp (&gc),
+    GNUNET_PQ_query_param_bool (do_age_check),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("reserve_found",
+                                found),
+    GNUNET_PQ_result_spec_bool ("balance_ok",
+                                balance_ok),
+    GNUNET_PQ_result_spec_bool ("nonce_ok",
+                                nonce_ok),
+    GNUNET_PQ_result_spec_bool ("age_ok",
+                                age_ok),
+    GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
+                                  allowed_maximum_age),
+    GNUNET_PQ_result_spec_uint64 ("ruuid",
+                                  ruuid),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "call_withdraw",
+           "SELECT "
+           " reserve_found"
+           ",balance_ok"
+           ",nonce_ok"
+           ",age_ok"
+           ",allowed_maximum_age"
+           ",ruuid"
+           " FROM exchange_do_withdraw"
+           " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+  gc = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_add (now.abs_time,
+                              pg->legal_reserve_expiration_time));
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "call_withdraw",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_do_withdraw.h b/src/exchangedb/pg_do_withdraw.h
new file mode 100644
index 0000000..e771b1a
--- /dev/null
+++ b/src/exchangedb/pg_do_withdraw.h
@@ -0,0 +1,59 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_withdraw.h
+ * @brief implementation of the do_withdraw function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_WITHDRAW_H
+#define PG_DO_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Perform withdraw operation, checking for sufficient balance
+ * and possibly persisting the withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+ * @param[in,out] collectable corresponding collectable coin (blind signature) 
if a coin is found; possibly updated if a (different) signature exists already
+ * @param now current time (rounded)
+ * @param do_age_check set to true if age requirements must be verified
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] nonce_ok set to false if the nonce was reused
+ * @param[out] age_ok set to true if age requirements are met
+ * @param[out] allowed_maximum_age if @e age_ok is false, the maximum age (in 
years) that is allowed during age-withdraw
+ * @param[out] ruuid set to the reserve's UUID (reserves table row)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_withdraw (
+  void *cls,
+  const struct TALER_CsNonce *nonce,
+  const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+  struct GNUNET_TIME_Timestamp now,
+  bool do_age_check,
+  bool *found,
+  bool *balance_ok,
+  bool *nonce_ok,
+  bool *age_ok,
+  uint16_t *allowed_maximum_age,
+  uint64_t *ruuid);
+
+#endif
diff --git a/src/exchangedb/pg_drop_tables.c b/src/exchangedb/pg_drop_tables.c
new file mode 100644
index 0000000..5585701
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.c
@@ -0,0 +1,58 @@
+/*
+   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_drop_tables.c
+ * @brief Implementation of the drop_tables 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_drop_tables.h"
+#include "pg_helper.h"
+
+
+/**
+ * Drop all Taler tables.  This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_Context *conn;
+  enum GNUNET_GenericReturnValue ret;
+
+  if (NULL != pg->conn)
+  {
+    GNUNET_PQ_disconnect (pg->conn);
+    pg->conn = NULL;
+  }
+  conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+                                     "exchangedb-postgres",
+                                     NULL,
+                                     NULL,
+                                     NULL);
+  if (NULL == conn)
+    return GNUNET_SYSERR;
+  ret = GNUNET_PQ_exec_sql (conn,
+                            "drop");
+  GNUNET_PQ_disconnect (conn);
+  return ret;
+}
diff --git a/src/exchangedb/pg_drop_tables.h b/src/exchangedb/pg_drop_tables.h
new file mode 100644
index 0000000..58729d5
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.h
@@ -0,0 +1,38 @@
+/*
+   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_drop_tables.h
+ * @brief implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DROP_TABLES_H
+#define PG_DROP_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Drop all Taler tables.  This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_ensure_coin_known.c 
b/src/exchangedb/pg_ensure_coin_known.c
new file mode 100644
index 0000000..952acf2
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.c
@@ -0,0 +1,156 @@
+/*
+   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_ensure_coin_known.c
+ * @brief Implementation of the ensure_coin_known 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_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+                          const struct TALER_CoinPublicInfo *coin,
+                          uint64_t *known_coin_id,
+                          struct TALER_DenominationHashP *denom_hash,
+                          struct TALER_AgeCommitmentHash *h_age_commitment)
+{
+  struct PostgresClosure *pg = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  bool existed;
+  bool is_denom_pub_hash_null = false;
+  bool is_age_hash_null = false;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash),
+    coin->no_age_commitment
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_auto_from_type (&coin->h_age_commitment),
+    TALER_PQ_query_param_denom_sig (&coin->denom_sig),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_bool ("existed",
+                                &existed),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  known_coin_id),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                            denom_hash),
+      &is_denom_pub_hash_null),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            h_age_commitment),
+      &is_age_hash_null),
+    GNUNET_PQ_result_spec_end
+  };
+
+  /*
+     See also:
+     
https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
+  */
+  PREPARE (pg,
+           "insert_known_coin",
+           "WITH dd"
+           "  (denominations_serial"
+           "  ,coin"
+           "  ) AS ("
+           "    SELECT "
+           "       denominations_serial"
+           "      ,coin"
+           "        FROM denominations"
+           "        WHERE denom_pub_hash=$2"
+           "  ), input_rows"
+           "    (coin_pub) AS ("
+           "      VALUES ($1::BYTEA)"
+           "  ), ins AS ("
+           "  INSERT INTO known_coins "
+           "  (coin_pub"
+           "  ,denominations_serial"
+           "  ,age_commitment_hash"
+           "  ,denom_sig"
+           "  ,remaining"
+           "  ) SELECT "
+           "     $1"
+           "    ,denominations_serial"
+           "    ,$3"
+           "    ,$4"
+           "    ,coin"
+           "  FROM dd"
+           "  ON CONFLICT DO NOTHING" /* CONFLICT on (coin_pub) */
+           "  RETURNING "
+           "     known_coin_id"
+           "  ) "
+           "SELECT "
+           "   FALSE AS existed"
+           "  ,known_coin_id"
+           "  ,NULL AS denom_pub_hash"
+           "  ,NULL AS age_commitment_hash"
+           "  FROM ins "
+           "UNION ALL "
+           "SELECT "
+           "   TRUE AS existed"
+           "  ,known_coin_id"
+           "  ,denom_pub_hash"
+           "  ,kc.age_commitment_hash"
+           "  FROM input_rows"
+           "  JOIN known_coins kc USING (coin_pub)"
+           "  JOIN denominations USING (denominations_serial)"
+           "  LIMIT 1");
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "insert_known_coin",
+                                                 params,
+                                                 rs);
+  switch (qs)
+  {
+  case GNUNET_DB_STATUS_HARD_ERROR:
+    GNUNET_break (0);
+    return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+  case GNUNET_DB_STATUS_SOFT_ERROR:
+    return TALER_EXCHANGEDB_CKS_SOFT_FAIL;
+  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+    GNUNET_break (0); /* should be impossible */
+    return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+    if (! existed)
+      return TALER_EXCHANGEDB_CKS_ADDED;
+    break; /* continued below */
+  }
+
+  if ( (! is_denom_pub_hash_null) &&
+       (0 != GNUNET_memcmp (&denom_hash->hash,
+                            &coin->denom_pub_hash.hash)) )
+  {
+    GNUNET_break_op (0);
+    return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
+  }
+
+  if ( (! is_age_hash_null) &&
+       (0 != GNUNET_memcmp (h_age_commitment,
+                            &coin->h_age_commitment)) )
+  {
+    GNUNET_break (GNUNET_is_zero (h_age_commitment));
+    GNUNET_break_op (0);
+    return TALER_EXCHANGEDB_CKS_AGE_CONFLICT;
+  }
+
+  return TALER_EXCHANGEDB_CKS_PRESENT;
+}
diff --git a/src/exchangedb/pg_ensure_coin_known.h 
b/src/exchangedb/pg_ensure_coin_known.h
new file mode 100644
index 0000000..68c1017
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.h
@@ -0,0 +1,45 @@
+/*
+   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_ensure_coin_known.h
+ * @brief implementation of the ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ENSURE_COIN_KNOWN_H
+#define PG_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Make sure the given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin the coin that must be made known
+ * @param[out] known_coin_id set to the unique row of the coin
+ * @param[out] denom_hash set to the denomination hash of the existing
+ *             coin (for conflict error reporting)
+ * @param[out] h_age_commitment  set to the conflicting age commitment hash on 
conflict
+ * @return database transaction status, non-negative on success
+ */
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+                          const struct TALER_CoinPublicInfo *coin,
+                          uint64_t *known_coin_id,
+                          struct TALER_DenominationHashP *denom_hash,
+                          struct TALER_AgeCommitmentHash *h_age_commitment);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen.c b/src/exchangedb/pg_event_listen.c
new file mode 100644
index 0000000..6e1d328
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.c
@@ -0,0 +1,53 @@
+/*
+   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_event_listen.c
+ * @brief Implementation of the event_listen 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_event_listen.h"
+#include "pg_helper.h"
+
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ *         multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+                     struct GNUNET_TIME_Relative timeout,
+                     const struct GNUNET_DB_EventHeaderP *es,
+                     GNUNET_DB_EventCallback cb,
+                     void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+
+  return GNUNET_PQ_event_listen (pg->conn,
+                                 es,
+                                 timeout,
+                                 cb,
+                                 cb_cls);
+}
diff --git a/src/exchangedb/pg_event_listen.h b/src/exchangedb/pg_event_listen.h
new file mode 100644
index 0000000..7e1e83a
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.h
@@ -0,0 +1,45 @@
+/*
+   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_event_listen.h
+ * @brief implementation of the event_listen function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_H
+#define PG_EVENT_LISTEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ *         multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+                     struct GNUNET_TIME_Relative timeout,
+                     const struct GNUNET_DB_EventHeaderP *es,
+                     GNUNET_DB_EventCallback cb,
+                     void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen_cancel.c 
b/src/exchangedb/pg_event_listen_cancel.c
new file mode 100644
index 0000000..9d77668
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.c
@@ -0,0 +1,36 @@
+/*
+   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_event_listen_cancel.c
+ * @brief Implementation of the event_listen_cancel 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_event_listen_cancel.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_listen_cancel (void *cls,
+                            struct GNUNET_DB_EventHandler *eh)
+
+{
+  (void) cls;
+  GNUNET_PQ_event_listen_cancel (eh);
+}
diff --git a/src/exchangedb/pg_event_listen_cancel.h 
b/src/exchangedb/pg_event_listen_cancel.h
new file mode 100644
index 0000000..258d7a5
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.h
@@ -0,0 +1,38 @@
+/*
+   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_event_listen_cancel.h
+ * @brief implementation of the event_listen_cancel function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_CANCEL_H
+#define PG_EVENT_LISTEN_CANCEL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Stop notifications.
+ *
+ * @param cls the plugin's `struct PostgresClosure`
+ * @param eh handle to unregister.
+ */
+void
+TEH_PG_event_listen_cancel (void *cls,
+                            struct GNUNET_DB_EventHandler *eh);
+#endif
diff --git a/src/exchangedb/pg_event_notify.c b/src/exchangedb/pg_event_notify.c
new file mode 100644
index 0000000..1888557
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.c
@@ -0,0 +1,41 @@
+/*
+   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_event_notify.c
+ * @brief Implementation of the event_notify 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_event_notify.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_notify (void *cls,
+                     const struct GNUNET_DB_EventHeaderP *es,
+                     const void *extra,
+                     size_t extra_size)
+{
+  struct PostgresClosure *pg = cls;
+
+  GNUNET_PQ_event_notify (pg->conn,
+                          es,
+                          extra,
+                          extra_size);
+}
diff --git a/src/exchangedb/pg_event_notify.h b/src/exchangedb/pg_event_notify.h
new file mode 100644
index 0000000..8506965
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.h
@@ -0,0 +1,42 @@
+/*
+   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_event_notify.h
+ * @brief implementation of the event_notify function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_NOTIFY_H
+#define PG_EVENT_NOTIFY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+void
+TEH_PG_event_notify (void *cls,
+                     const struct GNUNET_DB_EventHeaderP *es,
+                     const void *extra,
+                     size_t extra_size);
+
+#endif
diff --git a/src/exchangedb/pg_gc.c b/src/exchangedb/pg_gc.c
new file mode 100644
index 0000000..e01c1e1
--- /dev/null
+++ b/src/exchangedb/pg_gc.c
@@ -0,0 +1,80 @@
+/*
+   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_gc.c
+ * @brief Implementation of the gc 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_gc.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+  struct GNUNET_TIME_Absolute long_ago;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_absolute_time (&long_ago),
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_Context *conn;
+  enum GNUNET_GenericReturnValue ret;
+
+  /* Keep wire fees for 10 years, that should always
+     be enough _and_ they are tiny so it does not
+     matter to make this tight */
+  long_ago = GNUNET_TIME_absolute_subtract (
+    now,
+    GNUNET_TIME_relative_multiply (
+      GNUNET_TIME_UNIT_YEARS,
+      10));
+  {
+    struct GNUNET_PQ_ExecuteStatement es[] = {
+      GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+      GNUNET_PQ_EXECUTE_STATEMENT_END
+    };
+    struct GNUNET_PQ_PreparedStatement ps[] = {
+      /* Used in #postgres_gc() */
+      GNUNET_PQ_make_prepare ("run_gc",
+                              "CALL"
+                              " exchange_do_gc"
+                              " ($1,$2);"),
+      GNUNET_PQ_PREPARED_STATEMENT_END
+    };
+
+    conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+                                       "exchangedb-postgres",
+                                       NULL,
+                                       es,
+                                       ps);
+  }
+  if (NULL == conn)
+    return GNUNET_SYSERR;
+  ret = GNUNET_OK;
+  if (0 > GNUNET_PQ_eval_prepared_non_select (conn,
+                                              "run_gc",
+                                              params))
+    ret = GNUNET_SYSERR;
+  GNUNET_PQ_disconnect (conn);
+  return ret;
+}
diff --git a/src/exchangedb/pg_gc.h b/src/exchangedb/pg_gc.h
new file mode 100644
index 0000000..8035814
--- /dev/null
+++ b/src/exchangedb/pg_gc.h
@@ -0,0 +1,39 @@
+/*
+   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_gc.h
+ * @brief implementation of the gc function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GC_H
+#define PG_GC_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_coin_denomination.c 
b/src/exchangedb/pg_get_coin_denomination.c
new file mode 100644
index 0000000..9f9256f
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.c
@@ -0,0 +1,69 @@
+/*
+   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_get_coin_denomination.c
+ * @brief Implementation of the get_coin_denomination 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_get_coin_denomination.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t *known_coin_id,
+  struct TALER_DenominationHashP *denom_hash)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                          denom_hash),
+    GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+                                  known_coin_id),
+    GNUNET_PQ_result_spec_end
+  };
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Getting coin denomination of coin %s\n",
+              TALER_B2S (coin_pub));
+
+  /* Used in #postgres_get_coin_denomination() to fetch
+     the denomination public key hash for
+     a coin known to the exchange. */
+  PREPARE (pg,
+           "get_coin_denomination",
+           "SELECT"
+           " denominations.denom_pub_hash"
+           ",known_coin_id"
+           " FROM known_coins"
+           " JOIN denominations USING (denominations_serial)"
+           " WHERE coin_pub=$1"
+           " FOR SHARE;");
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_coin_denomination",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_coin_denomination.h 
b/src/exchangedb/pg_get_coin_denomination.h
new file mode 100644
index 0000000..3b9f03f
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.h
@@ -0,0 +1,43 @@
+/*
+   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_get_coin_denomination.h
+ * @brief implementation of the get_coin_denomination function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_DENOMINATION_H
+#define PG_GET_COIN_DENOMINATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the denomination of a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param[out] known_coin_id set to the ID of the coin in the known_coins table
+ * @param[out] denom_hash where to store the hash of the coins denomination
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t *known_coin_id,
+  struct TALER_DenominationHashP *denom_hash);
+
+#endif
diff --git a/src/exchangedb/pg_get_coin_transactions.c 
b/src/exchangedb/pg_get_coin_transactions.c
new file mode 100644
index 0000000..5deb3fd
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.c
@@ -0,0 +1,948 @@
+/*
+   This file is part of TALER
+   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
+   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 pg_get_coin_transactions.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_helper.h"
+#include "plugin_exchangedb_common.h"
+
+
+/**
+ * Closure for callbacks called from #postgres_get_coin_transactions()
+ */
+struct CoinHistoryContext
+{
+  /**
+   * Head of the coin's history list.
+   */
+  struct TALER_EXCHANGEDB_TransactionList *head;
+
+  /**
+   * Public key of the coin we are building the history for.
+   */
+  const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+  /**
+   * Closure for all callbacks of this database plugin.
+   */
+  void *db_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Set to 'true' if the transaction failed.
+   */
+  bool failed;
+
+  /**
+   * Set to 'true' if we found a deposit or melt (for invariant check).
+   */
+  bool have_deposit_or_melt;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_deposit (void *cls,
+                  PGresult *result,
+                  unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_DepositListEntry *deposit;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    chc->have_deposit_or_melt = true;
+    deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                     &deposit->amount_with_fee),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                     &deposit->deposit_fee),
+        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                              &deposit->h_denom_pub),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                                &deposit->h_age_commitment),
+          &deposit->no_age_commitment),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
+                                                &deposit->wallet_data_hash),
+          &deposit->no_wallet_data_hash),
+        GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+                                         &deposit->timestamp),
+        GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+                                         &deposit->refund_deadline),
+        GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+                                         &deposit->wire_deadline),
+        GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+                                              &deposit->merchant_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+                                              &deposit->h_contract_terms),
+        GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+                                              &deposit->wire_salt),
+        GNUNET_PQ_result_spec_string ("payto_uri",
+                                      &deposit->receiver_wire_account),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &deposit->csig),
+        GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_auto_from_type ("done",
+                                              &deposit->done),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (deposit);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
+    tl->details.deposit = deposit;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_deposit (void *cls,
+                        PGresult *result,
+                        unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    chc->have_deposit_or_melt = true;
+    deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
+    {
+      bool not_finished;
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                     &deposit->amount),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                     &deposit->deposit_fee),
+        GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+                                              &deposit->purse_pub),
+        GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_string ("partner_base_url",
+                                        &deposit->exchange_base_url),
+          NULL),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &deposit->coin_sig),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                                &deposit->h_age_commitment),
+          &deposit->no_age_commitment),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_bool ("refunded",
+                                      &deposit->refunded),
+          &not_finished),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (deposit);
+        chc->failed = true;
+        return;
+      }
+      if (not_finished)
+        deposit->refunded = false;
+      deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment);
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
+    tl->details.purse_deposit = deposit;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_melt (void *cls,
+               PGresult *result,
+               unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_MeltListEntry *melt;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    chc->have_deposit_or_melt = true;
+    melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("rc",
+                                              &melt->rc),
+        /* oldcoin_index not needed */
+        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                              &melt->h_denom_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+                                              &melt->coin_sig),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                     &melt->amount_with_fee),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+                                     &melt->melt_fee),
+        GNUNET_PQ_result_spec_allow_null (
+          GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                                &melt->h_age_commitment),
+          &melt->no_age_commitment),
+        GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (melt);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_MELT;
+    tl->details.melt = melt;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_refund (void *cls,
+                 PGresult *result,
+                 unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_RefundListEntry *refund;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+                                              &refund->merchant_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+                                              &refund->merchant_sig),
+        GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+                                              &refund->h_contract_terms),
+        GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+                                      &refund->rtransaction_id),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                     &refund->refund_amount),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                     &refund->refund_fee),
+        GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (refund);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_REFUND;
+    tl->details.refund = refund;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_decision (void *cls,
+                         PGresult *result,
+                         unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+                                              &prefund->purse_pub),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                     &prefund->refund_amount),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                     &prefund->refund_fee),
+        GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (prefund);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
+    tl->details.purse_refund = prefund;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_old_coin_recoup (void *cls,
+                     PGresult *result,
+                     unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+                                              &recoup->coin.coin_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &recoup->coin_sig),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+                                              &recoup->coin_blind),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                     &recoup->value),
+        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+                                         &recoup->timestamp),
+        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                              &recoup->coin.denom_pub_hash),
+        TALER_PQ_result_spec_denom_sig ("denom_sig",
+                                        &recoup->coin.denom_sig),
+        GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (recoup);
+        chc->failed = true;
+        return;
+      }
+      recoup->old_coin_pub = *chc->coin_pub;
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
+    tl->details.old_coin_recoup = recoup;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup (void *cls,
+                 PGresult *result,
+                 unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                              &recoup->reserve_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &recoup->coin_sig),
+        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                              &recoup->h_denom_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+                                              &recoup->coin_blind),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                     &recoup->value),
+        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+                                         &recoup->timestamp),
+        GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (recoup);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_RECOUP;
+    tl->details.recoup = recoup;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup_refresh (void *cls,
+                         PGresult *result,
+                         unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+                                              &recoup->old_coin_pub),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &recoup->coin_sig),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+                                              &recoup->coin_blind),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+                                     &recoup->value),
+        GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+                                         &recoup->timestamp),
+        GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                              &recoup->coin.denom_pub_hash),
+        TALER_PQ_result_spec_denom_sig ("denom_sig",
+                                        &recoup->coin.denom_sig),
+        GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (recoup);
+        chc->failed = true;
+        return;
+      }
+      recoup->coin.coin_pub = *chc->coin_pub;
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
+    tl->details.recoup_refresh = recoup;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_reserve_open (void *cls,
+                       PGresult *result,
+                       unsigned int num_results)
+{
+  struct CoinHistoryContext *chc = cls;
+  struct PostgresClosure *pg = chc->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+    struct TALER_EXCHANGEDB_TransactionList *tl;
+    uint64_t serial_id;
+
+    role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
+    {
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+                                              &role->reserve_sig),
+        GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+                                              &role->coin_sig),
+        TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
+                                     &role->coin_contribution),
+        GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
+                                      &serial_id),
+        GNUNET_PQ_result_spec_end
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_PQ_extract_result (result,
+                                    rs,
+                                    i))
+      {
+        GNUNET_break (0);
+        GNUNET_free (role);
+        chc->failed = true;
+        return;
+      }
+    }
+    tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+    tl->next = chc->head;
+    tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
+    tl->details.reserve_open = role;
+    tl->serial_id = serial_id;
+    chc->head = tl;
+  }
+}
+
+
+/**
+ * Work we need to do.
+ */
+struct Work
+{
+  /**
+   * SQL prepared statement name.
+   */
+  const char *statement;
+
+  /**
+   * Function to call to handle the result(s).
+   */
+  GNUNET_PQ_PostgresResultHandler cb;
+};
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_EXCHANGEDB_TransactionList **tlp)
+{
+  struct PostgresClosure *pg = cls;
+  static const struct Work work[] = {
+    /** #TALER_EXCHANGEDB_TT_DEPOSIT */
+    { "get_deposit_with_coin_pub",
+      &add_coin_deposit },
+    /** #TALER_EXCHANGEDB_TT_MELT */
+    { "get_refresh_session_by_coin",
+      &add_coin_melt },
+    /** #TALER_EXCHANGEDB_TT_PURSE_DEPOSIT */
+    { "get_purse_deposit_by_coin_pub",
+      &add_coin_purse_deposit },
+    /** #TALER_EXCHANGEDB_TT_PURSE_REFUND */
+    { "get_purse_decision_by_coin_pub",
+      &add_coin_purse_decision },
+    /** #TALER_EXCHANGEDB_TT_REFUND */
+    { "get_refunds_by_coin",
+      &add_coin_refund },
+    /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */
+    { "recoup_by_old_coin",
+      &add_old_coin_recoup },
+    /** #TALER_EXCHANGEDB_TT_RECOUP */
+    { "recoup_by_coin",
+      &add_coin_recoup },
+    /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */
+    { "recoup_by_refreshed_coin",
+      &add_coin_recoup_refresh },
+    /** #TALER_EXCHANGEDB_TT_RESERVE_OPEN */
+    { "reserve_open_by_coin",
+      &add_coin_reserve_open },
+    { NULL, NULL }
+  };
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  struct CoinHistoryContext chc = {
+    .head = NULL,
+    .coin_pub = coin_pub,
+    .pg = pg,
+    .db_cls = cls
+  };
+
+  PREPARE (pg,
+           "get_deposit_with_coin_pub",
+           "SELECT"
+           " cdep.amount_with_fee"
+           ",denoms.fee_deposit"
+           ",denoms.denom_pub_hash"
+           ",kc.age_commitment_hash"
+           ",bdep.wallet_timestamp"
+           ",bdep.refund_deadline"
+           ",bdep.wire_deadline"
+           ",bdep.merchant_pub"
+           ",bdep.h_contract_terms"
+           ",bdep.wallet_data_hash"
+           ",bdep.wire_salt"
+           ",wt.payto_uri"
+           ",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 = cdep.coin_pub)"
+           " JOIN denominations denoms"
+           "   USING (denominations_serial)"
+           " WHERE cdep.coin_pub=$1;");
+  PREPARE (pg,
+           "get_refresh_session_by_coin",
+           "SELECT"
+           " rc"
+           ",old_coin_sig"
+           ",amount_with_fee"
+           ",denoms.denom_pub_hash"
+           ",denoms.fee_refresh"
+           ",kc.age_commitment_hash"
+           ",melt_serial_id"
+           " FROM refresh_commitments"
+           " JOIN known_coins kc"
+           "   ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+           " JOIN denominations denoms"
+           "   USING (denominations_serial)"
+           " WHERE old_coin_pub=$1;");
+  PREPARE (pg,
+           "get_purse_deposit_by_coin_pub",
+           "SELECT"
+           " partner_base_url"
+           ",pd.amount_with_fee"
+           ",denoms.fee_deposit"
+           ",pd.purse_pub"
+           ",kc.age_commitment_hash"
+           ",pd.coin_sig"
+           ",pd.purse_deposit_serial_id"
+           ",pdes.refunded"
+           " FROM purse_deposits pd"
+           " LEFT JOIN partners"
+           "   USING (partner_serial_id)"
+           " JOIN purse_requests pr"
+           "   USING (purse_pub)"
+           " LEFT JOIN purse_decision pdes"
+           "   USING (purse_pub)"
+           " JOIN known_coins kc"
+           "   ON (pd.coin_pub = kc.coin_pub)"
+           " JOIN denominations denoms"
+           "   USING (denominations_serial)"
+           // FIXME: use to-be-created materialized index
+           // on coin_pub (query crosses partitions!)
+           " WHERE pd.coin_pub=$1;");
+  PREPARE (pg,
+           "get_refunds_by_coin",
+           "SELECT"
+           " bdep.merchant_pub"
+           ",ref.merchant_sig"
+           ",bdep.h_contract_terms"
+           ",ref.rtransaction_id"
+           ",ref.amount_with_fee"
+           ",denom.fee_refund"
+           ",ref.refund_serial_id"
+           " FROM refunds ref"
+           " 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"
+           "   USING (denominations_serial)"
+           " WHERE ref.coin_pub=$1;");
+  PREPARE (pg,
+           "get_purse_decision_by_coin_pub",
+           "SELECT"
+           " pdes.purse_pub"
+           ",pd.amount_with_fee"
+           ",denom.fee_refund"
+           ",pdes.purse_decision_serial_id"
+           " FROM purse_deposits pd"
+           " JOIN purse_decision pdes"
+           "   USING (purse_pub)"
+           " JOIN known_coins kc"
+           "   ON (pd.coin_pub = kc.coin_pub)"
+           " JOIN denominations denom"
+           "   USING (denominations_serial)"
+           " WHERE pd.coin_pub=$1"
+           "   AND pdes.refunded;");
+  PREPARE (pg,
+           "recoup_by_old_coin",
+           "SELECT"
+           " coins.coin_pub"
+           ",coin_sig"
+           ",coin_blind"
+           ",amount"
+           ",recoup_timestamp"
+           ",denoms.denom_pub_hash"
+           ",coins.denom_sig"
+           ",recoup_refresh_uuid"
+           " FROM recoup_refresh"
+           " JOIN known_coins coins"
+           "   USING (coin_pub)"
+           " JOIN denominations denoms"
+           "   USING (denominations_serial)"
+           " WHERE rrc_serial IN"
+           "   (SELECT rrc.rrc_serial"
+           "    FROM refresh_commitments"
+           "       JOIN refresh_revealed_coins rrc"
+           "           USING (melt_serial_id)"
+           "    WHERE old_coin_pub=$1);");
+  PREPARE (pg,
+           "recoup_by_coin",
+           "SELECT"
+           " reserves.reserve_pub"
+           ",denoms.denom_pub_hash"
+           ",coin_sig"
+           ",coin_blind"
+           ",amount"
+           ",recoup_timestamp"
+           ",recoup_uuid"
+           " FROM recoup rcp"
+           /* NOTE: suboptimal JOIN follows: crosses shards!
+              Could theoretically be improved via a materialized
+              index. But likely not worth it (query is rare and
+              number of reserve shards might be limited) */
+           " JOIN reserves_out ro"
+           "   USING (reserve_out_serial_id)"
+           " JOIN reserves"
+           "   USING (reserve_uuid)"
+           " JOIN known_coins coins"
+           "   USING (coin_pub)"
+           " JOIN denominations denoms"
+           "   ON (denoms.denominations_serial = coins.denominations_serial)"
+           " WHERE coins.coin_pub=$1;");
+  /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
+     for a refreshed coin */
+  PREPARE (pg,
+           "recoup_by_refreshed_coin",
+           "SELECT"
+           " old_coins.coin_pub AS old_coin_pub"
+           ",coin_sig"
+           ",coin_blind"
+           ",amount"
+           ",recoup_timestamp"
+           ",denoms.denom_pub_hash"
+           ",coins.denom_sig"
+           ",recoup_refresh_uuid"
+           " FROM recoup_refresh"
+           "    JOIN refresh_revealed_coins rrc"
+           "      USING (rrc_serial)"
+           "    JOIN refresh_commitments rfc"
+           "      ON (rrc.melt_serial_id = rfc.melt_serial_id)"
+           "    JOIN known_coins old_coins"
+           "      ON (rfc.old_coin_pub = old_coins.coin_pub)"
+           "    JOIN known_coins coins"
+           "      ON (recoup_refresh.coin_pub = coins.coin_pub)"
+           "    JOIN denominations denoms"
+           "      ON (denoms.denominations_serial = 
coins.denominations_serial)"
+           " WHERE coins.coin_pub=$1;");
+  PREPARE (pg,
+           "reserve_open_by_coin",
+           "SELECT"
+           " reserve_open_deposit_uuid"
+           ",coin_sig"
+           ",reserve_sig"
+           ",contribution"
+           " FROM reserves_open_deposits"
+           " WHERE coin_pub=$1;");
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Getting transactions for coin %s\n",
+              TALER_B2S (coin_pub));
+  for (unsigned int i = 0; NULL != work[i].statement; i++)
+  {
+    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               work[i].statement,
+                                               params,
+                                               work[i].cb,
+                                               &chc);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Coin %s yielded %d transactions of type %s\n",
+                TALER_B2S (coin_pub),
+                qs,
+                work[i].statement);
+    if ( (0 > qs) ||
+         (chc.failed) )
+    {
+      if (NULL != chc.head)
+        TEH_COMMON_free_coin_transaction_list (cls,
+                                               chc.head);
+      *tlp = NULL;
+      if (chc.failed)
+        qs = GNUNET_DB_STATUS_HARD_ERROR;
+      return qs;
+    }
+  }
+  *tlp = chc.head;
+  if (NULL == chc.head)
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
diff --git a/src/exchangedb/pg_get_coin_transactions.h 
b/src/exchangedb/pg_get_coin_transactions.h
new file mode 100644
index 0000000..c95fd09
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.h
@@ -0,0 +1,44 @@
+/*
+   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 pg_get_coin_transactions.h
+ * @brief implementation of the get_coin_transactions function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_TRANSACTIONS_H
+#define PG_GET_COIN_TRANSACTIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compile a list of all (historic) transactions performed with the given coin
+ * (/refresh/melt, /deposit, /refund and /recoup operations).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param coin_pub coin to investigate
+ * @param[out] tlp set to list of transactions, NULL if coin is fresh
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_EXCHANGEDB_TransactionList **tlp);
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_info.c 
b/src/exchangedb/pg_get_denomination_info.c
new file mode 100644
index 0000000..4bae297
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.c
@@ -0,0 +1,91 @@
+/*
+   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_get_denomination_info.c
+ * @brief Implementation of the get_denomination_info 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_get_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+  struct PostgresClosure *pg = cls;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                          &issue->signature),
+    GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                     &issue->start),
+    GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+                                     &issue->expire_withdraw),
+    GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+                                     &issue->expire_deposit),
+    GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                     &issue->expire_legal),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+                                 &issue->value),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+                                 &issue->fees.withdraw),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                 &issue->fees.deposit),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+                                 &issue->fees.refresh),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                 &issue->fees.refund),
+    GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                  &issue->age_mask.bits),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "denomination_get",
+           "SELECT"
+           " master_sig"
+           ",valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin"  /* value of this denom */
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",age_mask"
+           " FROM denominations"
+           " WHERE denom_pub_hash=$1;");
+  qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                 "denomination_get",
+                                                 params,
+                                                 rs);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+    return qs;
+  issue->denom_hash = *denom_pub_hash;
+  return qs;
+}
diff --git a/src/exchangedb/pg_get_denomination_info.h 
b/src/exchangedb/pg_get_denomination_info.h
new file mode 100644
index 0000000..8432277
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+   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_get_denomination_info.h
+ * @brief implementation of the get_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_INFO_H
+#define PG_GET_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Fetch information about a denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the public key used for signing coins of this 
denomination
+ * @param[out] issue set to issue information with value, fees and other info 
about the coin
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_revocation.c 
b/src/exchangedb/pg_get_denomination_revocation.c
new file mode 100644
index 0000000..5e7a3a3
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.c
@@ -0,0 +1,63 @@
+/*
+   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_get_denomination_revocation.c
+ * @brief Implementation of the get_denomination_revocation 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_get_denomination_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  struct TALER_MasterSignatureP *master_sig,
+  uint64_t *rowid)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                          master_sig),
+    GNUNET_PQ_result_spec_uint64 ("denom_revocations_serial_id",
+                                  rowid),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "denomination_revocation_get",
+           "SELECT"
+           " master_sig"
+           ",denom_revocations_serial_id"
+           " FROM denomination_revocations"
+           " WHERE denominations_serial="
+           "  (SELECT denominations_serial"
+           "    FROM denominations"
+           "    WHERE denom_pub_hash=$1);");
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"denomination_revocation_get",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_denomination_revocation.h 
b/src/exchangedb/pg_get_denomination_revocation.h
new file mode 100644
index 0000000..5f7f272
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.h
@@ -0,0 +1,45 @@
+/*
+   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_get_denomination_revocation.h
+ * @brief implementation of the get_denomination_revocation function for 
Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_REVOCATION_H
+#define PG_GET_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a denomination key's revocation from
+ * the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param[out] master_sig signature affirming the revocation
+ * @param[out] rowid row where the information is stored
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  struct TALER_MasterSignatureP *master_sig,
+  uint64_t *rowid);
+
+#endif
diff --git a/src/exchangedb/pg_get_expired_reserves.c 
b/src/exchangedb/pg_get_expired_reserves.c
new file mode 100644
index 0000000..be9ece9
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.c
@@ -0,0 +1,174 @@
+/*
+   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 pg_get_expired_reserves.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_expired_cb().
+ */
+struct ExpiredReserveContext
+{
+  /**
+   * Function to call for each expired reserve.
+   */
+  TALER_EXCHANGEDB_ReserveExpiredCallback rec;
+
+  /**
+   * Closure to give to @e rec.
+   */
+  void *rec_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+
+  /**
+   * Set to #GNUNET_SYSERR on error.
+   */
+  enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_expired_cb (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
+{
+  struct ExpiredReserveContext *erc = cls;
+  struct PostgresClosure *pg = erc->pg;
+  enum GNUNET_GenericReturnValue ret;
+
+  ret = GNUNET_OK;
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct GNUNET_TIME_Timestamp exp_date;
+    char *account_details;
+    struct TALER_ReservePublicKeyP reserve_pub;
+    struct TALER_Amount remaining_balance;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_timestamp ("expiration_date",
+                                       &exp_date),
+      GNUNET_PQ_result_spec_string ("account_details",
+                                    &account_details),
+      GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                            &reserve_pub),
+      TALER_PQ_result_spec_amount ("current_balance",
+                                   pg->currency,
+                                   &remaining_balance),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ret = GNUNET_SYSERR;
+      break;
+    }
+    ret = erc->rec (erc->rec_cls,
+                    &reserve_pub,
+                    &remaining_balance,
+                    account_details,
+                    exp_date,
+                    0);
+    GNUNET_PQ_cleanup_result (rs);
+    if (GNUNET_OK != ret)
+      break;
+  }
+  erc->status = ret;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+                             struct GNUNET_TIME_Timestamp now,
+                             TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+                             void *rec_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_timestamp (&now),
+    GNUNET_PQ_query_param_end
+  };
+  struct ExpiredReserveContext ectx = {
+    .rec = rec,
+    .rec_cls = rec_cls,
+    .pg = pg,
+    .status = GNUNET_OK
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  PREPARE (pg,
+           "get_expired_reserves",
+           "WITH ed AS MATERIALIZED ( "
+           " SELECT * "
+           " FROM reserves "
+           " WHERE expiration_date <= $1 "
+           "   AND ((current_balance).val != 0 OR (current_balance).frac != 0) 
"
+           " ORDER BY expiration_date ASC "
+           " LIMIT 1 "
+           ") "
+           "SELECT "
+           " ed.expiration_date "
+           " ,payto_uri AS account_details "
+           " ,ed.reserve_pub "
+           " ,current_balance "
+           "FROM ( "
+           " SELECT "
+           "  * "
+           " FROM reserves_in "
+           " WHERE reserve_pub = ( "
+           "     SELECT reserve_pub FROM ed) "
+           " ) ri "
+           "JOIN wire_targets wt ON (ri.wire_source_h_payto = 
wt.wire_target_h_payto) "
+           "JOIN ed ON (ri.reserve_pub = ed.reserve_pub);");
+  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                             "get_expired_reserves",
+                                             params,
+                                             &reserve_expired_cb,
+                                             &ectx);
+  switch (ectx.status)
+  {
+  case GNUNET_SYSERR:
+    return GNUNET_DB_STATUS_HARD_ERROR;
+  case GNUNET_NO:
+    return GNUNET_DB_STATUS_SOFT_ERROR;
+  case GNUNET_OK:
+    break;
+  }
+  return qs;
+}
diff --git a/src/exchangedb/pg_get_expired_reserves.h 
b/src/exchangedb/pg_get_expired_reserves.h
new file mode 100644
index 0000000..0874b53
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.h
@@ -0,0 +1,45 @@
+/*
+   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 pg_get_expired_reserves.h
+ * @brief implementation of the get_expired_reserves function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_EXPIRED_RESERVES_H
+#define PG_GET_EXPIRED_RESERVES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about expired reserves and their
+ * remaining balances.
+ *
+ * @param cls closure of the plugin
+ * @param now timestamp based on which we decide expiration
+ * @param rec function to call on expired reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+                             struct GNUNET_TIME_Timestamp now,
+                             TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+                             void *rec_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_known_coin.c 
b/src/exchangedb/pg_get_known_coin.c
new file mode 100644
index 0000000..bab48c1
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.c
@@ -0,0 +1,71 @@
+/*
+   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_get_known_coin.c
+ * @brief Implementation of the get_known_coin 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_get_known_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+                       const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                       struct TALER_CoinPublicInfo *coin_info)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                          &coin_info->denom_pub_hash),
+    GNUNET_PQ_result_spec_allow_null (
+      GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+                                            &coin_info->h_age_commitment),
+      &coin_info->no_age_commitment),
+    TALER_PQ_result_spec_denom_sig ("denom_sig",
+                                    &coin_info->denom_sig),
+    GNUNET_PQ_result_spec_end
+  };
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Getting known coin data for coin %s\n",
+              TALER_B2S (coin_pub));
+  coin_info->coin_pub = *coin_pub;
+  /* Used in #postgres_get_known_coin() to fetch
+     the denomination public key and signature for
+     a coin known to the exchange. */
+  PREPARE (pg,
+           "get_known_coin",
+           "SELECT"
+           " denominations.denom_pub_hash"
+           ",age_commitment_hash"
+           ",denom_sig"
+           " FROM known_coins"
+           " JOIN denominations USING (denominations_serial)"
+           " WHERE coin_pub=$1;");
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_known_coin",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_known_coin.h 
b/src/exchangedb/pg_get_known_coin.h
new file mode 100644
index 0000000..c34bd2a
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.h
@@ -0,0 +1,40 @@
+/*
+   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_get_known_coin.h
+ * @brief implementation of the get_known_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_KNOWN_COIN_H
+#define PG_GET_KNOWN_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the record for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param coin_info place holder for the returned coin information object
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+                       const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                       struct TALER_CoinPublicInfo *coin_info);
+
+#endif
diff --git a/src/exchangedb/pg_get_ready_deposit.c 
b/src/exchangedb/pg_get_ready_deposit.c
new file mode 100644
index 0000000..d8344fa
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.c
@@ -0,0 +1,74 @@
+/*
+   This file is part of TALER
+   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
+   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_get_ready_deposit.c
+ * @brief Implementation of the get_ready_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_get_ready_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+                          uint64_t start_shard_row,
+                          uint64_t end_shard_row,
+                          struct TALER_MerchantPublicKeyP *merchant_pub,
+                          char **payto_uri)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now
+    = GNUNET_TIME_absolute_get ();
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_uint64 (&start_shard_row),
+    GNUNET_PQ_query_param_uint64 (&end_shard_row),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+                                          merchant_pub),
+    GNUNET_PQ_result_spec_string ("payto_uri",
+                                  payto_uri),
+    GNUNET_PQ_result_spec_end
+  };
+  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,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_ready_deposit.h 
b/src/exchangedb/pg_get_ready_deposit.h
new file mode 100644
index 0000000..b1dd7a9
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.h
@@ -0,0 +1,46 @@
+/*
+   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_get_ready_deposit.h
+ * @brief implementation of the get_ready_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_READY_DEPOSIT_H
+#define PG_GET_READY_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about deposits that are ready to be executed.  Such
+ * deposits must not be marked as "done", the execution time must be
+ * in the past, and the KYC status must be 'ok'.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_shard_row minimum shard row to select
+ * @param end_shard_row maximum shard row to select (inclusive)
+ * @param[out] merchant_pub set to the public key of a merchant with a ready 
deposit
+ * @param[out] payto_uri set to the account of the merchant, to be freed by 
caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+                          uint64_t start_shard_row,
+                          uint64_t end_shard_row,
+                          struct TALER_MerchantPublicKeyP *merchant_pub,
+                          char **payto_uri);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_balance.c 
b/src/exchangedb/pg_get_reserve_balance.c
new file mode 100644
index 0000000..140bf3b
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.c
@@ -0,0 +1,55 @@
+/*
+   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_get_reserve_balance.c
+ * @brief Implementation of the get_reserve_balance 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_get_reserve_balance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            struct TALER_Amount *balance)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    TALER_PQ_result_spec_amount ("current_balance",
+                                 pg->currency,
+                                 balance),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "get_reserve_balance",
+           "SELECT"
+           " current_balance"
+           " FROM reserves"
+           " WHERE reserve_pub=$1;");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_reserve_balance",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_balance.h 
b/src/exchangedb/pg_get_reserve_balance.h
new file mode 100644
index 0000000..6dc88d9
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.h
@@ -0,0 +1,40 @@
+/*
+   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_get_reserve_balance.h
+ * @brief implementation of the get_reserve_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BALANCE_H
+#define PG_GET_RESERVE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the balance of the specified reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] balance set to the reserve balance
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+                            const struct TALER_ReservePublicKeyP *reserve_pub,
+                            struct TALER_Amount *balance);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.c 
b/src/exchangedb/pg_get_reserve_by_h_blind.c
new file mode 100644
index 0000000..f87fe6c
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.c
@@ -0,0 +1,63 @@
+/*
+   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_get_reserve_by_h_blind.c
+ * @brief Implementation of the get_reserve_by_h_blind 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_get_reserve_by_h_blind.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+  void *cls,
+  const struct TALER_BlindedCoinHashP *bch,
+  struct TALER_ReservePublicKeyP *reserve_pub,
+  uint64_t *reserve_out_serial_id)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (bch),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                          reserve_pub),
+    GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+                                  reserve_out_serial_id),
+    GNUNET_PQ_result_spec_end
+  };
+  /* Used in #postgres_get_reserve_by_h_blind() */
+  PREPARE (pg,
+           "reserve_by_h_blind",
+           "SELECT"
+           " reserves.reserve_pub"
+           ",reserve_out_serial_id"
+           " FROM reserves_out"
+           " JOIN reserves"
+           "   USING (reserve_uuid)"
+           " WHERE h_blind_ev=$1"
+           " LIMIT 1;");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "reserve_by_h_blind",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.h 
b/src/exchangedb/pg_get_reserve_by_h_blind.h
new file mode 100644
index 0000000..49c1c84
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.h
@@ -0,0 +1,44 @@
+/*
+   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_get_reserve_by_h_blind.h
+ * @brief implementation of the get_reserve_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BY_H_BLIND_H
+#define PG_GET_RESERVE_BY_H_BLIND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about which reserve a coin was generated
+ * from given the hash of the blinded coin.
+ *
+ * @param cls closure
+ * @param bch hash that uniquely identifies the withdraw request
+ * @param[out] reserve_pub set to information about the reserve (on success 
only)
+ * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in 
reserves_out
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+  void *cls,
+  const struct TALER_BlindedCoinHashP *bch,
+  struct TALER_ReservePublicKeyP *reserve_pub,
+  uint64_t *reserve_out_serial_id);
+
+#endif
diff --git a/src/exchangedb/pg_get_withdraw_info.c 
b/src/exchangedb/pg_get_withdraw_info.c
new file mode 100644
index 0000000..e06fa37
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.c
@@ -0,0 +1,79 @@
+/*
+   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_get_withdraw_info.c
+ * @brief Implementation of the get_withdraw_info 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_get_withdraw_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+  void *cls,
+  const struct TALER_BlindedCoinHashP *bch,
+  struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (bch),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                          &collectable->denom_pub_hash),
+    TALER_PQ_result_spec_blinded_denom_sig ("denom_sig",
+                                            &collectable->sig),
+    GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+                                          &collectable->reserve_sig),
+    GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+                                          &collectable->reserve_pub),
+    GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+                                          &collectable->h_coin_envelope),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                 &collectable->amount_with_fee),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+                                 &collectable->withdraw_fee),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "get_withdraw_info",
+           "SELECT"
+           " denom.denom_pub_hash"
+           ",denom_sig"
+           ",reserve_sig"
+           ",reserves.reserve_pub"
+           ",execution_date"
+           ",h_blind_ev"
+           ",amount_with_fee"
+           ",denom.fee_withdraw"
+           " FROM reserves_out"
+           "    JOIN reserves"
+           "      USING (reserve_uuid)"
+           "    JOIN denominations denom"
+           "      USING (denominations_serial)"
+           " WHERE h_blind_ev=$1;");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "get_withdraw_info",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_get_withdraw_info.h 
b/src/exchangedb/pg_get_withdraw_info.h
new file mode 100644
index 0000000..7c3e06a
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.h
@@ -0,0 +1,43 @@
+/*
+   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_get_withdraw_info.h
+ * @brief implementation of the get_withdraw_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WITHDRAW_INFO_H
+#define PG_GET_WITHDRAW_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Locate the response for a /reserve/withdraw request under the
+ * key of the hash of the blinded message.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bch hash that uniquely identifies the withdraw operation
+ * @param collectable corresponding collectable coin (blind signature)
+ *                    if a coin is found
+ * @return statement execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+  void *cls,
+  const struct TALER_BlindedCoinHashP *bch,
+  struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+
+#endif
diff --git a/src/exchangedb/pg_have_deposit2.c 
b/src/exchangedb/pg_have_deposit2.c
new file mode 100644
index 0000000..e00ad74
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.c
@@ -0,0 +1,117 @@
+/*
+   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_have_deposit2.c
+ * @brief Implementation of the have_deposit2 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_have_deposit2.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+  void *cls,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  struct TALER_Amount *deposit_fee,
+  struct GNUNET_TIME_Timestamp *exchange_timestamp)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (coin_pub),
+    GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+    GNUNET_PQ_query_param_auto_from_type (merchant),
+    GNUNET_PQ_query_param_end
+  };
+  struct TALER_EXCHANGEDB_Deposit deposit2;
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+                                 &deposit2.amount_with_fee),
+    GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+                                     &deposit2.timestamp),
+    GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+                                     exchange_timestamp),
+    GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+                                     &deposit2.refund_deadline),
+    GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+                                     &deposit2.wire_deadline),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                 deposit_fee),
+    GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+                                          &deposit2.wire_salt),
+    GNUNET_PQ_result_spec_string ("receiver_wire_account",
+                                  &deposit2.receiver_wire_account),
+    GNUNET_PQ_result_spec_end
+  };
+  enum GNUNET_DB_QueryStatus qs;
+  struct TALER_MerchantWireHashP h_wire2;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Getting deposits for coin %s\n",
+              TALER_B2S (coin_pub));
+  PREPARE (pg,
+           "get_deposit",
+           "SELECT"
+           " cdep.amount_with_fee"
+           ",denominations.fee_deposit"
+           ",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 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 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,
+                                                 rs);
+  if (0 >= qs)
+    return qs;
+  TALER_merchant_wire_signature_hash (deposit2.receiver_wire_account,
+                                      &deposit2.wire_salt,
+                                      &h_wire2);
+  GNUNET_free (deposit2.receiver_wire_account);
+  /* Now we check that the other information in @a deposit
+     also matches, and if not report inconsistencies. */
+  if ( (GNUNET_TIME_timestamp_cmp (refund_deadline,
+                                   !=,
+                                   deposit2.refund_deadline)) ||
+       (0 != GNUNET_memcmp (h_wire,
+                            &h_wire2) ) )
+  {
+    /* Inconsistencies detected! Does not match! */
+    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+  }
+  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
diff --git a/src/exchangedb/pg_have_deposit2.h 
b/src/exchangedb/pg_have_deposit2.h
new file mode 100644
index 0000000..0e8119c
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.h
@@ -0,0 +1,53 @@
+/*
+   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_have_deposit2.h
+ * @brief implementation of the have_deposit2 function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_HAVE_DEPOSIT2_H
+#define PG_HAVE_DEPOSIT2_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Check if we have the specified deposit already in the database.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param h_contract_terms contract to check for
+ * @param h_wire wire hash to check for
+ * @param coin_pub public key of the coin to check for
+ * @param merchant merchant public key to check for
+ * @param refund_deadline expected refund deadline
+ * @param[out] deposit_fee set to the deposit fee the exchange charged
+ * @param[out] exchange_timestamp set to the time when the exchange received 
the deposit
+ * @return 1 if we know this operation,
+ *         0 if this exact deposit is unknown to us,
+ *         otherwise transaction error status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+  void *cls,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  struct TALER_Amount *deposit_fee,
+  struct GNUNET_TIME_Timestamp *exchange_timestamp);
+#endif
diff --git a/src/exchangedb/pg_helper.h b/src/exchangedb/pg_helper.h
new file mode 100644
index 0000000..c63c911
--- /dev/null
+++ b/src/exchangedb/pg_helper.h
@@ -0,0 +1,149 @@
+/*
+   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 pg_helper.h
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#ifndef PG_HELPER_H
+#define PG_HELPER_H
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+  /**
+   * Our configuration.
+   */
+  const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+  /**
+   * Directory with SQL statements to run to create tables.
+   */
+  char *sql_dir;
+
+  /**
+   * After how long should idle reserves be closed?
+   */
+  struct GNUNET_TIME_Relative idle_reserve_expiration_time;
+
+  /**
+   * After how long should reserves that have seen withdraw operations
+   * be garbage collected?
+   */
+  struct GNUNET_TIME_Relative legal_reserve_expiration_time;
+
+  /**
+   * What delay should we introduce before ready transactions
+   * are actually aggregated?
+   */
+  struct GNUNET_TIME_Relative aggregator_shift;
+
+  /**
+   * Which currency should we assume all amounts to be in?
+   */
+  char *currency;
+
+  /**
+   * Our base URL.
+   */
+  char *exchange_url;
+
+  /**
+   * Postgres connection handle.
+   */
+  struct GNUNET_PQ_Context *conn;
+
+  /**
+   * Name of the current transaction, for debugging.
+   */
+  const char *transaction_name;
+
+  /**
+   * Counts how often we have established a fresh @e conn
+   * to the database. Used to re-prepare statements.
+   */
+  unsigned long long prep_gen;
+
+  /**
+   * Number of purses we allow to be opened concurrently
+   * for one year per annual fee payment.
+   */
+  uint32_t def_purse_limit;
+
+};
+
+
+/**
+ * Prepares SQL statement @a sql under @a name for
+ * connection @a pg once.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct PostgresClosure`
+ * @param name name to prepare the statement under
+ * @param sql actual SQL text
+ */
+#define PREPARE(pg,name,sql)                      \
+  do {                                            \
+    static struct {                               \
+      unsigned long long cnt;                     \
+      struct PostgresClosure *pg;                 \
+    } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \
+    unsigned int off = 0;                         \
+                                                  \
+    while ( (NULL != preps[off].pg) &&            \
+            (pg != preps[off].pg) &&              \
+            (off < sizeof(preps) / sizeof(*preps)) ) \
+    off++;                                      \
+    GNUNET_assert (off <                          \
+                   sizeof(preps) / sizeof(*preps)); \
+    if (preps[off].cnt < pg->prep_gen)            \
+    {                                             \
+      struct GNUNET_PQ_PreparedStatement ps[] = { \
+        GNUNET_PQ_make_prepare (name, sql),       \
+        GNUNET_PQ_PREPARED_STATEMENT_END          \
+      };                                          \
+                                                  \
+      if (GNUNET_OK !=                            \
+          GNUNET_PQ_prepare_statements (pg->conn, \
+                                        ps))      \
+      {                                           \
+        GNUNET_break (0);                         \
+        return GNUNET_DB_STATUS_HARD_ERROR;       \
+      }                                           \
+      preps[off].pg = pg;                         \
+      preps[off].cnt = pg->prep_gen;              \
+    }                                             \
+  } while (0)
+
+
+/**
+ * Wrapper macro to add the currency from the plugin's state
+ * when fetching amounts from the database.
+ *
+ * @param field name of the database field to fetch amount from
+ * @param[out] amountp pointer to amount to set
+ */
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field, \
+                                    amountp) TALER_PQ_result_spec_amount ( \
+    field,pg->currency,amountp)
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_info.c 
b/src/exchangedb/pg_insert_denomination_info.c
new file mode 100644
index 0000000..878bc5d
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.c
@@ -0,0 +1,101 @@
+/*
+   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_denomination_info.c
+ * @brief Implementation of the insert_denomination_info 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_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+  void *cls,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+  struct PostgresClosure *pg = cls;
+  struct TALER_DenominationHashP denom_hash;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&issue->denom_hash),
+    TALER_PQ_query_param_denom_pub (denom_pub),
+    GNUNET_PQ_query_param_auto_from_type (&issue->signature),
+    GNUNET_PQ_query_param_timestamp (&issue->start),
+    GNUNET_PQ_query_param_timestamp (&issue->expire_withdraw),
+    GNUNET_PQ_query_param_timestamp (&issue->expire_deposit),
+    GNUNET_PQ_query_param_timestamp (&issue->expire_legal),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &issue->value),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &issue->fees.withdraw),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &issue->fees.deposit),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &issue->fees.refresh),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &issue->fees.refund),
+    GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.bits),
+    GNUNET_PQ_query_param_end
+  };
+
+  GNUNET_assert (denom_pub->age_mask.bits ==
+                 issue->age_mask.bits);
+  TALER_denom_pub_hash (denom_pub,
+                        &denom_hash);
+  GNUNET_assert (0 ==
+                 GNUNET_memcmp (&denom_hash,
+                                &issue->denom_hash));
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   issue->start.abs_time));
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   issue->expire_withdraw.abs_time));
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   issue->expire_deposit.abs_time));
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   issue->expire_legal.abs_time));
+  /* check fees match denomination currency */
+  GNUNET_assert (GNUNET_YES ==
+                 TALER_denom_fee_check_currency (
+                   issue->value.currency,
+                   &issue->fees));
+  PREPARE (pg,
+           "denomination_insert",
+           "INSERT INTO denominations "
+           "(denom_pub_hash"
+           ",denom_pub"
+           ",master_sig"
+           ",valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin"  /* value of this denom */
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",age_mask"
+           ") VALUES "
+           "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+           " $11, $12, $13);");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "denomination_insert",
+                                             params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_info.h 
b/src/exchangedb/pg_insert_denomination_info.h
new file mode 100644
index 0000000..663f45b
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.h
@@ -0,0 +1,42 @@
+/*
+   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_denomination_info.h
+ * @brief implementation of the insert_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_INFO_H
+#define PG_INSERT_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert a denomination key's public information into the database for
+ * reference by auditors and other consistency checks.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub the public key used for signing coins of this denomination
+ * @param issue issuing information with value, fees and other info about the 
coin
+ * @return status of the query
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+  void *cls,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_revocation.c 
b/src/exchangedb/pg_insert_denomination_revocation.c
new file mode 100644
index 0000000..49445f2
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.c
@@ -0,0 +1,54 @@
+/*
+   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_denomination_revocation.c
+ * @brief Implementation of the insert_denomination_revocation 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_denomination_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  /* Used in #postgres_insert_denomination_revocation() */
+  PREPARE (pg,
+           "denomination_revocation_insert",
+           "INSERT INTO denomination_revocations "
+           "(denominations_serial"
+           ",master_sig"
+           ") SELECT denominations_serial,$2"
+           "    FROM denominations"
+           "   WHERE denom_pub_hash=$1;");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "denomination_revocation_insert",
+                                             params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_revocation.h 
b/src/exchangedb/pg_insert_denomination_revocation.h
new file mode 100644
index 0000000..e3da876
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.h
@@ -0,0 +1,42 @@
+/*
+   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_denomination_revocation.h
+ * @brief implementation of the insert_denomination_revocation function for 
Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_REVOCATION_H
+#define PG_INSERT_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information that a denomination key was revoked
+ * in the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+  void *cls,
+  const struct TALER_DenominationHashP *denom_pub_hash,
+  const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_signkey_revocation.c 
b/src/exchangedb/pg_insert_signkey_revocation.c
new file mode 100644
index 0000000..9197be6
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.c
@@ -0,0 +1,53 @@
+/*
+   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_signkey_revocation.c
+ * @brief Implementation of the insert_signkey_revocation 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_signkey_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+
+  PREPARE (pg,
+           "insert_signkey_revocation",
+           "INSERT INTO signkey_revocations "
+           "(esk_serial"
+           ",master_sig"
+           ") SELECT esk_serial, $2 "
+           "    FROM exchange_sign_keys"
+           "   WHERE exchange_pub=$1;");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_signkey_revocation",
+                                             params);
+}
diff --git a/src/exchangedb/pg_insert_signkey_revocation.h 
b/src/exchangedb/pg_insert_signkey_revocation.h
new file mode 100644
index 0000000..534e6d4
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.h
@@ -0,0 +1,41 @@
+/*
+   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_signkey_revocation.h
+ * @brief implementation of the insert_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_SIGNKEY_REVOCATION_H
+#define PG_INSERT_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_iterate_active_signkeys.c 
b/src/exchangedb/pg_iterate_active_signkeys.c
new file mode 100644
index 0000000..9c280c9
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.c
@@ -0,0 +1,144 @@
+/*
+   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_iterate_active_signkeys.c
+ * @brief Implementation of the iterate_active_signkeys 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_iterate_active_signkeys.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #signkeys_cb_helper()
+ */
+struct SignkeysIteratorContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_EXCHANGEDB_ActiveSignkeysCallback cb;
+
+  /**
+   * Closure to pass to @e cb
+   */
+  void *cb_cls;
+
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_active_signkeys().
+ * Calls the callback with each signkey.
+ *
+ * @param cls a `struct SignkeysIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+signkeys_cb_helper (void *cls,
+                    PGresult *result,
+                    unsigned int num_results)
+{
+  struct SignkeysIteratorContext *dic = cls;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+    struct TALER_ExchangePublicKeyP exchange_pub;
+    struct TALER_MasterSignatureP master_sig;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                            &master_sig),
+      GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+                                            &exchange_pub),
+      GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                       &meta.start),
+      GNUNET_PQ_result_spec_timestamp ("expire_sign",
+                                       &meta.expire_sign),
+      GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                       &meta.expire_legal),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      return;
+    }
+    dic->cb (dic->cb_cls,
+             &exchange_pub,
+             &meta,
+             &master_sig);
+  }
+}
+
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key.  Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+                                TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+                                void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_TIME_Absolute now = {0};
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_absolute_time (&now),
+    GNUNET_PQ_query_param_end
+  };
+  struct SignkeysIteratorContext dic = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+  };
+
+  PREPARE (pg,
+           "select_signkeys",
+           "SELECT"
+           " master_sig"
+           ",exchange_pub"
+           ",valid_from"
+           ",expire_sign"
+           ",expire_legal"
+           " FROM exchange_sign_keys esk"
+           " WHERE"
+           "   expire_sign > $1"
+           " AND NOT EXISTS "
+           "  (SELECT esk_serial "
+           "     FROM signkey_revocations skr"
+           "    WHERE esk.esk_serial = skr.esk_serial);");
+  now = GNUNET_TIME_absolute_get ();
+  return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "select_signkeys",
+                                               params,
+                                               &signkeys_cb_helper,
+                                               &dic);
+}
diff --git a/src/exchangedb/pg_iterate_active_signkeys.h 
b/src/exchangedb/pg_iterate_active_signkeys.h
new file mode 100644
index 0000000..5ebba9f
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.h
@@ -0,0 +1,43 @@
+/*
+   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_iterate_active_signkeys.h
+ * @brief implementation of the iterate_active_signkeys function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_ACTIVE_SIGNKEYS_H
+#define PG_ITERATE_ACTIVE_SIGNKEYS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key.  Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+                                TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+                                void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denomination_info.c 
b/src/exchangedb/pg_iterate_denomination_info.c
new file mode 100644
index 0000000..cab51d5
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.c
@@ -0,0 +1,180 @@
+/*
+   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_iterate_denomination_info.c
+ * @brief Implementation of the iterate_denomination_info 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_iterate_denomination_info.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #domination_cb_helper()
+ */
+struct DenomIteratorContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_EXCHANGEDB_DenominationCallback cb;
+
+  /**
+   * Closure to pass to @e cb
+   */
+  void *cb_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denomination_info().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+domination_cb_helper (void *cls,
+                      PGresult *result,
+                      unsigned int num_results)
+{
+  struct DenomIteratorContext *dic = cls;
+  struct PostgresClosure *pg = dic->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+    struct TALER_DenominationPublicKey denom_pub;
+    struct TALER_DenominationHashP denom_hash;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                            &issue.signature),
+      GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+                                            &denom_hash),
+      GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                       &issue.start),
+      GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+                                       &issue.expire_withdraw),
+      GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+                                       &issue.expire_deposit),
+      GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                       &issue.expire_legal),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+                                   &issue.value),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+                                   &issue.fees.withdraw),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                   &issue.fees.deposit),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+                                   &issue.fees.refresh),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                   &issue.fees.refund),
+      TALER_PQ_result_spec_denom_pub ("denom_pub",
+                                      &denom_pub),
+      GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                    &issue.age_mask.bits),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      return;
+    }
+
+    /* Unfortunately we have to carry the age mask in both, the
+     * TALER_DenominationPublicKey and
+     * TALER_EXCHANGEDB_DenominationKeyInformation at different times.
+     * Here we use _both_ so let's make sure the values are the same. */
+    denom_pub.age_mask = issue.age_mask;
+    TALER_denom_pub_hash (&denom_pub,
+                          &issue.denom_hash);
+    if (0 !=
+        GNUNET_memcmp (&issue.denom_hash,
+                       &denom_hash))
+    {
+      GNUNET_break (0);
+    }
+    else
+    {
+      dic->cb (dic->cb_cls,
+               &denom_pub,
+               &issue);
+    }
+    TALER_denom_pub_free (&denom_pub);
+  }
+}
+
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+                                  TALER_EXCHANGEDB_DenominationCallback cb,
+                                  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_end
+  };
+  struct DenomIteratorContext dic = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+    .pg = pg
+  };
+
+  PREPARE (pg,
+           "denomination_iterate",
+           "SELECT"
+           " master_sig"
+           ",denom_pub_hash"
+           ",valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin" /* value of this denom */
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",denom_pub"
+           ",age_mask"
+           " FROM denominations;");
+  return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "denomination_iterate",
+                                               params,
+                                               &domination_cb_helper,
+                                               &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denomination_info.h 
b/src/exchangedb/pg_iterate_denomination_info.h
new file mode 100644
index 0000000..27c08d0
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+   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_iterate_denomination_info.h
+ * @brief implementation of the iterate_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATION_INFO_H
+#define PG_ITERATE_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+                                  TALER_EXCHANGEDB_DenominationCallback cb,
+                                  void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denominations.c 
b/src/exchangedb/pg_iterate_denominations.c
new file mode 100644
index 0000000..684aa16
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.c
@@ -0,0 +1,172 @@
+/*
+   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_iterate_denominations.c
+ * @brief Implementation of the iterate_denominations 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_iterate_denominations.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #dominations_cb_helper()
+ */
+struct DenomsIteratorContext
+{
+  /**
+   * Function to call with the results.
+   */
+  TALER_EXCHANGEDB_DenominationsCallback cb;
+
+  /**
+   * Closure to pass to @e cb
+   */
+  void *cb_cls;
+
+  /**
+   * Plugin context.
+   */
+  struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denominations().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomsIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+dominations_cb_helper (void *cls,
+                       PGresult *result,
+                       unsigned int num_results)
+{
+  struct DenomsIteratorContext *dic = cls;
+  struct PostgresClosure *pg = dic->pg;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+    struct TALER_DenominationPublicKey denom_pub = {0};
+    struct TALER_MasterSignatureP master_sig = {0};
+    struct TALER_DenominationHashP h_denom_pub = {0};
+    bool revoked;
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_uint64 ("denominations_serial",
+                                    &meta.serial),
+      GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                            &master_sig),
+      GNUNET_PQ_result_spec_bool ("revoked",
+                                  &revoked),
+      GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                       &meta.start),
+      GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+                                       &meta.expire_withdraw),
+      GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+                                       &meta.expire_deposit),
+      GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                       &meta.expire_legal),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+                                   &meta.value),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+                                   &meta.fees.withdraw),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                   &meta.fees.deposit),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+                                   &meta.fees.refresh),
+      TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                   &meta.fees.refund),
+      TALER_PQ_result_spec_denom_pub ("denom_pub",
+                                      &denom_pub),
+      GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                    &meta.age_mask.bits),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      return;
+    }
+
+    /* make sure the mask information is the same */
+    denom_pub.age_mask = meta.age_mask;
+
+    TALER_denom_pub_hash (&denom_pub,
+                          &h_denom_pub);
+    dic->cb (dic->cb_cls,
+             &denom_pub,
+             &h_denom_pub,
+             &meta,
+             &master_sig,
+             revoked);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+                              TALER_EXCHANGEDB_DenominationsCallback cb,
+                              void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_end
+  };
+  struct DenomsIteratorContext dic = {
+    .cb = cb,
+    .cb_cls = cb_cls,
+    .pg = pg
+  };
+
+  PREPARE (pg,
+           "select_denominations",
+           "SELECT"
+           " denominations_serial"
+           ",denominations.master_sig"
+           ",denom_revocations_serial_id IS NOT NULL AS revoked"
+           ",valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin"              /* value of this denom */
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",denom_type"
+           ",age_mask"
+           ",denom_pub"
+           " FROM denominations"
+           " LEFT JOIN "
+           "   denomination_revocations USING (denominations_serial);");
+  return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "select_denominations",
+                                               params,
+                                               &dominations_cb_helper,
+                                               &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denominations.h 
b/src/exchangedb/pg_iterate_denominations.h
new file mode 100644
index 0000000..9f59fc8
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.h
@@ -0,0 +1,44 @@
+/*
+   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_iterate_denominations.h
+ * @brief implementation of the iterate_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATIONS_H
+#define PG_ITERATE_DENOMINATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every known denomination key (revoked
+ * and non-revoked) that has been signed by the master key. Runs in its own
+ * read-only transaction.
+ *
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+                              TALER_EXCHANGEDB_DenominationsCallback cb,
+                              void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_denomination_key.c 
b/src/exchangedb/pg_lookup_denomination_key.c
new file mode 100644
index 0000000..20eb703
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.c
@@ -0,0 +1,83 @@
+/*
+   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_lookup_denomination_key.c
+ * @brief Implementation of the lookup_denomination_key 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_lookup_denomination_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+  void *cls,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                     &meta->start),
+    GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+                                     &meta->expire_withdraw),
+    GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+                                     &meta->expire_deposit),
+    GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                     &meta->expire_legal),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+                                 &meta->value),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+                                 &meta->fees.withdraw),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+                                 &meta->fees.deposit),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+                                 &meta->fees.refresh),
+    TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+                                 &meta->fees.refund),
+    GNUNET_PQ_result_spec_uint32 ("age_mask",
+                                  &meta->age_mask.bits),
+    GNUNET_PQ_result_spec_end
+  };
+
+  /* used in #postgres_lookup_denomination_key() */
+  PREPARE (pg,
+           "lookup_denomination_key",
+           "SELECT"
+           " valid_from"
+           ",expire_withdraw"
+           ",expire_deposit"
+           ",expire_legal"
+           ",coin"
+           ",fee_withdraw"
+           ",fee_deposit"
+           ",fee_refresh"
+           ",fee_refund"
+           ",age_mask"
+           " FROM denominations"
+           " WHERE denom_pub_hash=$1;");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_denomination_key",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_lookup_denomination_key.h 
b/src/exchangedb/pg_lookup_denomination_key.h
new file mode 100644
index 0000000..b7317ac
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.h
@@ -0,0 +1,41 @@
+/*
+   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_lookup_denomination_key.h
+ * @brief implementation of the lookup_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_DENOMINATION_KEY_H
+#define PG_LOOKUP_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup information about current denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param[out] meta set to various meta data about the key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+  void *cls,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_signing_key.c 
b/src/exchangedb/pg_lookup_signing_key.c
new file mode 100644
index 0000000..3803d11
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.c
@@ -0,0 +1,64 @@
+/*
+   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_lookup_signing_key.c
+ * @brief Implementation of the lookup_signing_key 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_lookup_signing_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_timestamp ("valid_from",
+                                     &meta->start),
+    GNUNET_PQ_result_spec_timestamp ("expire_sign",
+                                     &meta->expire_sign),
+    GNUNET_PQ_result_spec_timestamp ("expire_legal",
+                                     &meta->expire_legal),
+    GNUNET_PQ_result_spec_end
+  };
+
+
+  PREPARE (pg,
+           "lookup_signing_key",
+           "SELECT"
+           " valid_from"
+           ",expire_sign"
+           ",expire_legal"
+           " FROM exchange_sign_keys"
+           " WHERE exchange_pub=$1");
+
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_signing_key",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_lookup_signing_key.h 
b/src/exchangedb/pg_lookup_signing_key.h
new file mode 100644
index 0000000..487d60d
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.h
@@ -0,0 +1,42 @@
+/*
+   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_lookup_signing_key.h
+ * @brief implementation of the lookup_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNING_KEY_H
+#define PG_LOOKUP_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup signing key meta data.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param[out] meta meta data about @a exchange_pub
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+#endif
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.c 
b/src/exchangedb/pg_lookup_signkey_revocation.c
new file mode 100644
index 0000000..056ecdd
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.c
@@ -0,0 +1,59 @@
+/*
+   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_lookup_signkey_revocation.c
+ * @brief Implementation of the lookup_signkey_revocation 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_lookup_signkey_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                          master_sig),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "lookup_signkey_revocation",
+           "SELECT "
+           " master_sig"
+           " FROM signkey_revocations"
+           " WHERE esk_serial="
+           "   (SELECT esk_serial"
+           "      FROM exchange_sign_keys"
+           "     WHERE exchange_pub=$1);");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "lookup_signkey_revocation",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.h 
b/src/exchangedb/pg_lookup_signkey_revocation.h
new file mode 100644
index 0000000..de0fb1d
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.h
@@ -0,0 +1,42 @@
+/*
+   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_lookup_signkey_revocation.h
+ * @brief implementation of the lookup_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNKEY_REVOCATION_H
+#define PG_LOOKUP_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key
+ * @param[out] master_sig set to signature affirming the revocation (if 
revoked)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_preflight.c b/src/exchangedb/pg_preflight.c
new file mode 100644
index 0000000..4533c9a
--- /dev/null
+++ b/src/exchangedb/pg_preflight.c
@@ -0,0 +1,69 @@
+/*
+   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_preflight.c
+ * @brief Implementation of the preflight 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_preflight.h"
+#include "pg_helper.h"
+
+
+/**
+ * Connect to the database if the connection does not exist yet.
+ *
+ * @param pg the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg);
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("ROLLBACK"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  if (GNUNET_OK !=
+      TEH_PG_internal_setup (pg))
+    return GNUNET_SYSERR;
+  if (NULL == pg->transaction_name)
+    return GNUNET_OK; /* all good */
+  if (GNUNET_OK ==
+      GNUNET_PQ_exec_statements (pg->conn,
+                                 es))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "BUG: Preflight check rolled back transaction `%s'!\n",
+                pg->transaction_name);
+  }
+  else
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "BUG: Preflight check failed to rollback transaction `%s'!\n",
+                pg->transaction_name);
+  }
+  pg->transaction_name = NULL;
+  return GNUNET_NO;
+}
diff --git a/src/exchangedb/pg_preflight.h b/src/exchangedb/pg_preflight.h
new file mode 100644
index 0000000..ba994f1
--- /dev/null
+++ b/src/exchangedb/pg_preflight.h
@@ -0,0 +1,44 @@
+/*
+   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_preflight.h
+ * @brief implementation of the preflight function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PREFLIGTH_H
+#define PG_PREFLIGTH_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK if everything is fine
+ *         #GNUNET_NO if a transaction was rolled back
+ *         #GNUNET_SYSERR on hard errors
+ */
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get.c b/src/exchangedb/pg_reserves_get.c
new file mode 100644
index 0000000..cae4764
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.c
@@ -0,0 +1,61 @@
+/*
+   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_reserves_get.c
+ * @brief Implementation of the reserves_get 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_reserves_get.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+                     struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    TALER_PQ_result_spec_amount ("current_balance",
+                                 pg->currency,
+                                 &reserve->balance),
+    GNUNET_PQ_result_spec_timestamp ("expiration_date",
+                                     &reserve->expiry),
+    GNUNET_PQ_result_spec_timestamp ("gc_date",
+                                     &reserve->gc),
+    GNUNET_PQ_result_spec_end
+  };
+  /* Used in #postgres_reserves_get() */
+  PREPARE (pg,
+           "reserves_get",
+           "SELECT"
+           " current_balance"
+           ",expiration_date"
+           ",gc_date"
+           " FROM reserves"
+           " WHERE reserve_pub=$1"
+           " LIMIT 1;");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   "reserves_get",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_reserves_get.h b/src/exchangedb/pg_reserves_get.h
new file mode 100644
index 0000000..8a96d53
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.h
@@ -0,0 +1,40 @@
+/*
+   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_reserves_get.h
+ * @brief implementation of the reserves_get function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_H
+#define PG_RESERVES_GET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the summary of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param[in,out] reserve the reserve data.  The public key of the reserve 
should be
+ *          set in this structure; it is used to query the database.  The 
balance
+ *          and expiration are then filled accordingly.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+                     struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get_origin.c 
b/src/exchangedb/pg_reserves_get_origin.c
new file mode 100644
index 0000000..55d3179
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.c
@@ -0,0 +1,57 @@
+/*
+   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_reserves_get_origin.c
+ * @brief Implementation of the reserves_get_origin 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_reserves_get_origin.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+  void *cls,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_PaytoHashP *h_payto)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_auto_from_type ("wire_source_h_payto",
+                                          h_payto),
+    GNUNET_PQ_result_spec_end
+  };
+
+
+  PREPARE (pg,
+           "get_h_wire_source_of_reserve",
+           "SELECT"
+           " wire_source_h_payto"
+           " FROM reserves_in"
+           " WHERE reserve_pub=$1");
+  return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                   
"get_h_wire_source_of_reserve",
+                                                   params,
+                                                   rs);
+}
diff --git a/src/exchangedb/pg_reserves_get_origin.h 
b/src/exchangedb/pg_reserves_get_origin.h
new file mode 100644
index 0000000..22085d8
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.h
@@ -0,0 +1,41 @@
+/*
+   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_reserves_get_origin.h
+ * @brief implementation of the reserves_get_origin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_ORIGIN_H
+#define PG_RESERVES_GET_ORIGIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the origin of funds of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] h_payto set to hash of the wire source payto://-URI
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+  void *cls,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_in_insert.c 
b/src/exchangedb/pg_reserves_in_insert.c
new file mode 100644
index 0000000..1b85404
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.c
@@ -0,0 +1,372 @@
+/*
+   This file is part of TALER
+   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
+   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_reserves_in_insert.c
+ * @brief Implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_preflight.h"
+#include "pg_rollback.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_event_notify.h"
+
+
+/**
+ * Generate event notification for the reserve change.
+ *
+ * @param reserve_pub reserve to notfiy on
+ * @return string to pass to postgres for the notification
+ */
+static char *
+compute_notify_on_reserve (const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+  struct TALER_ReserveEventP rep = {
+    .header.size = htons (sizeof (rep)),
+    .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
+    .reserve_pub = *reserve_pub
+  };
+
+  return GNUNET_PG_get_event_notify_channel (&rep.header);
+}
+
+
+/**
+ * Closure for our helper_cb()
+ */
+struct Context
+{
+  /**
+   * Array of reserve UUIDs to initialize.
+   */
+  uint64_t *reserve_uuids;
+
+  /**
+   * Array with entries set to 'true' for duplicate transactions.
+   */
+  bool *transaction_duplicates;
+
+  /**
+   * Array with entries set to 'true' for rows with conflicts.
+   */
+  bool *conflicts;
+
+  /**
+   * Set to #GNUNET_SYSERR on failures.
+   */
+  enum GNUNET_GenericReturnValue status;
+
+  /**
+   * Single value (no array) set to true if we need
+   * to follow-up with an update.
+   */
+  bool needs_update;
+};
+
+
+/**
+ * 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 Context *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+helper_cb (void *cls,
+           PGresult *result,
+           unsigned int num_results)
+{
+  struct Context *ctx = cls;
+
+  for (unsigned int i = 0; i<num_results; i++)
+  {
+    struct GNUNET_PQ_ResultSpec rs[] = {
+      GNUNET_PQ_result_spec_bool (
+        "transaction_duplicate",
+        &ctx->transaction_duplicates[i]),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_uint64 ("ruuid",
+                                      &ctx->reserve_uuids[i]),
+        &ctx->conflicts[i]),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ctx->status = GNUNET_SYSERR;
+      return;
+    }
+    if (! ctx->transaction_duplicates[i])
+      ctx->needs_update |= ctx->conflicts[i];
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+  void *cls,
+  const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+  unsigned int reserves_length,
+  enum GNUNET_DB_QueryStatus *results)
+{
+  struct PostgresClosure *pg = cls;
+  unsigned int dups = 0;
+
+  struct TALER_PaytoHashP h_paytos[GNUNET_NZL (reserves_length)];
+  char *notify_s[GNUNET_NZL (reserves_length)];
+  struct TALER_ReservePublicKeyP reserve_pubs[GNUNET_NZL (reserves_length)];
+  struct TALER_Amount balances[GNUNET_NZL (reserves_length)];
+  struct GNUNET_TIME_Timestamp execution_times[GNUNET_NZL (reserves_length)];
+  const char *sender_account_details[GNUNET_NZL (reserves_length)];
+  const char *exchange_account_names[GNUNET_NZL (reserves_length)];
+  uint64_t wire_references[GNUNET_NZL (reserves_length)];
+  uint64_t reserve_uuids[GNUNET_NZL (reserves_length)];
+  bool transaction_duplicates[GNUNET_NZL (reserves_length)];
+  bool conflicts[GNUNET_NZL (reserves_length)];
+  struct GNUNET_TIME_Timestamp reserve_expiration
+    = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
+  struct GNUNET_TIME_Timestamp gc
+    = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+  enum GNUNET_DB_QueryStatus qs;
+  bool need_update;
+
+  for (unsigned int i = 0; i<reserves_length; i++)
+  {
+    const struct TALER_EXCHANGEDB_ReserveInInfo *reserve = &reserves[i];
+
+    TALER_payto_hash (reserve->sender_account_details,
+                      &h_paytos[i]);
+    notify_s[i] = compute_notify_on_reserve (reserve->reserve_pub);
+    reserve_pubs[i] = *reserve->reserve_pub;
+    balances[i] = *reserve->balance;
+    execution_times[i] = reserve->execution_time;
+    sender_account_details[i] = reserve->sender_account_details;
+    exchange_account_names[i] = reserve->exchange_account_name;
+    wire_references[i] = reserve->wire_reference;
+  }
+
+  /* NOTE: kind-of pointless to explicitly start a transaction here... */
+  if (GNUNET_OK !=
+      TEH_PG_preflight (pg))
+  {
+    GNUNET_break (0);
+    qs = GNUNET_DB_STATUS_HARD_ERROR;
+    goto finished;
+  }
+  if (GNUNET_OK !=
+      TEH_PG_start_read_committed (pg,
+                                   "READ_COMMITED"))
+  {
+    GNUNET_break (0);
+    qs = GNUNET_DB_STATUS_HARD_ERROR;
+    goto finished;
+  }
+  PREPARE (pg,
+           "reserves_insert_with_array",
+           "SELECT"
+           " transaction_duplicate"
+           ",ruuid"
+           " FROM exchange_do_array_reserves_insert"
+           " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+  {
+    struct GNUNET_PQ_QueryParam params[] = {
+      GNUNET_PQ_query_param_timestamp (&gc),
+      GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+      GNUNET_PQ_query_param_array_auto_from_type (reserves_length,
+                                                  reserve_pubs,
+                                                  pg->conn),
+      GNUNET_PQ_query_param_array_uint64 (reserves_length,
+                                          wire_references,
+                                          pg->conn),
+      TALER_PQ_query_param_array_amount (
+        reserves_length,
+        balances,
+        pg->conn),
+      GNUNET_PQ_query_param_array_ptrs_string (
+        reserves_length,
+        (const char **) exchange_account_names,
+        pg->conn),
+      GNUNET_PQ_query_param_array_timestamp (
+        reserves_length,
+        execution_times,
+        pg->conn),
+      GNUNET_PQ_query_param_array_auto_from_type (
+        reserves_length,
+        h_paytos,
+        pg->conn),
+      GNUNET_PQ_query_param_array_ptrs_string (
+        reserves_length,
+        (const char **) sender_account_details,
+        pg->conn),
+      GNUNET_PQ_query_param_array_ptrs_string (
+        reserves_length,
+        (const char **) notify_s,
+        pg->conn),
+      GNUNET_PQ_query_param_end
+    };
+    struct Context ctx = {
+      .reserve_uuids = reserve_uuids,
+      .transaction_duplicates = transaction_duplicates,
+      .conflicts = conflicts,
+      .needs_update = false,
+      .status = GNUNET_OK
+    };
+
+    qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+                                               "reserves_insert_with_array",
+                                               params,
+                                               &helper_cb,
+                                               &ctx);
+    if ( (qs < 0) ||
+         (GNUNET_OK != ctx.status) )
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to insert into reserves (%d)\n",
+                  qs);
+      goto finished;
+    }
+    need_update = ctx.needs_update;
+  }
+
+  {
+    enum GNUNET_DB_QueryStatus cs;
+
+    cs = TEH_PG_commit (pg);
+    if (cs < 0)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to commit\n");
+      qs = cs;
+      goto finished;
+    }
+  }
+
+  for (unsigned int i = 0; i<reserves_length; i++)
+  {
+    if (transaction_duplicates[i])
+      dups++;
+    results[i] = transaction_duplicates[i]
+      ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+      : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+  }
+
+  if (! need_update)
+  {
+    qs = reserves_length;
+    goto finished;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Reserve update needed for some reserves in the batch\n");
+  PREPARE (pg,
+           "reserves_update",
+           "SELECT"
+           " out_duplicate AS duplicate "
+           "FROM exchange_do_batch_reserves_update"
+           " ($1,$2,$3,$4,$5,$6,$7);");
+
+  if (GNUNET_OK !=
+      TEH_PG_start (pg,
+                    "reserve-insert-continued"))
+  {
+    GNUNET_break (0);
+    qs = GNUNET_DB_STATUS_HARD_ERROR;
+    goto finished;
+  }
+
+  for (unsigned int i = 0; i<reserves_length; i++)
+  {
+    if (transaction_duplicates[i])
+      continue;
+    if (! conflicts[i])
+      continue;
+    {
+      bool duplicate;
+      struct GNUNET_PQ_QueryParam params[] = {
+        GNUNET_PQ_query_param_auto_from_type (&reserve_pubs[i]),
+        GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+        GNUNET_PQ_query_param_uint64 (&wire_references[i]),
+        TALER_PQ_query_param_amount (pg->conn,
+                                     &balances[i]),
+        GNUNET_PQ_query_param_string (exchange_account_names[i]),
+        GNUNET_PQ_query_param_auto_from_type (&h_paytos[i]),
+        GNUNET_PQ_query_param_string (notify_s[i]),
+        GNUNET_PQ_query_param_end
+      };
+      struct GNUNET_PQ_ResultSpec rs[] = {
+        GNUNET_PQ_result_spec_bool ("duplicate",
+                                    &duplicate),
+        GNUNET_PQ_result_spec_end
+      };
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+                                                     "reserves_update",
+                                                     params,
+                                                     rs);
+      if (qs < 0)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    "Failed to update reserves (%d)\n",
+                    qs);
+        results[i] = qs;
+        goto finished;
+      }
+      results[i] = duplicate
+          ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+          : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+    }
+  }
+  {
+    enum GNUNET_DB_QueryStatus cs;
+
+    cs = TEH_PG_commit (pg);
+    if (cs < 0)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to commit\n");
+      qs = cs;
+      goto finished;
+    }
+  }
+finished:
+  for (unsigned int i = 0; i<reserves_length; i++)
+    GNUNET_free (notify_s[i]);
+  if (qs < 0)
+    return qs;
+  GNUNET_PQ_event_do_poll (pg->conn);
+  if (0 != dups)
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "%u/%u duplicates among incoming transactions. Try increasing 
WIREWATCH_IDLE_SLEEP_INTERVAL in the [exchange] configuration section (if this 
happens a lot).\n",
+                dups,
+                reserves_length);
+  return qs;
+}
diff --git a/src/exchangedb/pg_reserves_in_insert.h 
b/src/exchangedb/pg_reserves_in_insert.h
new file mode 100644
index 0000000..938df3a
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.h
@@ -0,0 +1,47 @@
+/*
+   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_reserves_in_insert.h
+ * @brief implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_IN_INSERT_H
+#define PG_RESERVES_IN_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert an incoming transaction into reserves.  New reserves are also
+ * created through this function. Runs its own transaction(s).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserves array of reserves to insert
+ * @param reserves_length length of the @a reserves array
+ * @param[out] results set to query status per reserve, must be of length @a 
reserves_length
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+  void *cls,
+  const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+  unsigned int reserves_length,
+  enum GNUNET_DB_QueryStatus *results);
+
+
+#endif
diff --git a/src/exchangedb/pg_reserves_update.c 
b/src/exchangedb/pg_reserves_update.c
new file mode 100644
index 0000000..bfd32c6
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.c
@@ -0,0 +1,53 @@
+/*
+   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_reserves_update.c
+ * @brief Implementation of the reserves_update 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_reserves_update.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+                        const struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_timestamp (&reserve->expiry),
+    GNUNET_PQ_query_param_timestamp (&reserve->gc),
+    TALER_PQ_query_param_amount (pg->conn,
+                                 &reserve->balance),
+    GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+    GNUNET_PQ_query_param_end
+  };
+
+  PREPARE (pg,
+           "reserve_update",
+           "UPDATE reserves"
+           " SET"
+           " expiration_date=$1"
+           ",gc_date=$2"
+           ",current_balance=$3"
+           " WHERE reserve_pub=$4;");
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "reserve_update",
+                                             params);
+}
diff --git a/src/exchangedb/pg_reserves_update.h 
b/src/exchangedb/pg_reserves_update.h
new file mode 100644
index 0000000..24cf671
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.h
@@ -0,0 +1,40 @@
+/*
+   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_reserves_update.h
+ * @brief implementation of the reserves_update function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_UPDATE_H
+#define PG_RESERVES_UPDATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Updates a reserve with the data from the given reserve structure.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve the reserve structure whose data will be used to update the
+ *          corresponding record in the database.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+                        const struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_rollback.c b/src/exchangedb/pg_rollback.c
new file mode 100644
index 0000000..3610487
--- /dev/null
+++ b/src/exchangedb/pg_rollback.c
@@ -0,0 +1,50 @@
+/*
+   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_rollback.c
+ * @brief Implementation of the rollback 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_rollback.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_rollback (void *cls)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("ROLLBACK"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  if (NULL == pg->transaction_name)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Skipping rollback, no transaction active\n");
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Rolling back transaction\n");
+  GNUNET_break (GNUNET_OK ==
+                GNUNET_PQ_exec_statements (pg->conn,
+                                           es));
+  pg->transaction_name = NULL;
+}
diff --git a/src/exchangedb/pg_rollback.h b/src/exchangedb/pg_rollback.h
new file mode 100644
index 0000000..ddb9e41
--- /dev/null
+++ b/src/exchangedb/pg_rollback.h
@@ -0,0 +1,36 @@
+/*
+   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_rollback.h
+ * @brief implementation of the rollback function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ROLLBACK_H
+#define PG_ROLLBACK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+void
+TEH_PG_rollback (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_start.c b/src/exchangedb/pg_start.c
new file mode 100644
index 0000000..de5d698
--- /dev/null
+++ b/src/exchangedb/pg_start.c
@@ -0,0 +1,56 @@
+/*
+   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_start.c
+ * @brief Implementation of the start 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_preflight.h"
+#include "pg_start.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+              const char *name)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  GNUNET_assert (NULL != name);
+  if (GNUNET_SYSERR ==
+      TEH_PG_preflight (pg))
+    return GNUNET_SYSERR;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Starting transaction `%s'\n",
+              name);
+  if (GNUNET_OK !=
+      GNUNET_PQ_exec_statements (pg->conn,
+                                 es))
+  {
+    TALER_LOG_ERROR ("Failed to start transaction\n");
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  pg->transaction_name = name;
+  return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start.h b/src/exchangedb/pg_start.h
new file mode 100644
index 0000000..0a3bb9e
--- /dev/null
+++ b/src/exchangedb/pg_start.h
@@ -0,0 +1,40 @@
+/*
+   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_start.h
+ * @brief implementation of the start function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_H
+#define PG_START_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ *             must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+              const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_committed.c 
b/src/exchangedb/pg_start_read_committed.c
new file mode 100644
index 0000000..1c8248a
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.c
@@ -0,0 +1,56 @@
+/*
+   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_start_read_committed.c
+ * @brief Implementation of the start_read_committed 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_start_read_committed.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+                             const char *name)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ 
COMMITTED"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  GNUNET_assert (NULL != name);
+  if (GNUNET_SYSERR ==
+      TEH_PG_preflight (pg))
+    return GNUNET_SYSERR;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Starting READ COMMITTED transaction `%s`\n",
+              name);
+  if (GNUNET_OK !=
+      GNUNET_PQ_exec_statements (pg->conn,
+                                 es))
+  {
+    TALER_LOG_ERROR ("Failed to start transaction\n");
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  pg->transaction_name = name;
+  return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_committed.h 
b/src/exchangedb/pg_start_read_committed.h
new file mode 100644
index 0000000..08b60e6
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.h
@@ -0,0 +1,39 @@
+/*
+   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_start_read_committed.h
+ * @brief implementation of the start_read_committed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_COMMITTED_H
+#define PG_START_READ_COMMITTED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Start a READ COMMITTED transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ *             must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+                             const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_only.c 
b/src/exchangedb/pg_start_read_only.c
new file mode 100644
index 0000000..741d94b
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.c
@@ -0,0 +1,57 @@
+/*
+   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_start_read_only.c
+ * @brief Implementation of the start_read_only 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_start_read_only.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+                        const char *name)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_ExecuteStatement es[] = {
+    GNUNET_PQ_make_execute (
+      "START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY"),
+    GNUNET_PQ_EXECUTE_STATEMENT_END
+  };
+
+  GNUNET_assert (NULL != name);
+  if (GNUNET_SYSERR ==
+      TEH_PG_preflight (pg))
+    return GNUNET_SYSERR;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Starting READ ONLY transaction `%s`\n",
+              name);
+  if (GNUNET_OK !=
+      GNUNET_PQ_exec_statements (pg->conn,
+                                 es))
+  {
+    TALER_LOG_ERROR ("Failed to start transaction\n");
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  pg->transaction_name = name;
+  return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_only.h 
b/src/exchangedb/pg_start_read_only.h
new file mode 100644
index 0000000..bf639c1
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.h
@@ -0,0 +1,40 @@
+/*
+   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_start_read_only.h
+ * @brief implementation of the start_read_only function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_ONLY_H
+#define PG_START_READ_ONLY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a READ ONLY serializable transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ *             must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+                        const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_template.c b/src/exchangedb/pg_template.c
new file mode 100644
index 0000000..095d896
--- /dev/null
+++ b/src/exchangedb/pg_template.c
@@ -0,0 +1,26 @@
+/*
+   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_template.c
+ * @brief Implementation of the template 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_template.h"
+#include "pg_helper.h"
diff --git a/src/exchangedb/pg_template.h b/src/exchangedb/pg_template.h
new file mode 100644
index 0000000..88bb930
--- /dev/null
+++ b/src/exchangedb/pg_template.h
@@ -0,0 +1,29 @@
+/*
+   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_template.h
+ * @brief implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEMPLATE_H
+#define PG_TEMPLATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+#endif
diff --git a/src/exchangedb/pg_template.sh b/src/exchangedb/pg_template.sh
new file mode 100755
index 0000000..73bd7e9
--- /dev/null
+++ b/src/exchangedb/pg_template.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Instantiates pg_template for a particular function.
+
+for n in $*
+do
+    NCAPS=`echo $n | tr a-z A-Z`
+    if test ! -e pg_$n.c
+    then
+        cat pg_template.c | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > 
pg_$n.c
+        cat pg_template.h | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > 
pg_$n.h
+        echo "  plugin->$n\n    = &TEH_PG_$n;" >> tmpl.c
+        echo "#include \"pg_$n.h\"" >> tmpl.inc
+        echo "  pg_$n.h pg_$n.c \\" >> tmpl.am
+    fi
+done
+
+echo "Add lines from tmpl.am to Makefile.am"
+echo "Add lines from tmpl.inc to plugin_exchangedb_postgres.c at the beginning"
+echo "Add lines from tmpl.c to plugin_exchangedb_postgres.c at the end"
diff --git a/src/exchangedb/plugin_exchangedb_common.c 
b/src/exchangedb/plugin_exchangedb_common.c
new file mode 100644
index 0000000..562710e
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_common.c
@@ -0,0 +1,199 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015, 2016, 2020 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/plugin_exchangedb_common.c
+ * @brief Functions shared across plugins, this file is meant to be
+ *        included in each plugin.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "plugin_exchangedb_common.h"
+
+
+void
+TEH_COMMON_free_reserve_history (
+  void *cls,
+  struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+  (void) cls;
+  while (NULL != rh)
+  {
+    switch (rh->type)
+    {
+    case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+      {
+        struct TALER_EXCHANGEDB_BankTransfer *bt;
+
+        bt = rh->details.bank;
+        GNUNET_free (bt->sender_account_details);
+        GNUNET_free (bt);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
+      {
+        struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
+
+        cbc = rh->details.withdraw;
+        TALER_blinded_denom_sig_free (&cbc->sig);
+        GNUNET_free (cbc);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+      {
+        struct TALER_EXCHANGEDB_Recoup *recoup;
+
+        recoup = rh->details.recoup;
+        TALER_denom_sig_free (&recoup->coin.denom_sig);
+        GNUNET_free (recoup);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+      {
+        struct TALER_EXCHANGEDB_ClosingTransfer *closing;
+
+        closing = rh->details.closing;
+        GNUNET_free (closing->receiver_account_details);
+        GNUNET_free (closing);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+      {
+        struct TALER_EXCHANGEDB_PurseMerge *merge;
+
+        merge = rh->details.merge;
+        GNUNET_free (merge);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+      {
+        struct TALER_EXCHANGEDB_HistoryRequest *history;
+
+        history = rh->details.history;
+        GNUNET_free (history);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+      {
+        struct TALER_EXCHANGEDB_OpenRequest *or;
+
+        or = rh->details.open_request;
+        GNUNET_free (or);
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+      {
+        struct TALER_EXCHANGEDB_CloseRequest *cr;
+
+        cr = rh->details.close_request;
+        GNUNET_free (cr);
+        break;
+      }
+    }
+    {
+      struct TALER_EXCHANGEDB_ReserveHistory *next;
+
+      next = rh->next;
+      GNUNET_free (rh);
+      rh = next;
+    }
+  }
+}
+
+
+void
+TEH_COMMON_free_coin_transaction_list (
+  void *cls,
+  struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+  (void) cls;
+  while (NULL != tl)
+  {
+    switch (tl->type)
+    {
+    case TALER_EXCHANGEDB_TT_DEPOSIT:
+      {
+        struct TALER_EXCHANGEDB_DepositListEntry *deposit;
+
+        deposit = tl->details.deposit;
+        GNUNET_free (deposit->receiver_wire_account);
+        GNUNET_free (deposit);
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_MELT:
+      GNUNET_free (tl->details.melt);
+      break;
+    case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+      {
+        struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
+
+        rr = tl->details.old_coin_recoup;
+        TALER_denom_sig_free (&rr->coin.denom_sig);
+        GNUNET_free (rr);
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_REFUND:
+      GNUNET_free (tl->details.refund);
+      break;
+    case TALER_EXCHANGEDB_TT_RECOUP:
+      GNUNET_free (tl->details.recoup);
+      break;
+    case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+      {
+        struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
+
+        rr = tl->details.recoup_refresh;
+        TALER_denom_sig_free (&rr->coin.denom_sig);
+        GNUNET_free (rr);
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+      {
+        struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+
+        deposit = tl->details.purse_deposit;
+        GNUNET_free (deposit->exchange_base_url);
+        GNUNET_free (deposit);
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+      {
+        struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+
+        prefund = tl->details.purse_refund;
+        GNUNET_free (prefund);
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+      {
+        struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+
+        role = tl->details.reserve_open;
+        GNUNET_free (role);
+        break;
+      }
+    }
+    {
+      struct TALER_EXCHANGEDB_TransactionList *next;
+
+      next = tl->next;
+      GNUNET_free (tl);
+      tl = next;
+    }
+  }
+}
+
+
+/* end of plugin_exchangedb_common.c */
diff --git a/src/exchangedb/plugin_exchangedb_common.h 
b/src/exchangedb/plugin_exchangedb_common.h
new file mode 100644
index 0000000..0355c44
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_common.h
@@ -0,0 +1,51 @@
+/*
+   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 plugin_exchangedb_common.h
+ * @brief implementation of database-independent functions
+ * @author Christian Grothoff
+ */
+#ifndef PLUGIN_EXCHANGEDB_COMMON_H
+#define PLUGIN_EXCHANGEDB_COMMON_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Free memory associated with the given reserve history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] rh history to free.
+ */
+void
+TEH_COMMON_free_reserve_history (
+  void *cls,
+  struct TALER_EXCHANGEDB_ReserveHistory *rh);
+
+
+/**
+ * Free linked list of transactions.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] tl list to free
+ */
+void
+TEH_COMMON_free_coin_transaction_list (
+  void *cls,
+  struct TALER_EXCHANGEDB_TransactionList *tl);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c 
b/src/exchangedb/plugin_exchangedb_postgres.c
new file mode 100644
index 0000000..067e859
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -0,0 +1,806 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2014--2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * @file plugin_exchangedb_postgres.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Florian Dold
+ * @author Christian Grothoff
+ * @author Sree Harsha Totakura
+ * @author Marcello Stanisci
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include <poll.h>
+#include <pthread.h>
+#include <libpq-fe.h>
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_delete_aggregation_transient.h"
+#include "pg_get_link_data.h"
+#include "pg_helper.h"
+#include "pg_do_reserve_open.h"
+#include "pg_do_withdraw.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_get_purse_request.h"
+#include "pg_get_reserve_history.h"
+#include "pg_get_unfinished_close_requests.h"
+#include "pg_insert_close_request.h"
+#include "pg_insert_records_by_table.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_iterate_kyc_reference.h"
+#include "pg_iterate_reserve_close_info.h"
+#include "pg_lookup_records_by_table.h"
+#include "pg_lookup_serial_by_table.h"
+#include "pg_select_account_merges_above_serial_id.h"
+#include "pg_select_aml_threshold.h"
+#include "pg_select_all_purse_decisions_above_serial_id.h"
+#include "pg_select_purse.h"
+#include "pg_select_purse_deposits_above_serial_id.h"
+#include "pg_select_purse_merges_above_serial_id.h"
+#include "pg_select_purse_requests_above_serial_id.h"
+#include "pg_select_reserve_close_info.h"
+#include "pg_select_reserve_closed_above_serial_id.h"
+#include "pg_select_reserve_open_above_serial_id.h"
+#include "pg_insert_purse_request.h"
+#include "pg_iterate_active_signkeys.h"
+#include "pg_preflight.h"
+#include "pg_commit.h"
+#include "pg_drop_tables.h"
+#include "pg_select_satisfied_kyc_processes.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_kyc_provider_account_lookup.h"
+#include "pg_lookup_kyc_requirement_by_row.h"
+#include "pg_insert_kyc_requirement_for_account.h"
+#include "pg_lookup_kyc_process_by_account.h"
+#include "pg_update_kyc_process_by_row.h"
+#include "pg_insert_kyc_requirement_process.h"
+#include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_profit_drains_set_finished.h"
+#include "pg_profit_drains_get_pending.h"
+#include "pg_get_drain_profit.h"
+#include "pg_get_purse_deposit.h"
+#include "pg_insert_contract.h"
+#include "pg_select_contract.h"
+#include "pg_select_purse_merge.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_insert_drain_profit.h"
+#include "pg_do_reserve_purse.h"
+#include "pg_lookup_global_fee_by_time.h"
+#include "pg_do_purse_deposit.h"
+#include "pg_activate_signing_key.h"
+#include "pg_update_auditor.h"
+#include "pg_begin_revolving_shard.h"
+#include "pg_get_extension_manifest.h"
+#include "pg_insert_history_request.h"
+#include "pg_do_purse_delete.h"
+#include "pg_do_purse_merge.h"
+#include "pg_start_read_committed.h"
+#include "pg_start_read_only.h"
+#include "pg_insert_denomination_info.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_lookup_wire_fee_by_time.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+#include "pg_create_tables.h"
+#include "pg_event_listen.h"
+#include "pg_event_listen_cancel.h"
+#include "pg_event_notify.h"
+#include "pg_get_denomination_info.h"
+#include "pg_iterate_denomination_info.h"
+#include "pg_iterate_denominations.h"
+#include "pg_iterate_active_auditors.h"
+#include "pg_iterate_auditor_denominations.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_get_origin.h"
+#include "pg_drain_kyc_alert.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_get_withdraw_info.h"
+#include "pg_get_age_withdraw.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_do_age_withdraw.h"
+#include "pg_get_policy_details.h"
+#include "pg_persist_policy_details.h"
+#include "pg_do_deposit.h"
+#include "pg_add_policy_fulfillment_proof.h"
+#include "pg_do_melt.h"
+#include "pg_do_refund.h"
+#include "pg_do_recoup.h"
+#include "pg_do_recoup_refresh.h"
+#include "pg_get_reserve_balance.h"
+#include "pg_count_known_coins.h"
+#include "pg_ensure_coin_known.h"
+#include "pg_get_known_coin.h"
+#include "pg_get_coin_denomination.h"
+#include "pg_have_deposit2.h"
+#include "pg_aggregate.h"
+#include "pg_create_aggregation_transient.h"
+#include "pg_select_aggregation_transient.h"
+#include "pg_find_aggregation_transient.h"
+#include "pg_update_aggregation_transient.h"
+#include "pg_get_ready_deposit.h"
+#include "pg_insert_refund.h"
+#include "pg_select_refunds_by_coin.h"
+#include "pg_get_melt.h"
+#include "pg_insert_refresh_reveal.h"
+#include "pg_get_refresh_reveal.h"
+#include "pg_lookup_wire_transfer.h"
+#include "pg_lookup_transfer_by_deposit.h"
+#include "pg_insert_wire_fee.h"
+#include "pg_insert_global_fee.h"
+#include "pg_get_wire_fee.h"
+#include "pg_get_global_fee.h"
+#include "pg_get_global_fees.h"
+#include "pg_insert_reserve_closed.h"
+#include "pg_wire_prepare_data_insert.h"
+#include "pg_wire_prepare_data_mark_finished.h"
+#include "pg_wire_prepare_data_mark_failed.h"
+#include "pg_wire_prepare_data_get.h"
+#include "pg_start_deferred_wire_out.h"
+#include "pg_store_wire_transfer_out.h"
+#include "pg_gc.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"
+#include "pg_select_refreshes_above_serial_id.h"
+#include "pg_select_refunds_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id_by_account.h"
+#include "pg_select_withdrawals_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id_by_account.h"
+#include "pg_select_recoup_above_serial_id.h"
+#include "pg_select_recoup_refresh_above_serial_id.h"
+#include "pg_get_reserve_by_h_blind.h"
+#include "pg_get_old_coin_by_h_blind.h"
+#include "pg_insert_denomination_revocation.h"
+#include "pg_get_denomination_revocation.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"
+#include "pg_lookup_wire_timestamp.h"
+#include "pg_insert_wire.h"
+#include "pg_update_wire.h"
+#include "pg_get_wire_accounts.h"
+#include "pg_get_wire_fees.h"
+#include "pg_insert_signkey_revocation.h"
+#include "pg_lookup_signkey_revocation.h"
+#include "pg_lookup_denomination_key.h"
+#include "pg_insert_auditor_denom_sig.h"
+#include "pg_select_auditor_denom_sig.h"
+#include "pg_add_denomination_key.h"
+#include "pg_lookup_signing_key.h"
+#include "pg_begin_shard.h"
+#include "pg_abort_shard.h"
+#include "pg_complete_shard.h"
+#include "pg_release_revolving_shard.h"
+#include "pg_delete_shard_locks.h"
+#include "pg_set_extension_manifest.h"
+#include "pg_insert_partner.h"
+#include "pg_expire_purse.h"
+#include "pg_select_purse_by_merge_pub.h"
+#include "pg_set_purse_balance.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_compute_shard.h"
+#include "pg_insert_kyc_attributes.h"
+#include "pg_select_similar_kyc_attributes.h"
+#include "pg_select_kyc_attributes.h"
+#include "pg_insert_aml_officer.h"
+#include "pg_test_aml_officer.h"
+#include "pg_lookup_aml_officer.h"
+#include "pg_trigger_aml_process.h"
+#include "pg_select_aml_process.h"
+#include "pg_select_aml_history.h"
+#include "pg_insert_aml_decision.h"
+#include "pg_batch_ensure_coin_known.h"
+
+/**
+ * Set to 1 to enable Postgres auto_explain module. This will
+ * slow down things a _lot_, but also provide extensive logging
+ * in the Postgres database logger for performance analysis.
+ */
+#define AUTO_EXPLAIN 1
+
+
+/**
+ * Log a really unexpected PQ error with all the details we can get hold of.
+ *
+ * @param result PQ result object of the PQ operation that failed
+ * @param conn SQL connection that was used
+ */
+#define BREAK_DB_ERR(result,conn) do {                                  \
+    GNUNET_break (0);                                                   \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Database failure: %s/%s/%s/%s/%s",                     \
+                PQresultErrorField (result, PG_DIAG_MESSAGE_PRIMARY),   \
+                PQresultErrorField (result, PG_DIAG_MESSAGE_DETAIL),    \
+                PQresultErrorMessage (result),                          \
+                PQresStatus (PQresultStatus (result)),                  \
+                PQerrorMessage (conn));                                 \
+} while (0)
+
+
+/**
+ * Connect to the database if the connection does not exist yet.
+ *
+ * @param pg the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg)
+{
+  if (NULL == pg->conn)
+  {
+#if AUTO_EXPLAIN
+    /* Enable verbose logging to see where queries do not
+       properly use indices */
+    struct GNUNET_PQ_ExecuteStatement es[] = {
+      GNUNET_PQ_make_try_execute ("LOAD 'auto_explain';"),
+      GNUNET_PQ_make_try_execute ("SET auto_explain.log_min_duration=50;"),
+      GNUNET_PQ_make_try_execute ("SET auto_explain.log_timing=TRUE;"),
+      GNUNET_PQ_make_try_execute ("SET auto_explain.log_analyze=TRUE;"),
+      /* https://wiki.postgresql.org/wiki/Serializable suggests to really
+         force the default to 'serializable' if SSI is to be used. */
+      GNUNET_PQ_make_try_execute (
+        "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL 
SERIALIZABLE;"),
+      GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
+      GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+      GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+      GNUNET_PQ_EXECUTE_STATEMENT_END
+    };
+#else
+    struct GNUNET_PQ_ExecuteStatement es[] = {
+      GNUNET_PQ_make_try_execute (
+        "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL 
SERIALIZABLE;"),
+      GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
+      GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+      GNUNET_PQ_make_try_execute ("SET autocommit=OFF;"),
+      GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+      GNUNET_PQ_EXECUTE_STATEMENT_END
+    };
+#endif
+    struct GNUNET_PQ_Context *db_conn;
+
+    db_conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+                                          "exchangedb-postgres",
+                                          NULL,
+                                          es,
+                                          NULL);
+    if (NULL == db_conn)
+      return GNUNET_SYSERR;
+
+    pg->prep_gen++;
+    pg->conn = db_conn;
+  }
+  if (NULL == pg->transaction_name)
+    GNUNET_PQ_reconnect_if_down (pg->conn);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Initialize Postgres database subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct
+ *         TALER_EXCHANGEDB_Plugin`
+ */
+void *
+libtaler_plugin_exchangedb_postgres_init (void *cls)
+{
+  const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+  struct PostgresClosure *pg;
+  struct TALER_EXCHANGEDB_Plugin *plugin;
+  unsigned long long dpl;
+
+  pg = GNUNET_new (struct PostgresClosure);
+  pg->cfg = cfg;
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "exchangedb-postgres",
+                                               "SQL_DIR",
+                                               &pg->sql_dir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchangedb-postgres",
+                               "SQL_DIR");
+    GNUNET_free (pg);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &pg->exchange_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    GNUNET_free (pg->sql_dir);
+    GNUNET_free (pg);
+    return NULL;
+  }
+  if ( (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_time (cfg,
+                                             "exchangedb",
+                                             "IDLE_RESERVE_EXPIRATION_TIME",
+                                             
&pg->idle_reserve_expiration_time))
+       ||
+       (GNUNET_OK !=
+        GNUNET_CONFIGURATION_get_value_time (cfg,
+                                             "exchangedb",
+                                             "LEGAL_RESERVE_EXPIRATION_TIME",
+                                             
&pg->legal_reserve_expiration_time)) )
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchangedb",
+                               "LEGAL/IDLE_RESERVE_EXPIRATION_TIME");
+    GNUNET_free (pg->exchange_url);
+    GNUNET_free (pg->sql_dir);
+    GNUNET_free (pg);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_time (cfg,
+                                           "exchangedb",
+                                           "AGGREGATOR_SHIFT",
+                                           &pg->aggregator_shift))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+                               "exchangedb",
+                               "AGGREGATOR_SHIFT");
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_number (cfg,
+                                             "exchangedb",
+                                             "DEFAULT_PURSE_LIMIT",
+                                             &dpl))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+                               "exchangedb",
+                               "DEFAULT_PURSE_LIMIT");
+    pg->def_purse_limit = 1;
+  }
+  else
+  {
+    pg->def_purse_limit = (uint32_t) dpl;
+  }
+
+  if (GNUNET_OK !=
+      TALER_config_get_currency (cfg,
+                                 &pg->currency))
+  {
+    GNUNET_free (pg->exchange_url);
+    GNUNET_free (pg->sql_dir);
+    GNUNET_free (pg);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      TEH_PG_internal_setup (pg))
+  {
+    GNUNET_free (pg->exchange_url);
+    GNUNET_free (pg->currency);
+    GNUNET_free (pg->sql_dir);
+    GNUNET_free (pg);
+    return NULL;
+  }
+  plugin = GNUNET_new (struct TALER_EXCHANGEDB_Plugin);
+  plugin->cls = pg;
+  plugin->do_reserve_open
+    = &TEH_PG_do_reserve_open;
+  plugin->drop_tables
+    = &TEH_PG_drop_tables;
+  plugin->do_withdraw
+    = &TEH_PG_do_withdraw;
+  plugin->free_coin_transaction_list
+    = &TEH_COMMON_free_coin_transaction_list;
+  plugin->free_reserve_history
+    = &TEH_COMMON_free_reserve_history;
+  plugin->get_coin_transactions
+    = &TEH_PG_get_coin_transactions;
+  plugin->get_expired_reserves
+    = &TEH_PG_get_expired_reserves;
+  plugin->get_purse_request
+    = &TEH_PG_get_purse_request;
+  plugin->get_reserve_history
+    = &TEH_PG_get_reserve_history;
+  plugin->get_reserve_status
+    = &TEH_PG_get_reserve_status;
+  plugin->get_unfinished_close_requests
+    = &TEH_PG_get_unfinished_close_requests;
+  plugin->insert_records_by_table
+    = &TEH_PG_insert_records_by_table;
+  plugin->insert_reserve_open_deposit
+    = &TEH_PG_insert_reserve_open_deposit;
+  plugin->insert_close_request
+    = &TEH_PG_insert_close_request;
+  plugin->delete_aggregation_transient
+    = &TEH_PG_delete_aggregation_transient;
+  plugin->get_link_data
+    = &TEH_PG_get_link_data;
+  plugin->iterate_reserve_close_info
+    = &TEH_PG_iterate_reserve_close_info;
+  plugin->iterate_kyc_reference
+    = &TEH_PG_iterate_kyc_reference;
+  plugin->lookup_records_by_table
+    = &TEH_PG_lookup_records_by_table;
+  plugin->lookup_serial_by_table
+    = &TEH_PG_lookup_serial_by_table;
+  plugin->select_account_merges_above_serial_id
+    = &TEH_PG_select_account_merges_above_serial_id;
+  plugin->select_all_purse_decisions_above_serial_id
+    = &TEH_PG_select_all_purse_decisions_above_serial_id;
+  plugin->select_aml_threshold
+    = &TEH_PG_select_aml_threshold;
+  plugin->select_purse
+    = &TEH_PG_select_purse;
+  plugin->select_purse_deposits_above_serial_id
+    = &TEH_PG_select_purse_deposits_above_serial_id;
+  plugin->select_purse_merges_above_serial_id
+    = &TEH_PG_select_purse_merges_above_serial_id;
+  plugin->select_purse_requests_above_serial_id
+    = &TEH_PG_select_purse_requests_above_serial_id;
+  plugin->select_reserve_close_info
+    = &TEH_PG_select_reserve_close_info;
+  plugin->select_reserve_closed_above_serial_id
+    = &TEH_PG_select_reserve_closed_above_serial_id;
+  plugin->select_reserve_open_above_serial_id
+    = &TEH_PG_select_reserve_open_above_serial_id;
+  plugin->insert_purse_request
+    = &TEH_PG_insert_purse_request;
+  plugin->iterate_active_signkeys
+    = &TEH_PG_iterate_active_signkeys;
+  plugin->commit
+    = &TEH_PG_commit;
+  plugin->preflight
+    = &TEH_PG_preflight;
+  plugin->select_aggregation_amounts_for_kyc_check
+    = &TEH_PG_select_aggregation_amounts_for_kyc_check;
+  plugin->select_satisfied_kyc_processes
+    = &TEH_PG_select_satisfied_kyc_processes;
+  plugin->kyc_provider_account_lookup
+    = &TEH_PG_kyc_provider_account_lookup;
+  plugin->lookup_kyc_requirement_by_row
+    = &TEH_PG_lookup_kyc_requirement_by_row;
+  plugin->insert_kyc_requirement_for_account
+    = &TEH_PG_insert_kyc_requirement_for_account;
+  plugin->lookup_kyc_process_by_account
+    = &TEH_PG_lookup_kyc_process_by_account;
+  plugin->update_kyc_process_by_row
+    = &TEH_PG_update_kyc_process_by_row;
+  plugin->insert_kyc_requirement_process
+    = &TEH_PG_insert_kyc_requirement_process;
+  plugin->select_withdraw_amounts_for_kyc_check
+    = &TEH_PG_select_withdraw_amounts_for_kyc_check;
+  plugin->select_merge_amounts_for_kyc_check
+    = &TEH_PG_select_merge_amounts_for_kyc_check;
+  plugin->profit_drains_set_finished
+    = &TEH_PG_profit_drains_set_finished;
+  plugin->profit_drains_get_pending
+    = &TEH_PG_profit_drains_get_pending;
+  plugin->get_drain_profit
+    = &TEH_PG_get_drain_profit;
+  plugin->get_purse_deposit
+    = &TEH_PG_get_purse_deposit;
+  plugin->insert_contract
+    = &TEH_PG_insert_contract;
+  plugin->select_contract
+    = &TEH_PG_select_contract;
+  plugin->select_purse_merge
+    = &TEH_PG_select_purse_merge;
+  plugin->select_contract_by_purse
+    = &TEH_PG_select_contract_by_purse;
+  plugin->insert_drain_profit
+    = &TEH_PG_insert_drain_profit;
+  plugin->do_reserve_purse
+    = &TEH_PG_do_reserve_purse;
+  plugin->lookup_global_fee_by_time
+    = &TEH_PG_lookup_global_fee_by_time;
+  plugin->do_purse_deposit
+    = &TEH_PG_do_purse_deposit;
+  plugin->activate_signing_key
+    = &TEH_PG_activate_signing_key;
+  plugin->update_auditor
+    = &TEH_PG_update_auditor;
+  plugin->begin_revolving_shard
+    = &TEH_PG_begin_revolving_shard;
+  plugin->get_extension_manifest
+    = &TEH_PG_get_extension_manifest;
+  plugin->insert_history_request
+    = &TEH_PG_insert_history_request;
+  plugin->do_purse_merge
+    = &TEH_PG_do_purse_merge;
+  plugin->do_purse_delete
+    = &TEH_PG_do_purse_delete;
+  plugin->start_read_committed
+    = &TEH_PG_start_read_committed;
+  plugin->start_read_only
+    = &TEH_PG_start_read_only;
+  plugin->insert_denomination_info
+    = &TEH_PG_insert_denomination_info;
+  plugin->do_batch_withdraw_insert
+    = &TEH_PG_do_batch_withdraw_insert;
+  plugin->lookup_wire_fee_by_time
+    = &TEH_PG_lookup_wire_fee_by_time;
+  plugin->start
+    = &TEH_PG_start;
+  plugin->rollback
+    = &TEH_PG_rollback;
+  plugin->create_tables
+    = &TEH_PG_create_tables;
+  plugin->event_listen
+    = &TEH_PG_event_listen;
+  plugin->event_listen_cancel
+    = &TEH_PG_event_listen_cancel;
+  plugin->event_notify
+    = &TEH_PG_event_notify;
+  plugin->get_denomination_info
+    = &TEH_PG_get_denomination_info;
+  plugin->iterate_denomination_info
+    = &TEH_PG_iterate_denomination_info;
+  plugin->iterate_denominations
+    = &TEH_PG_iterate_denominations;
+  plugin->iterate_active_auditors
+    = &TEH_PG_iterate_active_auditors;
+  plugin->iterate_auditor_denominations
+    = &TEH_PG_iterate_auditor_denominations;
+  plugin->reserves_get
+    = &TEH_PG_reserves_get;
+  plugin->reserves_get_origin
+    = &TEH_PG_reserves_get_origin;
+  plugin->drain_kyc_alert
+    = &TEH_PG_drain_kyc_alert;
+  plugin->reserves_in_insert
+    = &TEH_PG_reserves_in_insert;
+  plugin->get_withdraw_info
+    = &TEH_PG_get_withdraw_info;
+  plugin->do_batch_withdraw
+    = &TEH_PG_do_batch_withdraw;
+  plugin->do_age_withdraw
+    = &TEH_PG_do_age_withdraw;
+  plugin->get_age_withdraw
+    = &TEH_PG_get_age_withdraw;
+  plugin->get_policy_details
+    = &TEH_PG_get_policy_details;
+  plugin->persist_policy_details
+    = &TEH_PG_persist_policy_details;
+  plugin->do_deposit
+    = &TEH_PG_do_deposit;
+  plugin->add_policy_fulfillment_proof
+    = &TEH_PG_add_policy_fulfillment_proof;
+  plugin->do_melt
+    = &TEH_PG_do_melt;
+  plugin->do_refund
+    = &TEH_PG_do_refund;
+  plugin->do_recoup
+    = &TEH_PG_do_recoup;
+  plugin->do_recoup_refresh
+    = &TEH_PG_do_recoup_refresh;
+  plugin->get_reserve_balance
+    = &TEH_PG_get_reserve_balance;
+  plugin->count_known_coins
+    = &TEH_PG_count_known_coins;
+  plugin->ensure_coin_known
+    = &TEH_PG_ensure_coin_known;
+  plugin->get_known_coin
+    = &TEH_PG_get_known_coin;
+  plugin->get_coin_denomination
+    = &TEH_PG_get_coin_denomination;
+  plugin->have_deposit2
+    = &TEH_PG_have_deposit2;
+  plugin->aggregate
+    = &TEH_PG_aggregate;
+  plugin->create_aggregation_transient
+    = &TEH_PG_create_aggregation_transient;
+  plugin->select_aggregation_transient
+    = &TEH_PG_select_aggregation_transient;
+  plugin->find_aggregation_transient
+    = &TEH_PG_find_aggregation_transient;
+  plugin->update_aggregation_transient
+    = &TEH_PG_update_aggregation_transient;
+  plugin->get_ready_deposit
+    = &TEH_PG_get_ready_deposit;
+  plugin->insert_refund
+    = &TEH_PG_insert_refund;
+  plugin->select_refunds_by_coin
+    = &TEH_PG_select_refunds_by_coin;
+  plugin->get_melt
+    = &TEH_PG_get_melt;
+  plugin->insert_refresh_reveal
+    = &TEH_PG_insert_refresh_reveal;
+  plugin->get_refresh_reveal
+    = &TEH_PG_get_refresh_reveal;
+  plugin->lookup_wire_transfer
+    = &TEH_PG_lookup_wire_transfer;
+  plugin->lookup_transfer_by_deposit
+    = &TEH_PG_lookup_transfer_by_deposit;
+  plugin->insert_wire_fee
+    = &TEH_PG_insert_wire_fee;
+  plugin->insert_global_fee
+    = &TEH_PG_insert_global_fee;
+  plugin->get_wire_fee
+    = &TEH_PG_get_wire_fee;
+  plugin->get_global_fee
+    = &TEH_PG_get_global_fee;
+  plugin->get_global_fees
+    = &TEH_PG_get_global_fees;
+  plugin->insert_reserve_closed
+    = &TEH_PG_insert_reserve_closed;
+  plugin->wire_prepare_data_insert
+    = &TEH_PG_wire_prepare_data_insert;
+  plugin->wire_prepare_data_mark_finished
+    = &TEH_PG_wire_prepare_data_mark_finished;
+  plugin->wire_prepare_data_mark_failed
+    = &TEH_PG_wire_prepare_data_mark_failed;
+  plugin->wire_prepare_data_get
+    = &TEH_PG_wire_prepare_data_get;
+  plugin->start_deferred_wire_out
+    = &TEH_PG_start_deferred_wire_out;
+  plugin->store_wire_transfer_out
+    = &TEH_PG_store_wire_transfer_out;
+  plugin->gc
+    = &TEH_PG_gc;
+  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
+    = &TEH_PG_select_purse_decisions_above_serial_id;
+  plugin->select_purse_deposits_by_purse
+    = &TEH_PG_select_purse_deposits_by_purse;
+  plugin->select_refreshes_above_serial_id
+    = &TEH_PG_select_refreshes_above_serial_id;
+  plugin->select_refunds_above_serial_id
+    = &TEH_PG_select_refunds_above_serial_id;
+  plugin->select_reserves_in_above_serial_id
+    = &TEH_PG_select_reserves_in_above_serial_id;
+  plugin->select_reserves_in_above_serial_id_by_account
+    = &TEH_PG_select_reserves_in_above_serial_id_by_account;
+  plugin->select_withdrawals_above_serial_id
+    = &TEH_PG_select_withdrawals_above_serial_id;
+  plugin->select_wire_out_above_serial_id
+    = &TEH_PG_select_wire_out_above_serial_id;
+  plugin->select_wire_out_above_serial_id_by_account
+    = &TEH_PG_select_wire_out_above_serial_id_by_account;
+  plugin->select_recoup_above_serial_id
+    = &TEH_PG_select_recoup_above_serial_id;
+  plugin->select_recoup_refresh_above_serial_id
+    = &TEH_PG_select_recoup_refresh_above_serial_id;
+  plugin->get_reserve_by_h_blind
+    = &TEH_PG_get_reserve_by_h_blind;
+  plugin->get_old_coin_by_h_blind
+    = &TEH_PG_get_old_coin_by_h_blind;
+  plugin->insert_denomination_revocation
+    = &TEH_PG_insert_denomination_revocation;
+  plugin->get_denomination_revocation
+    = &TEH_PG_get_denomination_revocation;
+  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
+    = &TEH_PG_lookup_auditor_status;
+  plugin->insert_auditor
+    = &TEH_PG_insert_auditor;
+  plugin->lookup_wire_timestamp
+    = &TEH_PG_lookup_wire_timestamp;
+  plugin->insert_wire
+    = &TEH_PG_insert_wire;
+  plugin->update_wire
+    = &TEH_PG_update_wire;
+  plugin->get_wire_accounts
+    = &TEH_PG_get_wire_accounts;
+  plugin->get_wire_fees
+    = &TEH_PG_get_wire_fees;
+  plugin->insert_signkey_revocation
+    = &TEH_PG_insert_signkey_revocation;
+  plugin->lookup_signkey_revocation
+    = &TEH_PG_lookup_signkey_revocation;
+  plugin->lookup_denomination_key
+    = &TEH_PG_lookup_denomination_key;
+  plugin->insert_auditor_denom_sig
+    = &TEH_PG_insert_auditor_denom_sig;
+  plugin->select_auditor_denom_sig
+    = &TEH_PG_select_auditor_denom_sig;
+  plugin->add_denomination_key
+    = &TEH_PG_add_denomination_key;
+  plugin->lookup_signing_key
+    = &TEH_PG_lookup_signing_key;
+  plugin->begin_shard
+    = &TEH_PG_begin_shard;
+  plugin->abort_shard
+    = &TEH_PG_abort_shard;
+  plugin->complete_shard
+    = &TEH_PG_complete_shard;
+  plugin->release_revolving_shard
+    = &TEH_PG_release_revolving_shard;
+  plugin->delete_shard_locks
+    = &TEH_PG_delete_shard_locks;
+  plugin->set_extension_manifest
+    = &TEH_PG_set_extension_manifest;
+  plugin->insert_partner
+    = &TEH_PG_insert_partner;
+  plugin->expire_purse
+    = &TEH_PG_expire_purse;
+  plugin->select_purse_by_merge_pub
+    = &TEH_PG_select_purse_by_merge_pub;
+  plugin->set_purse_balance
+    = &TEH_PG_set_purse_balance;
+  plugin->insert_kyc_attributes
+    = &TEH_PG_insert_kyc_attributes;
+  plugin->select_similar_kyc_attributes
+    = &TEH_PG_select_similar_kyc_attributes;
+  plugin->select_kyc_attributes
+    = &TEH_PG_select_kyc_attributes;
+  plugin->insert_aml_officer
+    = &TEH_PG_insert_aml_officer;
+  plugin->test_aml_officer
+    = &TEH_PG_test_aml_officer;
+  plugin->lookup_aml_officer
+    = &TEH_PG_lookup_aml_officer;
+  plugin->trigger_aml_process
+    = &TEH_PG_trigger_aml_process;
+  plugin->select_aml_process
+    = &TEH_PG_select_aml_process;
+  plugin->select_aml_history
+    = &TEH_PG_select_aml_history;
+  plugin->insert_aml_decision
+    = &TEH_PG_insert_aml_decision;
+
+  plugin->batch_ensure_coin_known
+    = &TEH_PG_batch_ensure_coin_known;
+
+  return plugin;
+}
+
+
+/**
+ * Shutdown Postgres database subsystem.
+ *
+ * @param cls a `struct TALER_EXCHANGEDB_Plugin`
+ * @return NULL (always)
+ */
+void *
+libtaler_plugin_exchangedb_postgres_done (void *cls)
+{
+  struct TALER_EXCHANGEDB_Plugin *plugin = cls;
+  struct PostgresClosure *pg = plugin->cls;
+
+  if (NULL != pg->conn)
+  {
+    GNUNET_PQ_disconnect (pg->conn);
+    pg->conn = NULL;
+  }
+  GNUNET_free (pg->exchange_url);
+  GNUNET_free (pg->sql_dir);
+  GNUNET_free (pg->currency);
+  GNUNET_free (pg);
+  GNUNET_free (plugin);
+  return NULL;
+}
+
+
+/* end of plugin_exchangedb_postgres.c */
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
new file mode 100644
index 0000000..cc67249
--- /dev/null
+++ b/src/exchangedb/procedures.sql.in
@@ -0,0 +1,50 @@
+--
+-- 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/>
+--
+
+BEGIN;
+
+SET search_path TO exchange;
+
+#include "exchange_do_amount_specific.sql"
+#include "exchange_do_withdraw.sql"
+#include "exchange_do_batch_withdraw.sql"
+#include "exchange_do_batch_withdraw_insert.sql"
+#include "exchange_do_age_withdraw.sql"
+#include "exchange_do_recoup_by_reserve.sql"
+#include "exchange_do_deposit.sql"
+#include "exchange_do_melt.sql"
+#include "exchange_do_refund.sql"
+#include "exchange_do_recoup_to_reserve.sql"
+#include "exchange_do_recoup_to_coin.sql"
+#include "exchange_do_gc.sql"
+#include "exchange_do_purse_delete.sql"
+#include "exchange_do_purse_deposit.sql"
+#include "exchange_do_purse_merge.sql"
+#include "exchange_do_reserve_purse.sql"
+#include "exchange_do_expire_purse.sql"
+#include "exchange_do_history_request.sql"
+#include "exchange_do_reserve_open_deposit.sql"
+#include "exchange_do_reserve_open.sql"
+#include "exchange_do_insert_or_update_policy_details.sql"
+#include "exchange_do_insert_aml_decision.sql"
+#include "exchange_do_insert_aml_officer.sql"
+#include "exchange_do_insert_kyc_attributes.sql"
+#include "exchange_do_reserves_in_insert.sql"
+#include "exchange_do_batch_reserves_update.sql"
+#include "exchange_do_get_link_data.sql"
+#include "exchange_do_batch_coin_known.sql"
+
+COMMIT;
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
new file mode 100644
index 0000000..6e1d3a0
--- /dev/null
+++ b/src/exchangedb/test_exchangedb.c
@@ -0,0 +1,2485 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/test_exchangedb.c
+ * @brief test cases for DB interaction functions
+ * @author Sree Harsha Totakura
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond)                              \
+  do {                                          \
+    if (! (cond)) { break;}                      \
+    GNUNET_break (0);                           \
+    goto drop;                                  \
+  } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr)                                                    \
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+  memset (ptr, 0, sizeof (*ptr))
+
+
+/**
+ * Currency we use.  Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+
+/**
+ * Callback that should never be called.
+ */
+static void
+dead_prepare_cb (void *cls,
+                 uint64_t rowid,
+                 const char *wire_method,
+                 const char *buf,
+                 size_t buf_size)
+{
+  (void) cls;
+  (void) rowid;
+  (void) wire_method;
+  (void) buf;
+  (void) buf_size;
+  GNUNET_assert (0);
+}
+
+
+/**
+ * Callback that is called with wire prepare data
+ * and then marks it as finished.
+ */
+static void
+mark_prepare_cb (void *cls,
+                 uint64_t rowid,
+                 const char *wire_method,
+                 const char *buf,
+                 size_t buf_size)
+{
+  (void) cls;
+  GNUNET_assert (11 == buf_size);
+  GNUNET_assert (0 == strcasecmp (wire_method,
+                                  "testcase"));
+  GNUNET_assert (0 == memcmp (buf,
+                              "hello world",
+                              buf_size));
+  GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+                plugin->wire_prepare_data_mark_finished (plugin->cls,
+                                                         rowid));
+}
+
+
+/**
+ * Simple check that config retrieval and setting for extensions work
+ */
+static enum GNUNET_GenericReturnValue
+test_extension_manifest (void)
+{
+  char *manifest;
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->get_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          &manifest));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          "bar"));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          &manifest));
+
+  FAILIF (0 != strcmp ("bar", manifest));
+  GNUNET_free (manifest);
+
+  /* let's do this again! */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          "buzz"));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          &manifest));
+
+  FAILIF (0 != strcmp ("buzz", manifest));
+  GNUNET_free (manifest);
+
+  /* let's do this again, with NULL */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->set_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          NULL));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_extension_manifest (plugin->cls,
+                                          "fnord",
+                                          &manifest));
+
+  FAILIF (NULL != manifest);
+
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Test API relating to persisting the wire plugins preparation data.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+test_wire_prepare (void)
+{
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->wire_prepare_data_get (plugin->cls,
+                                         0,
+                                         1,
+                                         &dead_prepare_cb,
+                                         NULL));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->wire_prepare_data_insert (plugin->cls,
+                                            "testcase",
+                                            "hello world",
+                                            11));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->wire_prepare_data_get (plugin->cls,
+                                         0,
+                                         1,
+                                         &mark_prepare_cb,
+                                         NULL));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->wire_prepare_data_get (plugin->cls,
+                                         0,
+                                         1,
+                                         &dead_prepare_cb,
+                                         NULL));
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Checks if the given reserve has the given amount of balance and expiry
+ *
+ * @param pub the public key of the reserve
+ * @param value balance value
+ * @param fraction balance fraction
+ * @param currency currency of the reserve
+ * @return #GNUNET_OK if the given reserve has the same balance and expiration
+ *           as the given parameters; #GNUNET_SYSERR if not
+ */
+static enum GNUNET_GenericReturnValue
+check_reserve (const struct TALER_ReservePublicKeyP *pub,
+               uint64_t value,
+               uint32_t fraction,
+               const char *currency)
+{
+  struct TALER_EXCHANGEDB_Reserve reserve;
+
+  reserve.pub = *pub;
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->reserves_get (plugin->cls,
+                                &reserve));
+  FAILIF (value != reserve.balance.value);
+  FAILIF (fraction != reserve.balance.fraction);
+  FAILIF (0 != strcmp (currency,
+                       reserve.balance.currency));
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
+struct DenomKeyPair
+{
+  struct TALER_DenominationPrivateKey priv;
+  struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair.  The key is not necessarily removed from 
the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+  TALER_denom_pub_free (&dkp->pub);
+  TALER_denom_priv_free (&dkp->priv);
+  GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h 
later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+                       struct GNUNET_TIME_Timestamp now,
+                       const struct TALER_Amount *value,
+                       const struct TALER_DenomFeeSet *fees)
+{
+  struct DenomKeyPair *dkp;
+  struct TALER_EXCHANGEDB_DenominationKey dki;
+  struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+  dkp = GNUNET_new (struct DenomKeyPair);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_denom_priv_create (&dkp->priv,
+                                          &dkp->pub,
+                                          TALER_DENOMINATION_RSA,
+                                          size));
+  /* Using memset() as fields like master key and signature
+     are not properly initialized for this test. */
+  memset (&dki,
+          0,
+          sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+  dki.denom_pub = dkp->pub;
+  dki.issue.start = now;
+  dki.issue.expire_withdraw
+    = GNUNET_TIME_absolute_to_timestamp (
+        GNUNET_TIME_absolute_add (
+          now.abs_time,
+          GNUNET_TIME_UNIT_HOURS));
+  dki.issue.expire_deposit
+    = GNUNET_TIME_absolute_to_timestamp (
+        GNUNET_TIME_absolute_add (
+          now.abs_time,
+          GNUNET_TIME_relative_multiply (
+            GNUNET_TIME_UNIT_HOURS, 2)));
+  dki.issue.expire_legal
+    = GNUNET_TIME_absolute_to_timestamp (
+        GNUNET_TIME_absolute_add (
+          now.abs_time,
+          GNUNET_TIME_relative_multiply (
+            GNUNET_TIME_UNIT_HOURS, 3)));
+  dki.issue.value = *value;
+  dki.issue.fees = *fees;
+  TALER_denom_pub_hash (&dkp->pub,
+                        &dki.issue.denom_hash);
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->insert_denomination_info (plugin->cls,
+                                        &dki.denom_pub,
+                                        &dki.issue))
+  {
+    GNUNET_break (0);
+    destroy_denom_key_pair (dkp);
+    return NULL;
+  }
+  memset (&issue2, 0, sizeof (issue2));
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->get_denomination_info (plugin->cls,
+                                     &dki.issue.denom_hash,
+                                     &issue2))
+  {
+    GNUNET_break (0);
+    destroy_denom_key_pair (dkp);
+    return NULL;
+  }
+  if (0 != GNUNET_memcmp (&dki.issue,
+                          &issue2))
+  {
+    GNUNET_break (0);
+    destroy_denom_key_pair (dkp);
+    return NULL;
+  }
+  return dkp;
+}
+
+
+static struct TALER_Amount value;
+static struct TALER_DenomFeeSet fees;
+static struct TALER_Amount fee_closing;
+static struct TALER_Amount amount_with_fee;
+
+
+/**
+ * Number of newly minted coins to use in the test.
+ */
+#define MELT_NEW_COINS 5
+
+/**
+ * Which index was 'randomly' chosen for the reveal for the test?
+ */
+#define MELT_NOREVEAL_INDEX 1
+
+/**
+ * How big do we make the RSA keys?
+ */
+#define RSA_KEY_SIZE 1024
+
+static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+
+static struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA];
+
+static struct TALER_TransferPublicKeyP tpub;
+
+
+/**
+ * Function called with information about a refresh order.  This
+ * one should not be called in a successful test.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the row in our database
+ * @param num_freshcoins size of the @a rrcs array
+ * @param rrcs array of @a num_freshcoins information about coins to be created
+ */
+static void
+never_called_cb (void *cls,
+                 uint32_t num_freshcoins,
+                 const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
+{
+  (void) cls;
+  (void) num_freshcoins;
+  (void) rrcs;
+  GNUNET_assert (0); /* should never be called! */
+}
+
+
+/**
+ * Function called with information about a refresh order.
+ * Checks that the response matches what we expect to see.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the row in our database
+ * @param num_freshcoins size of the @a rrcs array
+ * @param rrcs array of @a num_freshcoins information about coins to be created
+ */
+static void
+check_refresh_reveal_cb (
+  void *cls,
+  uint32_t num_freshcoins,
+  const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
+{
+  (void) cls;
+  /* compare the refresh commit coin arrays */
+  for (unsigned int cnt = 0; cnt < num_freshcoins; cnt++)
+  {
+    const struct TALER_EXCHANGEDB_RefreshRevealedCoin *acoin =
+      &revealed_coins[cnt];
+    const struct TALER_EXCHANGEDB_RefreshRevealedCoin *bcoin = &rrcs[cnt];
+
+    GNUNET_assert (0 ==
+                   TALER_blinded_planchet_cmp (&acoin->blinded_planchet,
+                                               &bcoin->blinded_planchet));
+    GNUNET_assert (0 ==
+                   GNUNET_memcmp (&acoin->h_denom_pub,
+                                  &bcoin->h_denom_pub));
+  }
+}
+
+
+/**
+ * Counter used in auditor-related db functions. Used to count
+ * expected rows.
+ */
+static unsigned int auditor_row_cnt;
+
+
+/**
+ * Function called with details about coins that were melted,
+ * with the goal of auditing the refresh's execution.
+ *
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param denom_pub denomination of the @a coin_pub
+ * @param h_age_commitment hash of age commitment that went into the minting, 
may be NULL
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature from the coin
+ * @param amount_with_fee amount that was deposited including fee
+ * @param num_freshcoins how many coins were issued
+ * @param noreveal_index which index was picked by the exchange in 
cut-and-choose
+ * @param rc what is the session hash
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+audit_refresh_session_cb (
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const struct TALER_Amount *amount_with_fee,
+  uint32_t noreveal_index,
+  const struct TALER_RefreshCommitmentP *rc)
+{
+  (void) cls;
+  (void) rowid;
+  (void) denom_pub;
+  (void) coin_pub;
+  (void) coin_sig;
+  (void) amount_with_fee;
+  (void) noreveal_index;
+  (void) rc;
+  (void) h_age_commitment;
+  auditor_row_cnt++;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Denomination keys used for fresh coins in melt test.
+ */
+static struct DenomKeyPair **new_dkp;
+
+
+/**
+ * Function called with the session hashes and transfer secret
+ * information for a given coin.
+ *
+ * @param cls closure
+ * @param transfer_pub public transfer key for the session
+ * @param ldl link data for @a transfer_pub
+ */
+static void
+handle_link_data_cb (void *cls,
+                     const struct TALER_TransferPublicKeyP *transfer_pub,
+                     const struct TALER_EXCHANGEDB_LinkList *ldl)
+{
+  (void) cls;
+  (void) transfer_pub;
+  for (const struct TALER_EXCHANGEDB_LinkList *ldlp = ldl;
+       NULL != ldlp;
+       ldlp = ldlp->next)
+  {
+    bool found;
+
+    found = false;
+    for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+    {
+      if ( (0 ==
+            TALER_denom_pub_cmp (&ldlp->denom_pub,
+                                 &new_dkp[cnt]->pub)) &&
+           (0 ==
+            TALER_blinded_denom_sig_cmp (&ldlp->ev_sig,
+                                         &revealed_coins[cnt].coin_sig)) )
+      {
+        found = true;
+        break;
+      }
+    }
+    GNUNET_assert (GNUNET_NO != found);
+  }
+}
+
+
+/**
+ * Callback that should never be called.
+ */
+static void
+cb_wt_never (void *cls,
+             uint64_t serial_id,
+             const struct TALER_MerchantPublicKeyP *merchant_pub,
+             const char *account_payto_uri,
+             const struct TALER_PaytoHashP *h_payto,
+             struct GNUNET_TIME_Timestamp exec_time,
+             const struct TALER_PrivateContractHashP *h_contract_terms,
+             const struct TALER_DenominationPublicKey *denom_pub,
+             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+             const struct TALER_Amount *coin_value,
+             const struct TALER_Amount *coin_fee)
+{
+  (void) cls;
+  (void) serial_id;
+  (void) merchant_pub;
+  (void) account_payto_uri;
+  (void) h_payto;
+  (void) exec_time;
+  (void) h_contract_terms;
+  (void) denom_pub;
+  (void) coin_pub;
+  (void) coin_value;
+  (void) coin_fee;
+  GNUNET_assert (0); /* this statement should be unreachable */
+}
+
+
+static struct TALER_MerchantPublicKeyP merchant_pub_wt;
+static struct TALER_MerchantWireHashP h_wire_wt;
+static struct TALER_PrivateContractHashP h_contract_terms_wt;
+static struct TALER_CoinSpendPublicKeyP coin_pub_wt;
+static struct TALER_Amount coin_value_wt;
+static struct TALER_Amount coin_fee_wt;
+static struct TALER_Amount transfer_value_wt;
+static struct GNUNET_TIME_Timestamp wire_out_date;
+static struct TALER_WireTransferIdentifierRawP wire_out_wtid;
+
+
+/**
+ * Callback that should be called with the WT data.
+ */
+static void
+cb_wt_check (void *cls,
+             uint64_t rowid,
+             const struct TALER_MerchantPublicKeyP *merchant_pub,
+             const char *account_payto_uri,
+             const struct TALER_PaytoHashP *h_payto,
+             struct GNUNET_TIME_Timestamp exec_time,
+             const struct TALER_PrivateContractHashP *h_contract_terms,
+             const struct TALER_DenominationPublicKey *denom_pub,
+             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+             const struct TALER_Amount *coin_value,
+             const struct TALER_Amount *coin_fee)
+{
+  (void) rowid;
+  (void) denom_pub;
+  (void) h_payto;
+  GNUNET_assert (cls == &cb_wt_never);
+  GNUNET_assert (0 == GNUNET_memcmp (merchant_pub,
+                                     &merchant_pub_wt));
+  GNUNET_assert (0 == strcmp (account_payto_uri,
+                              
"payto://iban/DE67830654080004822650?receiver-name=Test"));
+  GNUNET_assert (GNUNET_TIME_timestamp_cmp (exec_time,
+                                            ==,
+                                            wire_out_date));
+  GNUNET_assert (0 == GNUNET_memcmp (h_contract_terms,
+                                     &h_contract_terms_wt));
+  GNUNET_assert (0 == GNUNET_memcmp (coin_pub,
+                                     &coin_pub_wt));
+  GNUNET_assert (0 == TALER_amount_cmp (coin_value,
+                                        &coin_value_wt));
+  GNUNET_assert (0 == TALER_amount_cmp (coin_fee,
+                                        &coin_fee_wt));
+}
+
+
+/**
+ * Here we store the hash of the payto URI.
+ */
+static struct TALER_PaytoHashP wire_target_h_payto;
+
+
+/**
+ * Callback for #select_coin_deposits_above_serial_id ()
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param exchange_timestamp when did the deposit happen
+ * @param deposit deposit details
+ * @param denom_pub denomination of the @a coin_pub
+ * @param done flag set if the deposit was already executed (or not)
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+audit_deposit_cb (void *cls,
+                  uint64_t rowid,
+                  struct GNUNET_TIME_Timestamp exchange_timestamp,
+                  const struct TALER_EXCHANGEDB_Deposit *deposit,
+                  const struct TALER_DenominationPublicKey *denom_pub,
+                  bool done)
+{
+  (void) cls;
+  (void) rowid;
+  (void) exchange_timestamp;
+  (void) deposit;
+  (void) denom_pub;
+  (void) done;
+  auditor_row_cnt++;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about coins that were refunding,
+ * with the goal of auditing the refund's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refund in our DB
+ * @param denom_pub denomination of the @a coin_pub
+ * @param coin_pub public key of the coin
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature of the merchant
+ * @param h_contract_terms hash of the proposal data in
+ *                        the contract between merchant and customer
+ * @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund the deposit
+ * @param amount_with_fee amount that was deposited including fee
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+audit_refund_cb (void *cls,
+                 uint64_t rowid,
+                 const struct TALER_DenominationPublicKey *denom_pub,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                 const struct TALER_MerchantPublicKeyP *merchant_pub,
+                 const struct TALER_MerchantSignatureP *merchant_sig,
+                 const struct TALER_PrivateContractHashP *h_contract_terms,
+                 uint64_t rtransaction_id,
+                 bool full_refund,
+                 const struct TALER_Amount *amount_with_fee)
+{
+  (void) cls;
+  (void) rowid;
+  (void) denom_pub;
+  (void) coin_pub;
+  (void) merchant_pub;
+  (void) merchant_sig;
+  (void) h_contract_terms;
+  (void) rtransaction_id;
+  (void) amount_with_fee;
+  (void) full_refund;
+  auditor_row_cnt++;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about incoming wire transfers.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param reserve_pub public key of the reserve (also the WTID)
+ * @param credit amount that was received
+ * @param sender_account_details information about the sender's bank account
+ * @param wire_reference unique reference identifying the wire transfer
+ * @param execution_date when did we receive the funds
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+audit_reserve_in_cb (void *cls,
+                     uint64_t rowid,
+                     const struct TALER_ReservePublicKeyP *reserve_pub,
+                     const struct TALER_Amount *credit,
+                     const char *sender_account_details,
+                     uint64_t wire_reference,
+                     struct GNUNET_TIME_Timestamp execution_date)
+{
+  (void) cls;
+  (void) rowid;
+  (void) reserve_pub;
+  (void) credit;
+  (void) sender_account_details;
+  (void) wire_reference;
+  (void) execution_date;
+  auditor_row_cnt++;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with details about withdraw operations.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param h_blind_ev blinded hash of the coin's public key
+ * @param denom_pub public denomination key of the deposited coin
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature over the withdraw operation
+ * @param execution_date when did the wallet withdraw the coin
+ * @param amount_with_fee amount that was withdrawn
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+audit_reserve_out_cb (void *cls,
+                      uint64_t rowid,
+                      const struct TALER_BlindedCoinHashP *h_blind_ev,
+                      const struct TALER_DenominationPublicKey *denom_pub,
+                      const struct TALER_ReservePublicKeyP *reserve_pub,
+                      const struct TALER_ReserveSignatureP *reserve_sig,
+                      struct GNUNET_TIME_Timestamp execution_date,
+                      const struct TALER_Amount *amount_with_fee)
+{
+  (void) cls;
+  (void) rowid;
+  (void) h_blind_ev;
+  (void) denom_pub;
+  (void) reserve_pub;
+  (void) reserve_sig;
+  (void) execution_date;
+  (void) amount_with_fee;
+  auditor_row_cnt++;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Test garbage collection.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+test_gc (void)
+{
+  struct DenomKeyPair *dkp;
+  struct GNUNET_TIME_Timestamp now;
+  struct GNUNET_TIME_Timestamp past;
+  struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+  struct TALER_DenominationHashP denom_hash;
+
+  now = GNUNET_TIME_timestamp_get ();
+  past = GNUNET_TIME_absolute_to_timestamp (
+    GNUNET_TIME_absolute_subtract (now.abs_time,
+                                   GNUNET_TIME_relative_multiply (
+                                     GNUNET_TIME_UNIT_HOURS,
+                                     4)));
+  dkp = create_denom_key_pair (RSA_KEY_SIZE,
+                               past,
+                               &value,
+                               &fees);
+  GNUNET_assert (NULL != dkp);
+  if (GNUNET_OK !=
+      plugin->gc (plugin->cls))
+  {
+    GNUNET_break (0);
+    destroy_denom_key_pair (dkp);
+    return GNUNET_SYSERR;
+  }
+  TALER_denom_pub_hash (&dkp->pub,
+                        &denom_hash);
+
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->get_denomination_info (plugin->cls,
+                                     &denom_hash,
+                                     &issue2))
+  {
+    GNUNET_break (0);
+    destroy_denom_key_pair (dkp);
+    return GNUNET_SYSERR;
+  }
+  destroy_denom_key_pair (dkp);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Test wire fee storage.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+test_wire_fees (void)
+{
+  struct GNUNET_TIME_Timestamp start_date;
+  struct GNUNET_TIME_Timestamp end_date;
+  struct TALER_WireFeeSet fees;
+  struct TALER_MasterSignatureP master_sig;
+  struct GNUNET_TIME_Timestamp sd;
+  struct GNUNET_TIME_Timestamp ed;
+  struct TALER_WireFeeSet fees2;
+  struct TALER_MasterSignatureP ms;
+
+  start_date = GNUNET_TIME_timestamp_get ();
+  end_date = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":1.424242",
+                                         &fees.wire));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":2.424242",
+                                         &fees.closing));
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &master_sig,
+                              sizeof (master_sig));
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->insert_wire_fee (plugin->cls,
+                               "wire-method",
+                               start_date,
+                               end_date,
+                               &fees,
+                               &master_sig))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->insert_wire_fee (plugin->cls,
+                               "wire-method",
+                               start_date,
+                               end_date,
+                               &fees,
+                               &master_sig))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* This must fail as 'end_date' is NOT in the
+     half-open interval [start_date,end_date) */
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+      plugin->get_wire_fee (plugin->cls,
+                            "wire-method",
+                            end_date,
+                            &sd,
+                            &ed,
+                            &fees2,
+                            &ms))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+      plugin->get_wire_fee (plugin->cls,
+                            "wire-method",
+                            start_date,
+                            &sd,
+                            &ed,
+                            &fees2,
+                            &ms))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (GNUNET_TIME_timestamp_cmp (sd,
+                                   !=,
+                                   start_date)) ||
+       (GNUNET_TIME_timestamp_cmp (ed,
+                                   !=,
+                                   end_date)) ||
+       (0 != TALER_wire_fee_set_cmp (&fees,
+                                     &fees2)) ||
+       (0 != GNUNET_memcmp (&ms,
+                            &master_sig)) )
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+static struct TALER_Amount wire_out_amount;
+
+
+/**
+ * Callback with data about an executed wire transfer.
+ *
+ * @param cls closure
+ * @param rowid identifier of the respective row in the database
+ * @param date timestamp of the wire transfer (roughly)
+ * @param wtid wire transfer subject
+ * @param wire wire transfer details of the receiver
+ * @param amount amount that was wired
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
+ */
+static enum GNUNET_GenericReturnValue
+audit_wire_cb (void *cls,
+               uint64_t rowid,
+               struct GNUNET_TIME_Timestamp date,
+               const struct TALER_WireTransferIdentifierRawP *wtid,
+               const char *payto_uri,
+               const struct TALER_Amount *amount)
+{
+  (void) cls;
+  (void) rowid;
+  (void) payto_uri;
+  auditor_row_cnt++;
+  GNUNET_assert (0 ==
+                 TALER_amount_cmp (amount,
+                                   &wire_out_amount));
+  GNUNET_assert (0 ==
+                 GNUNET_memcmp (wtid,
+                                &wire_out_wtid));
+  GNUNET_assert (GNUNET_TIME_timestamp_cmp (date,
+                                            ==,
+                                            wire_out_date));
+  return GNUNET_OK;
+}
+
+
+/**
+ * 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_BatchDeposit *bd)
+{
+  const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = &bd->cdis[0];
+  struct TALER_PaytoHashP h_payto;
+
+  GNUNET_assert (0 < bd->num_cdis);
+  TALER_payto_hash (bd->receiver_wire_account,
+                    &h_payto);
+  auditor_row_cnt = 0;
+  memset (&wire_out_wtid,
+          41,
+          sizeof (wire_out_wtid));
+  wire_out_date = GNUNET_TIME_timestamp_get ();
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":1",
+                                         &wire_out_amount));
+
+  /* we will transiently violate the wtid constraint on
+     the aggregation table, so we need to start the special
+     transaction where this is allowed... */
+  FAILIF (GNUNET_OK !=
+          plugin->start_deferred_wire_out (plugin->cls));
+
+  /* setup values for wire transfer aggregation data */
+  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;
+  coin_fee_wt = fees.deposit;
+  GNUNET_assert (0 <
+                 TALER_amount_subtract (&transfer_value_wt,
+                                        &coin_value_wt,
+                                        &coin_fee_wt));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->lookup_wire_transfer (plugin->cls,
+                                        &wire_out_wtid,
+                                        &cb_wt_never,
+                                        NULL));
+
+  {
+    struct TALER_PrivateContractHashP h_contract_terms_wt2 =
+      h_contract_terms_wt;
+    bool pending;
+    struct TALER_WireTransferIdentifierRawP wtid2;
+    struct TALER_Amount coin_contribution2;
+    struct TALER_Amount coin_fee2;
+    struct GNUNET_TIME_Timestamp execution_time2;
+    struct TALER_EXCHANGEDB_KycStatus kyc;
+    enum TALER_AmlDecisionState aml;
+
+    h_contract_terms_wt2.hash.bits[0]++;
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->lookup_transfer_by_deposit (plugin->cls,
+                                                &h_contract_terms_wt2,
+                                                &h_wire_wt,
+                                                &coin_pub_wt,
+                                                &merchant_pub_wt,
+                                                &pending,
+                                                &wtid2,
+                                                &execution_time2,
+                                                &coin_contribution2,
+                                                &coin_fee2,
+                                                &kyc,
+                                                &aml));
+  }
+  {
+    struct TALER_ReservePublicKeyP rpub;
+
+    memset (&rpub,
+            44,
+            sizeof (rpub));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->store_wire_transfer_out (plugin->cls,
+                                             wire_out_date,
+                                             &wire_out_wtid,
+                                             &h_payto,
+                                             "my-config-section",
+                                             &wire_out_amount));
+  }
+  /* And now the commit should still succeed! */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->lookup_wire_transfer (plugin->cls,
+                                        &wire_out_wtid,
+                                        &cb_wt_check,
+                                        &cb_wt_never));
+  {
+    bool pending;
+    struct TALER_WireTransferIdentifierRawP wtid2;
+    struct TALER_Amount coin_contribution2;
+    struct TALER_Amount coin_fee2;
+    struct GNUNET_TIME_Timestamp execution_time2;
+    struct TALER_EXCHANGEDB_KycStatus kyc;
+    enum TALER_AmlDecisionState aml = TALER_AML_FROZEN;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->lookup_transfer_by_deposit (plugin->cls,
+                                                &h_contract_terms_wt,
+                                                &h_wire_wt,
+                                                &coin_pub_wt,
+                                                &merchant_pub_wt,
+                                                &pending,
+                                                &wtid2,
+                                                &execution_time2,
+                                                &coin_contribution2,
+                                                &coin_fee2,
+                                                &kyc,
+                                                &aml));
+    FAILIF (TALER_AML_NORMAL != aml);
+    GNUNET_assert (0 == GNUNET_memcmp (&wtid2,
+                                       &wire_out_wtid));
+    GNUNET_assert (GNUNET_TIME_timestamp_cmp (execution_time2,
+                                              ==,
+                                              wire_out_date));
+    GNUNET_assert (0 == TALER_amount_cmp (&coin_contribution2,
+                                          &coin_value_wt));
+    GNUNET_assert (0 == TALER_amount_cmp (&coin_fee2,
+                                          &coin_fee_wt));
+  }
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->select_wire_out_above_serial_id (plugin->cls,
+                                                   0,
+                                                   &audit_wire_cb,
+                                                   NULL));
+  FAILIF (1 != auditor_row_cnt);
+
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called about recoups the exchange has to perform.
+ *
+ * @param cls closure with the expected value for @a coin_blind
+ * @param rowid row identifier used to uniquely identify the recoup operation
+ * @param timestamp when did we receive the recoup request
+ * @param amount how much should be added back to the reserve
+ * @param reserve_pub public key of the reserve
+ * @param coin public information about the coin
+ * @param denom_pub denomination key of @a coin
+ * @param coin_sig signature with @e coin_pub of type 
#TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * @param coin_blind blinding factor used to blind the coin
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+recoup_cb (void *cls,
+           uint64_t rowid,
+           struct GNUNET_TIME_Timestamp timestamp,
+           const struct TALER_Amount *amount,
+           const struct TALER_ReservePublicKeyP *reserve_pub,
+           const struct TALER_CoinPublicInfo *coin,
+           const struct TALER_DenominationPublicKey *denom_pub,
+           const struct TALER_CoinSpendSignatureP *coin_sig,
+           const union TALER_DenominationBlindingKeyP *coin_blind)
+{
+  const union TALER_DenominationBlindingKeyP *cb = cls;
+
+  (void) rowid;
+  (void) timestamp;
+  (void) amount;
+  (void) reserve_pub;
+  (void) coin_sig;
+  (void) coin;
+  (void) denom_pub;
+  FAILIF (NULL == cb);
+  FAILIF (0 != GNUNET_memcmp (cb,
+                              coin_blind));
+  return GNUNET_OK;
+drop:
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Function called on deposits that are past their due date
+ * and have not yet seen a wire transfer.
+ *
+ * @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
+ * @param rowid deposit table row of the coin's deposit
+ * @param coin_pub public key of the coin
+ * @param amount value of the deposit, including fee
+ * @param payto_uri where should the funds be wired
+ * @param deadline what was the requested wire transfer deadline
+ * @param done did the exchange claim that it made a transfer?
+ */
+static void
+wire_missing_cb (void *cls,
+                 uint64_t rowid,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                 const struct TALER_Amount *amount,
+                 const char *payto_uri,
+                 struct GNUNET_TIME_Timestamp deadline,
+                 bool done)
+{
+  const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = cls;
+
+  (void) payto_uri;
+  (void) deadline;
+  (void) rowid;
+  if (done)
+  {
+    GNUNET_break (0);
+    result = 66;
+  }
+  if (0 != TALER_amount_cmp (amount,
+                             &deposit->amount_with_fee))
+  {
+    GNUNET_break (0);
+    result = 66;
+  }
+  if (0 != GNUNET_memcmp (coin_pub,
+                          &deposit->coin.coin_pub))
+  {
+    GNUNET_break (0);
+    result = 66;
+  }
+}
+
+
+/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin.
+ *
+ * @param cls closure with the `struct TALER_EXCHANGEDB_Refund *` we expect to 
get
+ * @param amount_with_fee amount being refunded
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+check_refund_cb (void *cls,
+                 const struct TALER_Amount *amount_with_fee)
+{
+  const struct TALER_EXCHANGEDB_Refund *refund = cls;
+
+  if (0 != TALER_amount_cmp (amount_with_fee,
+                             &refund->details.refund_amount))
+  {
+    GNUNET_break (0);
+    result = 66;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+  struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_TIME_Timestamp deadline;
+  union TALER_DenominationBlindingKeyP coin_blind;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  struct TALER_ReservePublicKeyP reserve_pub2;
+  struct TALER_ReservePublicKeyP reserve_pub3;
+  struct DenomKeyPair *dkp = NULL;
+  struct TALER_MasterSignatureP master_sig;
+  struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+  struct TALER_EXCHANGEDB_CollectableBlindcoin cbc2;
+  struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
+  struct TALER_EXCHANGEDB_ReserveHistory *rh_head;
+  struct TALER_EXCHANGEDB_BankTransfer *bt;
+  struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
+  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;
+  const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+  const char *rcvr = "payto://x-taler-bank/localhost:8080/2";
+  const uint32_t num_partitions = 10;
+  unsigned int matched;
+  unsigned int cnt;
+  enum GNUNET_DB_QueryStatus qs;
+  struct GNUNET_TIME_Timestamp now;
+  struct TALER_WireSaltP salt;
+  struct TALER_CoinPubHashP c_hash;
+  uint64_t known_coin_id;
+  uint64_t rrc_serial;
+  struct TALER_EXCHANGEDB_Refresh refresh;
+  struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+  uint64_t reserve_out_serial_id;
+  uint64_t melt_serial_id;
+  struct TALER_PlanchetMasterSecretP ps;
+  union TALER_DenominationBlindingKeyP bks;
+  struct TALER_ExchangeWithdrawValues alg_values = {
+    /* RSA is simpler, and for the DB there is no real difference between
+       CS and RSA, just one should be used, so we use RSA */
+    .cipher = TALER_DENOMINATION_RSA
+  };
+
+  memset (&deposit,
+          0,
+          sizeof (deposit));
+  memset (&bd,
+          0,
+          sizeof (bd));
+  bd.receiver_wire_account = (char *) rcvr;
+  bd.cdis = &deposit;
+  bd.num_cdis = 1;
+  memset (&salt,
+          45,
+          sizeof (salt));
+  memset (&refresh,
+          0,
+          sizeof (refresh));
+  ZR_BLK (&cbc);
+  ZR_BLK (&cbc2);
+  if (NULL ==
+      (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+  {
+    result = 77;
+    return;
+  }
+  (void) plugin->drop_tables (plugin->cls);
+  if (GNUNET_OK !=
+      plugin->create_tables (plugin->cls,
+                             true,
+                             num_partitions))
+  {
+    result = 77;
+    goto cleanup;
+  }
+  plugin->preflight (plugin->cls);
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-1"));
+
+  /* test DB is empty */
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->select_recoup_above_serial_id (plugin->cls,
+                                                 0,
+                                                 &recoup_cb,
+                                                 NULL));
+  /* simple extension check */
+  FAILIF (GNUNET_OK !=
+          test_extension_manifest ());
+
+  RND_BLK (&reserve_pub);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":1.000010",
+                                         &value));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":0.000010",
+                                         &fees.withdraw));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":0.000010",
+                                         &fees.deposit));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":0.000010",
+                                         &fees.refresh));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":0.000010",
+                                         &fees.refund));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":1.000010",
+                                         &amount_with_fee));
+  result = 4;
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+  now = GNUNET_TIME_timestamp_get ();
+  {
+    struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+      .reserve_pub = &reserve_pub,
+      .balance = &value,
+      .execution_time = now,
+      .sender_account_details = sndr,
+      .exchange_account_name = "exchange-account-1",
+      .wire_reference = 4
+    };
+    enum GNUNET_DB_QueryStatus qsr;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->reserves_in_insert (plugin->cls,
+                                        &reserve,
+                                        1,
+                                        &qsr));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            qsr);
+  }
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub,
+                         value.value,
+                         value.fraction,
+                         value.currency));
+  now = GNUNET_TIME_timestamp_get ();
+  RND_BLK (&reserve_pub2);
+  {
+    struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+      .reserve_pub = &reserve_pub2,
+      .balance = &value,
+      .execution_time = now,
+      .sender_account_details = sndr,
+      .exchange_account_name = "exchange-account-1",
+      .wire_reference = 5
+    };
+    enum GNUNET_DB_QueryStatus qsr;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->reserves_in_insert (plugin->cls,
+                                        &reserve,
+                                        1,
+                                        &qsr));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            qsr);
+  }
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-2"));
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub,
+                         value.value,
+                         value.fraction,
+                         value.currency));
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub2,
+                         value.value,
+                         value.fraction,
+                         value.currency));
+  result = 5;
+  now = GNUNET_TIME_timestamp_get ();
+  dkp = create_denom_key_pair (RSA_KEY_SIZE,
+                               now,
+                               &value,
+                               &fees);
+  GNUNET_assert (NULL != dkp);
+  TALER_denom_pub_hash (&dkp->pub,
+                        &cbc.denom_pub_hash);
+  RND_BLK (&cbc.reserve_sig);
+  RND_BLK (&ps);
+  TALER_planchet_blinding_secret_create (&ps,
+                                         &alg_values,
+                                         &bks);
+  {
+    struct TALER_PlanchetDetail pd;
+    struct TALER_CoinSpendPublicKeyP coin_pub;
+    struct TALER_AgeCommitmentHash age_hash;
+    struct TALER_AgeCommitmentHash *p_ah[2] = {
+      NULL,
+      &age_hash
+    };
+
+    /* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without
+     * age_hash, once with age_hash */
+    RND_BLK (&age_hash);
+    for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++)
+    {
+
+      RND_BLK (&coin_pub);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_denom_blind (&dkp->pub,
+                                        &bks,
+                                        p_ah[i],
+                                        &coin_pub,
+                                        &alg_values,
+                                        &c_hash,
+                                        &pd.blinded_planchet));
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_coin_ev_hash (&pd.blinded_planchet,
+                                         &cbc.denom_pub_hash,
+                                         &cbc.h_coin_envelope));
+      if (i != 0)
+        TALER_blinded_denom_sig_free (&cbc.sig);
+      GNUNET_assert (
+        GNUNET_OK ==
+        TALER_denom_sign_blinded (
+          &cbc.sig,
+          &dkp->priv,
+          false,
+          &pd.blinded_planchet));
+      TALER_blinded_planchet_free (&pd.blinded_planchet);
+    }
+  }
+
+  cbc.reserve_pub = reserve_pub;
+  cbc.amount_with_fee = value;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (CURRENCY,
+                                        &cbc.withdraw_fee));
+
+  {
+    bool found;
+    bool nonce_ok;
+    bool balance_ok;
+    bool age_ok;
+    uint16_t maximum_age;
+    uint64_t ruuid;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_withdraw (plugin->cls,
+                                 NULL,
+                                 &cbc,
+                                 now,
+                                 false,
+                                 &found,
+                                 &balance_ok,
+                                 &nonce_ok,
+                                 &age_ok,
+                                 &maximum_age,
+                                 &ruuid));
+    GNUNET_assert (found);
+    GNUNET_assert (nonce_ok);
+    GNUNET_assert (balance_ok);
+  }
+
+
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub,
+                         0,
+                         0,
+                         value.currency));
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub2,
+                         value.value,
+                         value.fraction,
+                         value.currency));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_reserve_by_h_blind (plugin->cls,
+                                          &cbc.h_coin_envelope,
+                                          &reserve_pub3,
+                                          &reserve_out_serial_id));
+  FAILIF (0 != GNUNET_memcmp (&reserve_pub,
+                              &reserve_pub3));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->get_withdraw_info (plugin->cls,
+                                     &cbc.h_coin_envelope,
+                                     &cbc2));
+  FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig,
+                              &cbc.reserve_sig));
+  FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_pub,
+                              &cbc.reserve_pub));
+  result = 6;
+
+  {
+    struct TALER_DenominationSignature ds;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_denom_sig_unblind (&ds,
+                                            &cbc2.sig,
+                                            &bks,
+                                            &c_hash,
+                                            &alg_values,
+                                            &dkp->pub));
+    FAILIF (GNUNET_OK !=
+            TALER_denom_pub_verify (&dkp->pub,
+                                    &ds,
+                                    &c_hash));
+    TALER_denom_sig_free (&ds);
+  }
+
+  RND_BLK (&coin_sig);
+  RND_BLK (&coin_blind);
+  RND_BLK (&deposit.coin.coin_pub);
+  TALER_denom_pub_hash (&dkp->pub,
+                        &deposit.coin.denom_pub_hash);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+                                          &cbc.sig,
+                                          &bks,
+                                          &c_hash,
+                                          &alg_values,
+                                          &dkp->pub));
+  deadline = GNUNET_TIME_timestamp_get ();
+  {
+    struct TALER_DenominationHashP dph;
+    struct TALER_AgeCommitmentHash agh;
+
+    FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+            plugin->ensure_coin_known (plugin->cls,
+                                       &deposit.coin,
+                                       &known_coin_id,
+                                       &dph,
+                                       &agh));
+  }
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->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);
+    bd.refund_deadline
+      = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
+    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,
+                                &bd,
+                                &deposit_timestamp,
+                                &balance_ok,
+                                &bad_balance_idx,
+                                &in_conflict));
+    FAILIF (! balance_ok);
+    FAILIF (in_conflict);
+  }
+
+  {
+    bool not_found;
+    bool refund_ok;
+    bool gone;
+    bool conflict;
+
+    refund.coin = deposit.coin;
+    refund.details.merchant_pub = bd.merchant_pub;
+    RND_BLK (&refund.details.merchant_sig);
+    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;
+    RND_BLK (&refund.details.merchant_sig);
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_refund (plugin->cls,
+                               &refund,
+                               &fees.deposit,
+                               known_coin_id,
+                               &not_found,
+                               &refund_ok,
+                               &gone,
+                               &conflict));
+    FAILIF (not_found);
+    FAILIF (! refund_ok);
+    FAILIF (gone);
+    FAILIF (conflict);
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->select_refunds_by_coin (plugin->cls,
+                                            &refund.coin.coin_pub,
+                                            &refund.details.merchant_pub,
+                                            &refund.details.h_contract_terms,
+                                            &check_refund_cb,
+                                            &refund));
+  }
+
+  /* test do_melt */
+  {
+    bool zombie_required = false;
+    bool balance_ok;
+
+    refresh.coin = deposit.coin;
+    RND_BLK (&refresh.coin_sig);
+    RND_BLK (&refresh.rc);
+    refresh.amount_with_fee = value;
+    refresh.noreveal_index = MELT_NOREVEAL_INDEX;
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_melt (plugin->cls,
+                             NULL,
+                             &refresh,
+                             known_coin_id,
+                             &zombie_required,
+                             &balance_ok));
+    FAILIF (! balance_ok);
+    FAILIF (zombie_required);
+  }
+
+  /* test get_melt */
+  {
+    struct TALER_EXCHANGEDB_Melt ret_refresh_session;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->get_melt (plugin->cls,
+                              &refresh.rc,
+                              &ret_refresh_session,
+                              &melt_serial_id));
+    FAILIF (refresh.noreveal_index !=
+            ret_refresh_session.session.noreveal_index);
+    FAILIF (0 !=
+            TALER_amount_cmp (&refresh.amount_with_fee,
+                              &ret_refresh_session.session.amount_with_fee));
+    FAILIF (0 !=
+            TALER_amount_cmp (&fees.refresh,
+                              &ret_refresh_session.melt_fee));
+    FAILIF (0 !=
+            GNUNET_memcmp (&refresh.rc,
+                           &ret_refresh_session.session.rc));
+    FAILIF (0 != GNUNET_memcmp (&refresh.coin_sig,
+                                &ret_refresh_session.session.coin_sig));
+    FAILIF (0 !=
+            GNUNET_memcmp (&refresh.coin.coin_pub,
+                           &ret_refresh_session.session.coin.coin_pub));
+    FAILIF (0 !=
+            GNUNET_memcmp (&refresh.coin.denom_pub_hash,
+                           &ret_refresh_session.session.coin.denom_pub_hash));
+  }
+
+  {
+    /* test 'select_refreshes_above_serial_id' */
+    auditor_row_cnt = 0;
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->select_refreshes_above_serial_id (plugin->cls,
+                                                      0,
+                                                      
&audit_refresh_session_cb,
+                                                      NULL));
+    FAILIF (1 != auditor_row_cnt);
+  }
+
+  /* do refresh-reveal */
+  {
+    new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+                                struct DenomKeyPair *);
+    new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+                                       struct TALER_DenominationPublicKey);
+    revealed_coins
+      = GNUNET_new_array (MELT_NEW_COINS,
+                          struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+    for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+    {
+      struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+      struct GNUNET_TIME_Timestamp now;
+      struct TALER_BlindedRsaPlanchet *rp;
+      struct TALER_BlindedPlanchet *bp;
+
+      now = GNUNET_TIME_timestamp_get ();
+      new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+                                            now,
+                                            &value,
+                                            &fees);
+      GNUNET_assert (NULL != new_dkp[cnt]);
+      new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+      ccoin = &revealed_coins[cnt];
+      bp = &ccoin->blinded_planchet;
+      bp->cipher = TALER_DENOMINATION_RSA;
+      rp = &bp->details.rsa_blinded_planchet;
+      rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+        GNUNET_CRYPTO_QUALITY_WEAK,
+        (RSA_KEY_SIZE / 8) - 1);
+      rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                  rp->blinded_msg,
+                                  rp->blinded_msg_size);
+      TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+                            &ccoin->h_denom_pub);
+      ccoin->exchange_vals = alg_values;
+      TALER_coin_ev_hash (bp,
+                          &ccoin->h_denom_pub,
+                          &ccoin->coin_envelope_hash);
+      GNUNET_assert (GNUNET_OK ==
+                     TALER_denom_sign_blinded (&ccoin->coin_sig,
+                                               &new_dkp[cnt]->priv,
+                                               true,
+                                               bp));
+    }
+    RND_BLK (&tprivs);
+    RND_BLK (&tpub);
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->get_refresh_reveal (plugin->cls,
+                                        &refresh.rc,
+                                        &never_called_cb,
+                                        NULL));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->insert_refresh_reveal (plugin->cls,
+                                           melt_serial_id,
+                                           MELT_NEW_COINS,
+                                           revealed_coins,
+                                           TALER_CNC_KAPPA - 1,
+                                           tprivs,
+                                           &tpub));
+    {
+      struct TALER_BlindedCoinHashP h_coin_ev;
+      struct TALER_CoinSpendPublicKeyP ocp;
+      struct TALER_DenominationHashP denom_hash;
+
+      TALER_denom_pub_hash (&new_denom_pubs[0],
+                            &denom_hash);
+      TALER_coin_ev_hash (&revealed_coins[0].blinded_planchet,
+                          &denom_hash,
+                          &h_coin_ev);
+      FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+              plugin->get_old_coin_by_h_blind (plugin->cls,
+                                               &h_coin_ev,
+                                               &ocp,
+                                               &rrc_serial));
+      FAILIF (0 !=
+              GNUNET_memcmp (&ocp,
+                             &refresh.coin.coin_pub));
+    }
+    FAILIF (0 >=
+            plugin->get_refresh_reveal (plugin->cls,
+                                        &refresh.rc,
+                                        &check_refresh_reveal_cb,
+                                        NULL));
+    qs = plugin->get_link_data (plugin->cls,
+                                &refresh.coin.coin_pub,
+                                &handle_link_data_cb,
+                                NULL);
+    FAILIF (0 >= qs);
+    {
+      /* Just to test fetching a coin with melt history */
+      struct TALER_EXCHANGEDB_TransactionList *tl;
+      enum GNUNET_DB_QueryStatus qs;
+
+      qs = plugin->get_coin_transactions (plugin->cls,
+                                          &refresh.coin.coin_pub,
+                                          &tl);
+      FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+      plugin->free_coin_transaction_list (plugin->cls,
+                                          tl);
+    }
+  }
+
+  /* do recoup-refresh */
+  {
+    struct GNUNET_TIME_Timestamp recoup_timestamp
+      = GNUNET_TIME_timestamp_get ();
+    union TALER_DenominationBlindingKeyP coin_bks;
+    uint64_t new_known_coin_id;
+    struct TALER_CoinPublicInfo new_coin;
+    struct TALER_DenominationHashP dph;
+    struct TALER_AgeCommitmentHash agh;
+    bool recoup_ok;
+    bool internal_failure;
+
+    new_coin = deposit.coin; /* steal basic data */
+    RND_BLK (&new_coin.coin_pub);
+    FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+            plugin->ensure_coin_known (plugin->cls,
+                                       &new_coin,
+                                       &new_known_coin_id,
+                                       &dph,
+                                       &agh));
+    RND_BLK (&coin_bks);
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_recoup_refresh (plugin->cls,
+                                       &deposit.coin.coin_pub,
+                                       rrc_serial,
+                                       &coin_bks,
+                                       &new_coin.coin_pub,
+                                       new_known_coin_id,
+                                       &coin_sig,
+                                       &recoup_timestamp,
+                                       &recoup_ok,
+                                       &internal_failure));
+    FAILIF (! recoup_ok);
+    FAILIF (internal_failure);
+  }
+
+  /* do recoup */
+  {
+    struct TALER_EXCHANGEDB_Reserve pre_reserve;
+    struct TALER_EXCHANGEDB_Reserve post_reserve;
+    struct TALER_Amount delta;
+    bool recoup_ok;
+    bool internal_failure;
+    struct GNUNET_TIME_Timestamp recoup_timestamp
+      = GNUNET_TIME_timestamp_get ();
+
+    pre_reserve.pub = reserve_pub;
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->reserves_get (plugin->cls,
+                                  &pre_reserve));
+    FAILIF (! TALER_amount_is_zero (&pre_reserve.balance));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->do_recoup (plugin->cls,
+                               &reserve_pub,
+                               reserve_out_serial_id,
+                               &coin_blind,
+                               &deposit.coin.coin_pub,
+                               known_coin_id,
+                               &coin_sig,
+                               &recoup_timestamp,
+                               &recoup_ok,
+                               &internal_failure));
+    FAILIF (internal_failure);
+    FAILIF (! recoup_ok);
+    post_reserve.pub = reserve_pub;
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->reserves_get (plugin->cls,
+                                  &post_reserve));
+    FAILIF (0 >=
+            TALER_amount_subtract (&delta,
+                                   &post_reserve.balance,
+                                   &pre_reserve.balance));
+    FAILIF (0 !=
+            TALER_amount_cmp (&delta,
+                              &value));
+  }
+
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-3"));
+
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->select_recoup_above_serial_id (plugin->cls,
+                                                 0,
+                                                 &recoup_cb,
+                                                 &coin_blind));
+  /* Do reserve close */
+  now = GNUNET_TIME_timestamp_get ();
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (CURRENCY ":0.000010",
+                                         &fee_closing));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->insert_reserve_closed (plugin->cls,
+                                         &reserve_pub2,
+                                         now,
+                                         sndr,
+                                         &wire_out_wtid,
+                                         &amount_with_fee,
+                                         &fee_closing,
+                                         0));
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub2,
+                         0,
+                         0,
+                         value.currency));
+  now = GNUNET_TIME_timestamp_get ();
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->insert_reserve_closed (plugin->cls,
+                                         &reserve_pub,
+                                         now,
+                                         sndr,
+                                         &wire_out_wtid,
+                                         &value,
+                                         &fee_closing,
+                                         0));
+  FAILIF (GNUNET_OK !=
+          check_reserve (&reserve_pub,
+                         0,
+                         0,
+                         value.currency));
+  result = 7;
+
+  /* check reserve history */
+  {
+    struct TALER_Amount balance;
+
+    qs = plugin->get_reserve_history (plugin->cls,
+                                      &reserve_pub,
+                                      &balance,
+                                      &rh);
+  }
+  FAILIF (0 > qs);
+  FAILIF (NULL == rh);
+  rh_head = rh;
+  for (cnt = 0; NULL != rh_head; rh_head = rh_head->next, cnt++)
+  {
+    switch (rh_head->type)
+    {
+    case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+      bt = rh_head->details.bank;
+      FAILIF (0 !=
+              GNUNET_memcmp (&bt->reserve_pub,
+                             &reserve_pub));
+      /* this is the amount we transferred twice*/
+      FAILIF (1 != bt->amount.value);
+      FAILIF (1000 != bt->amount.fraction);
+      FAILIF (0 != strcmp (CURRENCY, bt->amount.currency));
+      FAILIF (NULL == bt->sender_account_details);
+      break;
+    case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
+      withdraw = rh_head->details.withdraw;
+      FAILIF (0 !=
+              GNUNET_memcmp (&withdraw->reserve_pub,
+                             &reserve_pub));
+      FAILIF (0 !=
+              GNUNET_memcmp (&withdraw->h_coin_envelope,
+                             &cbc.h_coin_envelope));
+      break;
+    case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+      {
+        struct TALER_EXCHANGEDB_Recoup *recoup = rh_head->details.recoup;
+
+        FAILIF (0 !=
+                GNUNET_memcmp (&recoup->coin_sig,
+                               &coin_sig));
+        FAILIF (0 !=
+                GNUNET_memcmp (&recoup->coin_blind,
+                               &coin_blind));
+        FAILIF (0 !=
+                GNUNET_memcmp (&recoup->reserve_pub,
+                               &reserve_pub));
+        FAILIF (0 !=
+                GNUNET_memcmp (&recoup->coin.coin_pub,
+                               &deposit.coin.coin_pub));
+        FAILIF (0 !=
+                TALER_amount_cmp (&recoup->value,
+                                  &value));
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+      {
+        struct TALER_EXCHANGEDB_ClosingTransfer *closing
+          = rh_head->details.closing;
+
+        FAILIF (0 !=
+                GNUNET_memcmp (&closing->reserve_pub,
+                               &reserve_pub));
+        FAILIF (0 != TALER_amount_cmp (&closing->amount,
+                                       &amount_with_fee));
+        FAILIF (0 != TALER_amount_cmp (&closing->closing_fee,
+                                       &fee_closing));
+      }
+      break;
+    case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+      {
+        /* FIXME: not yet tested */
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+      {
+        /* FIXME: not yet tested */
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+      {
+        /* FIXME: not yet tested */
+        break;
+      }
+    case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+      {
+        /* FIXME: not yet tested */
+        break;
+      }
+    }
+  }
+  GNUNET_assert (4 == cnt);
+  FAILIF (4 != cnt);
+
+  auditor_row_cnt = 0;
+  FAILIF (0 >=
+          plugin->select_reserves_in_above_serial_id (plugin->cls,
+                                                      0,
+                                                      &audit_reserve_in_cb,
+                                                      NULL));
+  FAILIF (0 >=
+          plugin->select_withdrawals_above_serial_id (plugin->cls,
+                                                      0,
+                                                      &audit_reserve_out_cb,
+                                                      NULL));
+  FAILIF (3 != auditor_row_cnt);
+
+
+  auditor_row_cnt = 0;
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->select_refunds_above_serial_id (plugin->cls,
+                                                  0,
+                                                  &audit_refund_cb,
+                                                  NULL));
+  FAILIF (1 != auditor_row_cnt);
+  qs = plugin->get_coin_transactions (plugin->cls,
+                                      &refund.coin.coin_pub,
+                                      &tl);
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+  GNUNET_assert (NULL != tl);
+  matched = 0;
+  for (tlp = tl; NULL != tlp; tlp = tlp->next)
+  {
+    switch (tlp->type)
+    {
+    case TALER_EXCHANGEDB_TT_DEPOSIT:
+      {
+        struct TALER_EXCHANGEDB_DepositListEntry *have = tlp->details.deposit;
+
+        /* Note: we're not comparing the denomination keys, as there is
+           still the question of whether we should even bother exporting
+           them here. */
+        FAILIF (0 !=
+                GNUNET_memcmp (&have->csig,
+                               &deposit.csig));
+        FAILIF (0 !=
+                GNUNET_memcmp (&have->merchant_pub,
+                               &bd.merchant_pub));
+        FAILIF (0 !=
+                GNUNET_memcmp (&have->h_contract_terms,
+                               &bd.h_contract_terms));
+        FAILIF (0 !=
+                GNUNET_memcmp (&have->wire_salt,
+                               &bd.wire_salt));
+        FAILIF (GNUNET_TIME_timestamp_cmp (have->timestamp,
+                                           !=,
+                                           bd.wallet_timestamp));
+        FAILIF (GNUNET_TIME_timestamp_cmp (have->refund_deadline,
+                                           !=,
+                                           bd.refund_deadline));
+        FAILIF (GNUNET_TIME_timestamp_cmp (have->wire_deadline,
+                                           !=,
+                                           bd.wire_deadline));
+        FAILIF (0 != TALER_amount_cmp (&have->amount_with_fee,
+                                       &deposit.amount_with_fee));
+        matched |= 1;
+        break;
+      }
+    /* this coin pub was actually never melted... */
+    case TALER_EXCHANGEDB_TT_MELT:
+      FAILIF (0 !=
+              GNUNET_memcmp (&refresh.rc,
+                             &tlp->details.melt->rc));
+      matched |= 2;
+      break;
+    case TALER_EXCHANGEDB_TT_REFUND:
+      {
+        struct TALER_EXCHANGEDB_RefundListEntry *have = tlp->details.refund;
+
+        /* Note: we're not comparing the denomination keys, as there is
+           still the question of whether we should even bother exporting
+           them here. */
+        FAILIF (0 != GNUNET_memcmp (&have->merchant_pub,
+                                    &refund.details.merchant_pub));
+        FAILIF (0 != GNUNET_memcmp (&have->merchant_sig,
+                                    &refund.details.merchant_sig));
+        FAILIF (0 != GNUNET_memcmp (&have->h_contract_terms,
+                                    &refund.details.h_contract_terms));
+        FAILIF (have->rtransaction_id != refund.details.rtransaction_id);
+        FAILIF (0 != TALER_amount_cmp (&have->refund_amount,
+                                       &refund.details.refund_amount));
+        FAILIF (0 != TALER_amount_cmp (&have->refund_fee,
+                                       &refund.details.refund_fee));
+        matched |= 4;
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_RECOUP:
+      {
+        struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
+          tlp->details.recoup;
+
+        FAILIF (0 != GNUNET_memcmp (&recoup->coin_sig,
+                                    &coin_sig));
+        FAILIF (0 != GNUNET_memcmp (&recoup->coin_blind,
+                                    &coin_blind));
+        FAILIF (0 != GNUNET_memcmp (&recoup->reserve_pub,
+                                    &reserve_pub));
+        FAILIF (0 != TALER_amount_cmp (&recoup->value,
+                                       &value));
+        matched |= 8;
+        break;
+      }
+    case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+      /* TODO: check fields better... */
+      matched |= 16;
+      break;
+    default:
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected coin history transaction type: %d\n",
+                  tlp->type);
+      FAILIF (1);
+      break;
+    }
+  }
+  FAILIF (31 != matched);
+
+  plugin->free_coin_transaction_list (plugin->cls,
+                                      tl);
+
+
+  /* Tests for deposits+wire */
+  TALER_denom_sig_free (&deposit.coin.denom_sig);
+  memset (&deposit,
+          0,
+          sizeof (deposit));
+  RND_BLK (&deposit.coin.coin_pub);
+  TALER_denom_pub_hash (&dkp->pub,
+                        &deposit.coin.denom_pub_hash);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+                                          &cbc.sig,
+                                          &bks,
+                                          &c_hash,
+                                          &alg_values,
+                                          &dkp->pub));
+  RND_BLK (&deposit.csig);
+  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",
+    &bd.wire_salt,
+    &h_wire_wt);
+  deposit.amount_with_fee = value;
+  bd.refund_deadline = deadline;
+  bd.wire_deadline = deadline;
+  result = 8;
+  {
+    uint64_t known_coin_id;
+    struct TALER_DenominationHashP dph;
+    struct TALER_AgeCommitmentHash agh;
+
+    FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+            plugin->ensure_coin_known (plugin->cls,
+                                       &deposit.coin,
+                                       &known_coin_id,
+                                       &dph,
+                                       &agh));
+  }
+  {
+    struct GNUNET_TIME_Timestamp now;
+    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->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,
+                                   &bd.h_contract_terms,
+                                   &h_wire,
+                                   &deposit.coin.coin_pub,
+                                   &bd.merchant_pub,
+                                   bd.refund_deadline,
+                                   &deposit_fee,
+                                   &r));
+    FAILIF (GNUNET_TIME_timestamp_cmp (now,
+                                       !=,
+                                       r));
+  }
+  {
+    struct GNUNET_TIME_Timestamp start_range;
+    struct GNUNET_TIME_Timestamp end_range;
+
+    start_range = GNUNET_TIME_absolute_to_timestamp (
+      GNUNET_TIME_absolute_subtract (deadline.abs_time,
+                                     GNUNET_TIME_UNIT_SECONDS));
+    end_range = GNUNET_TIME_absolute_to_timestamp (
+      GNUNET_TIME_absolute_add (deadline.abs_time,
+                                GNUNET_TIME_UNIT_SECONDS));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            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_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 */
+  {
+    struct TALER_MerchantPublicKeyP merchant_pub2;
+    char *payto_uri2;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->get_ready_deposit (plugin->cls,
+                                       0,
+                                       INT32_MAX,
+                                       &merchant_pub2,
+                                       &payto_uri2));
+    FAILIF (0 != GNUNET_memcmp (&merchant_pub2,
+                                &bd.merchant_pub));
+    FAILIF (0 != strcmp (payto_uri2,
+                         bd.receiver_wire_account));
+    TALER_payto_hash (payto_uri2,
+                      &wire_target_h_payto);
+    GNUNET_free (payto_uri2);
+  }
+
+  {
+    struct TALER_Amount total;
+    struct TALER_WireTransferIdentifierRawP wtid;
+
+    memset (&wtid,
+            41,
+            sizeof (wtid));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->aggregate (plugin->cls,
+                               &wire_target_h_payto,
+                               &bd.merchant_pub,
+                               &wtid,
+                               &total));
+  }
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-3"));
+  {
+    struct TALER_WireTransferIdentifierRawP wtid;
+    struct TALER_Amount total;
+    struct TALER_WireTransferIdentifierRawP wtid2;
+    struct TALER_Amount total2;
+
+    memset (&wtid,
+            42,
+            sizeof (wtid));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (CURRENCY ":42",
+                                           &total));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->select_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &bd.merchant_pub,
+                                                  "x-bank",
+                                                  &wtid2,
+                                                  &total2));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->create_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  "x-bank",
+                                                  &bd.merchant_pub,
+                                                  &wtid,
+                                                  0,
+                                                  &total));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->select_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &bd.merchant_pub,
+                                                  "x-bank",
+                                                  &wtid2,
+                                                  &total2));
+    FAILIF (0 !=
+            GNUNET_memcmp (&wtid2,
+                           &wtid));
+    FAILIF (0 !=
+            TALER_amount_cmp (&total2,
+                              &total));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (CURRENCY ":43",
+                                           &total));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->update_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &wtid,
+                                                  0,
+                                                  &total));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->select_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &bd.merchant_pub,
+                                                  "x-bank",
+                                                  &wtid2,
+                                                  &total2));
+    FAILIF (0 !=
+            GNUNET_memcmp (&wtid2,
+                           &wtid));
+    FAILIF (0 !=
+            TALER_amount_cmp (&total2,
+                              &total));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->delete_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &wtid));
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->select_aggregation_transient (plugin->cls,
+                                                  &wire_target_h_payto,
+                                                  &bd.merchant_pub,
+                                                  "x-bank",
+                                                  &wtid2,
+                                                  &total2));
+  }
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+
+  result = 10;
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-2"));
+  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 (bd.receiver_wire_account,
+                                        &bd.wire_salt,
+                                        &h_wire);
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->have_deposit2 (plugin->cls,
+                                   &bd.h_contract_terms,
+                                   &h_wire,
+                                   &deposit.coin.coin_pub,
+                                   &mpub2,
+                                   bd.refund_deadline,
+                                   &deposit_fee,
+                                   &r));
+    RND_BLK (&cpub2); /* should fail if coin is different */
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+            plugin->have_deposit2 (plugin->cls,
+                                   &bd.h_contract_terms,
+                                   &h_wire,
+                                   &cpub2,
+                                   &bd.merchant_pub,
+                                   bd.refund_deadline,
+                                   &deposit_fee,
+                                   &r));
+  }
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+
+
+  /* test revocation */
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-3b"));
+  RND_BLK (&master_sig);
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          plugin->insert_denomination_revocation (plugin->cls,
+                                                  &cbc.denom_pub_hash,
+                                                  &master_sig));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->commit (plugin->cls));
+  plugin->preflight (plugin->cls);
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-4"));
+  FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+          plugin->insert_denomination_revocation (plugin->cls,
+                                                  &cbc.denom_pub_hash,
+                                                  &master_sig));
+  plugin->rollback (plugin->cls);
+  plugin->preflight (plugin->cls);
+  FAILIF (GNUNET_OK !=
+          plugin->start (plugin->cls,
+                         "test-5"));
+  {
+    struct TALER_MasterSignatureP msig;
+    uint64_t rev_rowid;
+
+    FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+            plugin->get_denomination_revocation (plugin->cls,
+                                                 &cbc.denom_pub_hash,
+                                                 &msig,
+                                                 &rev_rowid));
+    FAILIF (0 != GNUNET_memcmp (&msig,
+                                &master_sig));
+  }
+
+
+  plugin->rollback (plugin->cls);
+  FAILIF (GNUNET_OK !=
+          test_wire_prepare ());
+  FAILIF (GNUNET_OK !=
+          test_wire_out (&bd));
+  FAILIF (GNUNET_OK !=
+          test_gc ());
+  FAILIF (GNUNET_OK !=
+          test_wire_fees ());
+
+  plugin->preflight (plugin->cls);
+
+  result = 0;
+
+drop:
+  if (0 != result)
+    plugin->rollback (plugin->cls);
+  if (NULL != rh)
+    plugin->free_reserve_history (plugin->cls,
+                                  rh);
+  rh = NULL;
+  GNUNET_break (GNUNET_OK ==
+                plugin->drop_tables (plugin->cls));
+cleanup:
+  if (NULL != dkp)
+    destroy_denom_key_pair (dkp);
+  if (NULL != revealed_coins)
+  {
+    for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+    {
+      TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+      TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+    }
+    GNUNET_free (revealed_coins);
+    revealed_coins = NULL;
+  }
+  GNUNET_free (new_denom_pubs);
+  for (unsigned int cnt = 0;
+       (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+       cnt++)
+    destroy_denom_key_pair (new_dkp[cnt]);
+  GNUNET_free (new_dkp);
+  TALER_denom_sig_free (&deposit.coin.denom_sig);
+  TALER_blinded_denom_sig_free (&cbc.sig);
+  TALER_blinded_denom_sig_free (&cbc2.sig);
+  dkp = NULL;
+  TALER_EXCHANGEDB_plugin_unload (plugin);
+  plugin = NULL;
+}
+
+
+int
+main (int argc,
+      char *const argv[])
+{
+  const char *plugin_name;
+  char *config_filename;
+  char *testname;
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+
+  (void) argc;
+  result = -1;
+  if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+  {
+    GNUNET_break (0);
+    return -1;
+  }
+  GNUNET_log_setup (argv[0],
+                    "WARNING",
+                    NULL);
+  plugin_name++;
+  (void) GNUNET_asprintf (&testname,
+                          "test-exchange-db-%s",
+                          plugin_name);
+  (void) GNUNET_asprintf (&config_filename,
+                          "%s.conf",
+                          testname);
+  cfg = GNUNET_CONFIGURATION_create ();
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_parse (cfg,
+                                  config_filename))
+  {
+    GNUNET_break (0);
+    GNUNET_free (config_filename);
+    GNUNET_free (testname);
+    return 2;
+  }
+  GNUNET_SCHEDULER_run (&run,
+                        cfg);
+  GNUNET_CONFIGURATION_destroy (cfg);
+  GNUNET_free (config_filename);
+  GNUNET_free (testname);
+  return result;
+}
+
+
+/* end of test_exchangedb.c */
diff --git a/src/exchangedb/versioning.sql b/src/exchangedb/versioning.sql
new file mode 100644
index 0000000..444cf95
--- /dev/null
+++ b/src/exchangedb/versioning.sql
@@ -0,0 +1,294 @@
+-- LICENSE AND COPYRIGHT
+--
+-- Copyright (C) 2010 Hubert depesz Lubaczewski
+--
+-- This program is distributed under the (Revised) BSD License:
+-- L<http://www.opensource.org/licenses/bsd-license.php>
+--
+-- Redistribution and use in source and binary forms, with or without
+-- modification, are permitted provided that the following conditions
+-- are met:
+--
+-- * Redistributions of source code must retain the above copyright
+-- notice, this list of conditions and the following disclaimer.
+--
+-- * Redistributions in binary form must reproduce the above copyright
+--   notice, this list of conditions and the following disclaimer in the
+--   documentation and/or other materials provided with the distribution.
+--
+-- * Neither the name of Hubert depesz Lubaczewski's Organization
+--   nor the names of its contributors may be used to endorse or
+--   promote products derived from this software without specific
+--   prior written permission.
+--
+-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+-- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
ARE
+-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+-- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY,
+-- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE
+-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+--
+-- Code origin: 
https://gitlab.com/depesz/Versioning/blob/master/install.versioning.sql
+--
+--
+-- # NAME
+--
+-- **Versioning** - simplistic take on tracking and applying changes to 
databases.
+--
+-- # DESCRIPTION
+--
+-- This project strives to provide simple way to manage changes to
+-- database.
+--
+-- Instead of making changes on development server, then finding
+-- differences between production and development, deciding which ones
+-- should be installed on production, and finding a way to install them -
+-- you start with writing diffs themselves!
+--
+-- # INSTALLATION
+--
+-- To install versioning simply run install.versioning.sql in your database
+-- (all of them: production, stage, test, devel, ...).
+--
+-- # USAGE
+--
+-- In your files with patches to database, put whole logic in single
+-- transaction, and use \_v.\* functions - usually \_v.register_patch() at
+-- least to make sure everything is OK.
+--
+-- For example. Let's assume you have patch files:
+--
+-- ## 0001.sql:
+--
+-- ```
+-- create table users (id serial primary key, username text);
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- insert into users (username) values ('depesz');
+-- ```
+-- To change it to use versioning you would change the files, to this
+-- state:
+--
+-- 0000.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('000-base', NULL, NULL);
+-- create table users (id serial primary key, username text);
+-- COMMIT;
+-- ```
+--
+-- ## 0002.sql:
+--
+-- ```
+-- BEGIN;
+-- select _v.register_patch('001-users', ARRAY['000-base'], NULL);
+-- insert into users (username) values ('depesz');
+-- COMMIT;
+-- ```
+--
+-- This will make sure that patch 001-users can only be applied after
+-- 000-base.
+--
+-- # AVAILABLE FUNCTIONS
+--
+-- ## \_v.register_patch( TEXT )
+--
+-- Registers named patch, or dies if it is already registered.
+--
+-- Returns integer which is id of patch in \_v.patches table - only if it
+-- succeeded.
+--
+-- ## \_v.register_patch( TEXT, TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT ), but checks is all given patches (given 
as
+-- array in second argument) are already registered.
+--
+-- ## \_v.register_patch( TEXT, TEXT[], TEXT[] )
+--
+-- Same as \_v.register_patch( TEXT, TEXT[] ), but also checks if there are no 
conflicts with preexisting patches.
+--
+-- Third argument is array of names of patches that conflict with current one. 
So
+-- if any of them is installed - register_patch will error out.
+--
+-- ## \_v.unregister_patch( TEXT )
+--
+-- Removes information about given patch from the versioning data.
+--
+-- It doesn't remove objects that were created by this patch - just removes
+-- metainformation.
+--
+-- ## \_v.assert_user_is_superuser()
+--
+-- Make sure that current patch is being loaded by superuser.
+--
+-- If it's not - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_not_superuser()
+--
+-- Make sure that current patch is not being loaded by superuser.
+--
+-- If it is - it will raise exception, and break transaction.
+--
+-- ## \_v.assert_user_is_one_of(TEXT, TEXT, ... )
+--
+-- Make sure that current patch is being loaded by one of listed users.
+--
+-- If ```current_user``` is not listed as one of arguments - function will 
raise
+-- exception and break the transaction.
+
+BEGIN;
+
+
+-- This file adds versioning support to database it will be loaded to.
+-- It requires that PL/pgSQL is already loaded - will raise exception 
otherwise.
+-- All versioning "stuff" (tables, functions) is in "_v" schema.
+
+-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them 
to RETURN literally nothing (0 rows).
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql 
when calling
+CREATE SCHEMA IF NOT EXISTS _v;
+COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';
+
+CREATE TABLE IF NOT EXISTS _v.patches (
+    patch_name  TEXT        PRIMARY KEY,
+    applied_tsz TIMESTAMPTZ NOT NULL DEFAULT now(),
+    applied_by  TEXT        NOT NULL,
+    requires    TEXT[],
+    conflicts   TEXT[]
+);
+COMMENT ON TABLE _v.patches              IS 'Contains information about what 
patches are currently applied on database.';
+COMMENT ON COLUMN _v.patches.patch_name  IS 'Name of patch, has to be unique 
for every patch.';
+COMMENT ON COLUMN _v.patches.applied_tsz IS 'When the patch was applied.';
+COMMENT ON COLUMN _v.patches.applied_by  IS 'Who applied this patch 
(PostgreSQL username)';
+COMMENT ON COLUMN _v.patches.requires    IS 'List of patches that are required 
for given patch.';
+COMMENT ON COLUMN _v.patches.conflicts   IS 'List of patches that conflict 
with given patch.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( IN in_patch_name TEXT, IN 
in_requirements TEXT[], in_conflicts TEXT[], OUT versioning INT4 ) RETURNS 
setof INT4 AS $$
+DECLARE
+    t_text   TEXT;
+    t_text_a TEXT[];
+    i INT4;
+BEGIN
+    -- Thanks to this we know only one patch will be applied at a time
+    LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+    SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_patch_name;
+    IF FOUND THEN
+        RAISE EXCEPTION 'Patch % is already applied!', in_patch_name;
+    END IF;
+
+    t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE patch_name = 
any( in_conflicts ) );
+    IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+        RAISE EXCEPTION 'Versioning patches conflict. Conflicting patche(s) 
installed: %.', array_to_string( t_text_a, ', ' );
+    END IF;
+
+    IF array_upper( in_requirements, 1 ) IS NOT NULL THEN
+        t_text_a := '{}';
+        FOR i IN array_lower( in_requirements, 1 ) .. array_upper( 
in_requirements, 1 ) LOOP
+            SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_requirements[i];
+            IF NOT FOUND THEN
+                t_text_a := t_text_a || in_requirements[i];
+            END IF;
+        END LOOP;
+        IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+            RAISE EXCEPTION 'Missing prerequisite(s): %.', array_to_string( 
t_text_a, ', ' );
+        END IF;
+    END IF;
+
+    INSERT INTO _v.patches (patch_name, applied_tsz, applied_by, requires, 
conflicts ) VALUES ( in_patch_name, now(), current_user, coalesce( 
in_requirements, '{}' ), coalesce( in_conflicts, '{}' ) );
+    RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[], TEXT[] ) IS 'Function to 
register patches in database. Raises exception if there are conflicts, 
prerequisites are not installed or the migration has already been installed.';
+
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT, TEXT[] ) RETURNS setof 
INT4 AS $$
+    SELECT _v.register_patch( $1, $2, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT, TEXT[] ) IS 'Wrapper to allow 
registration of patches without conflicts.';
+CREATE OR REPLACE FUNCTION _v.register_patch( TEXT ) RETURNS setof INT4 AS $$
+    SELECT _v.register_patch( $1, NULL, NULL );
+$$ language sql;
+COMMENT ON FUNCTION _v.register_patch( TEXT ) IS 'Wrapper to allow 
registration of patches without requirements and conflicts.';
+
+CREATE OR REPLACE FUNCTION _v.unregister_patch( IN in_patch_name TEXT, OUT 
versioning INT4 ) RETURNS setof INT4 AS $$
+DECLARE
+    i        INT4;
+    t_text_a TEXT[];
+BEGIN
+    -- Thanks to this we know only one patch will be applied at a time
+    LOCK TABLE _v.patches IN EXCLUSIVE MODE;
+
+    t_text_a := ARRAY( SELECT patch_name FROM _v.patches WHERE in_patch_name = 
ANY( requires ) );
+    IF array_upper( t_text_a, 1 ) IS NOT NULL THEN
+        RAISE EXCEPTION 'Cannot uninstall %, as it is required by: %.', 
in_patch_name, array_to_string( t_text_a, ', ' );
+    END IF;
+
+    DELETE FROM _v.patches WHERE patch_name = in_patch_name;
+    GET DIAGNOSTICS i = ROW_COUNT;
+    IF i < 1 THEN
+        RAISE EXCEPTION 'Patch % is not installed, so it can''t be 
uninstalled!', in_patch_name;
+    END IF;
+
+    RETURN;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.unregister_patch( TEXT ) IS 'Function to unregister 
patches in database. Dies if the patch is not registered, or if unregistering 
it would break dependencies.';
+
+CREATE OR REPLACE FUNCTION _v.assert_patch_is_applied( IN in_patch_name TEXT ) 
RETURNS TEXT as $$
+DECLARE
+    t_text TEXT;
+BEGIN
+    SELECT patch_name INTO t_text FROM _v.patches WHERE patch_name = 
in_patch_name;
+    IF NOT FOUND THEN
+        RAISE EXCEPTION 'Patch % is not applied!', in_patch_name;
+    END IF;
+    RETURN format('Patch %s is applied.', in_patch_name);
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_patch_is_applied( TEXT ) IS 'Function that can 
be used to make sure that patch has been applied.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_superuser() RETURNS TEXT as $$
+DECLARE
+    v_super bool;
+BEGIN
+    SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+    IF v_super THEN
+        RETURN 'assert_user_is_superuser: OK';
+    END IF;
+    RAISE EXCEPTION 'Current user is not superuser - cannot continue.';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_superuser() IS 'Function that can be 
used to make sure that patch is being applied using superuser account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_not_superuser() RETURNS TEXT as $$
+DECLARE
+    v_super bool;
+BEGIN
+    SELECT usesuper INTO v_super FROM pg_user WHERE usename = current_user;
+    IF v_super THEN
+        RAISE EXCEPTION 'Current user is superuser - cannot continue.';
+    END IF;
+    RETURN 'assert_user_is_not_superuser: OK';
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_not_superuser() IS 'Function that can be 
used to make sure that patch is being applied using normal (not superuser) 
account.';
+
+CREATE OR REPLACE FUNCTION _v.assert_user_is_one_of(VARIADIC 
p_acceptable_users TEXT[] ) RETURNS TEXT as $$
+DECLARE
+BEGIN
+    IF current_user = any( p_acceptable_users ) THEN
+        RETURN 'assert_user_is_one_of: OK';
+    END IF;
+    RAISE EXCEPTION 'User is not one of: % - cannot continue.', 
p_acceptable_users;
+END;
+$$ language plpgsql;
+COMMENT ON FUNCTION _v.assert_user_is_one_of(TEXT[]) IS 'Function that can be 
used to make sure that patch is being applied by one of defined users.';
+
+COMMIT;
diff --git a/src/include/.gitignore b/src/include/.gitignore
new file mode 100644
index 0000000..beba883
--- /dev/null
+++ b/src/include/.gitignore
@@ -0,0 +1 @@
+taler_signatures.h
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
new file mode 100644
index 0000000..45d74ab
--- /dev/null
+++ b/src/include/Makefile.am
@@ -0,0 +1,36 @@
+# This Makefile.am is in the public domain
+talerincludedir = $(includedir)/taler
+
+talerinclude_HEADERS = \
+  platform.h gettext.h \
+  taler_auditor_service.h \
+  taler_amount_lib.h \
+  taler_attributes.h \
+  taler_auditordb_lib.h \
+  taler_auditordb_plugin.h \
+  taler_bank_service.h \
+  taler_crypto_lib.h \
+  taler_curl_lib.h \
+  taler_dbevents.h \
+  taler_error_codes.h \
+  taler_exchange_service.h \
+  taler_exchangedb_lib.h \
+  taler_exchangedb_plugin.h \
+  taler_extensions.h \
+  taler_extensions_policy.h \
+  taler_fakebank_lib.h \
+  taler_kyclogic_lib.h \
+  taler_kyclogic_plugin.h \
+  taler_json_lib.h \
+  taler_testing_lib.h \
+  taler_util.h \
+  taler_mhd_lib.h \
+  taler_pq_lib.h \
+  taler_signatures.h \
+  taler_sq_lib.h \
+  taler_templating_lib.h \
+  taler_twister_testing_lib.h
+
+EXTRA_DIST = \
+  backoff.h \
+  gauger.h
diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h
new file mode 100644
index 0000000..67e5ff7
--- /dev/null
+++ b/src/include/taler_crypto_lib.h
@@ -0,0 +1,6091 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/taler_crypto_lib.h
+ * @brief taler-specific crypto functions
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff <christian@grothoff.org>
+ * @author Özgür Kesim <oec-taler@kesim.org>
+ */
+#if ! defined (__TALER_UTIL_LIB_H_INSIDE__)
+#error "Only <taler_util.h> can be included directly."
+#endif
+
+#ifndef TALER_CRYPTO_LIB_H
+#define TALER_CRYPTO_LIB_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_error_codes.h"
+#include <gcrypt.h>
+#include <jansson.h>
+
+
+/**
+ * Maximum number of coins we allow per operation.
+ */
+#define TALER_MAX_FRESH_COINS 256
+
+/**
+ * Cut-and-choose size for refreshing.  Client looses the gamble (of
+ * unaccountable transfers) with probability 1/TALER_CNC_KAPPA.  Refresh cost
+ * increases linearly with TALER_CNC_KAPPA, and 3 is sufficient up to a
+ * income/sales tax of 66% of total transaction value.  As there is
+ * no good reason to change this security parameter, we declare it
+ * fixed and part of the protocol.
+ */
+#define TALER_CNC_KAPPA 3
+#define TALER_CNC_KAPPA_MINUS_ONE_STR "2"
+
+
+/* ****************** Coin crypto primitives ************* */
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Type of public keys for Taler security modules (software or 
hardware).
+ * Note that there are usually at least two security modules (RSA and EdDSA),
+ * each with its own private key.
+ */
+struct TALER_SecurityModulePublicKeyP
+{
+  /**
+   * Taler uses EdDSA for security modules.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+/**
+ * @brief Set of the public keys of the security modules
+ */
+struct TALER_SecurityModulePublicKeySetP
+{
+  /**
+   * Public key of the RSA security module
+   */
+  struct TALER_SecurityModulePublicKeyP rsa;
+
+  /**
+   * Public key of the CS security module
+   */
+  struct TALER_SecurityModulePublicKeyP cs;
+
+  /**
+   * Public key of the eddsa security module
+   */
+  struct TALER_SecurityModulePublicKeyP eddsa;
+};
+
+/**
+ * @brief Type of private keys for Taler security modules (software or 
hardware).
+ */
+struct TALER_SecurityModulePrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for security modules.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used for Taler security modules (software or 
hardware).
+ */
+struct TALER_SecurityModuleSignatureP
+{
+  /**
+   * Taler uses EdDSA for security modules.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of public keys for Taler reserves.
+ */
+struct TALER_ReservePublicKeyP
+{
+  /**
+   * Taler uses EdDSA for reserves.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of private keys for Taler reserves.
+ */
+struct TALER_ReservePrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for reserves.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used with Taler reserves.
+ */
+struct TALER_ReserveSignatureP
+{
+  /**
+   * Taler uses EdDSA for reserves.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * (Symmetric) key used to encrypt KYC attribute data in the database.
+ */
+struct TALER_AttributeKeyP
+{
+  /**
+   * Actual key material.
+   */
+  struct GNUNET_HashCode key;
+};
+
+
+/**
+ * @brief Type of public keys to for merchant authorizations.
+ * Merchants can issue refunds using the corresponding
+ * private key.
+ */
+struct TALER_MerchantPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for merchants.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of private keys for merchant authorizations.
+ * Merchants can issue refunds using the corresponding
+ * private key.
+ */
+struct TALER_MerchantPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for merchants.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures made by merchants.
+ */
+struct TALER_MerchantSignatureP
+{
+  /**
+   * Taler uses EdDSA for merchants.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_sig;
+};
+
+
+/**
+ * @brief Type of transfer public keys used during refresh
+ * operations.
+ */
+struct TALER_TransferPublicKeyP
+{
+  /**
+   * Taler uses ECDHE for transfer keys.
+   */
+  struct GNUNET_CRYPTO_EcdhePublicKey ecdhe_pub;
+};
+
+
+/**
+ * @brief Type of transfer private keys used during refresh
+ * operations.
+ */
+struct TALER_TransferPrivateKeyP
+{
+  /**
+   * Taler uses ECDHE for melting session keys.
+   */
+  struct GNUNET_CRYPTO_EcdhePrivateKey ecdhe_priv;
+};
+
+
+/**
+ * @brief Type of public keys used for contract
+ * encryption.
+ */
+struct TALER_ContractDiffiePublicP
+{
+  /**
+   * Taler uses ECDHE for contract encryption.
+   */
+  struct GNUNET_CRYPTO_EcdhePublicKey ecdhe_pub;
+};
+
+
+/**
+ * @brief Type of private keys used for contract
+ * encryption.
+ */
+struct TALER_ContractDiffiePrivateP
+{
+  /**
+   * Taler uses ECDHE for contract encryption.
+   */
+  struct GNUNET_CRYPTO_EcdhePrivateKey ecdhe_priv;
+};
+
+
+/**
+ * @brief Type of online public keys used by the exchange to sign
+ * messages.
+ */
+struct TALER_ExchangePublicKeyP
+{
+  /**
+   * Taler uses EdDSA for online exchange message signing.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online public keys used by the exchange to
+ * sign messages.
+ */
+struct TALER_ExchangePrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by the exchange to sign messages online.
+ */
+struct TALER_ExchangeSignatureP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of the offline master public key used by the exchange.
+ */
+struct TALER_MasterPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for the long-term offline master key.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of the offline master public keys used by the exchange.
+ */
+struct TALER_MasterPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for the long-term offline master key.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures by the offline master public key used by the 
exchange.
+ */
+struct TALER_MasterSignatureP
+{
+  /**
+   * Taler uses EdDSA for the long-term offline master key.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of the private key used by the auditor.
+ */
+struct TALER_AuditorPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for the auditor's signing key.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of the public key used by the auditor.
+ */
+struct TALER_AuditorPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for the auditor's signing key.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of signatures used by the auditor.
+ */
+struct TALER_AuditorSignatureP
+{
+  /**
+   * Taler uses EdDSA signatures for auditors.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_sig;
+};
+
+
+/**
+ * @brief Type of public keys for Taler coins.  The same key material is used
+ * for EdDSA and ECDHE operations.
+ */
+struct TALER_CoinSpendPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for coins when signing deposit requests.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+
+};
+
+
+/**
+ * @brief Type of private keys for Taler coins.  The same key material is used
+ * for EdDSA and ECDHE operations.
+ */
+struct TALER_CoinSpendPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for coins when signing deposit requests.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+/**
+ * @brief Type of signatures made with Taler coins.
+ */
+struct TALER_CoinSpendSignatureP
+{
+  /**
+   * Taler uses EdDSA for coins.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of private keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPrivateKeyP
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+  /**
+   * Taler uses EcDSA for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_EcdsaPrivateKey priv;
+#else
+  /**
+   * Taler uses Edx25519 for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_Edx25519PrivateKey priv;
+#endif
+};
+
+
+/**
+ * @brief Type of public keys for age commitment in coins.
+ */
+struct TALER_AgeCommitmentPublicKeyP
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+  /**
+   * Taler uses EcDSA for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_EcdsaPublicKey pub;
+#else
+  /**
+   * Taler uses Edx25519 for coins when signing age verification attestation.
+   */
+  struct GNUNET_CRYPTO_Edx25519PublicKey pub;
+#endif
+};
+
+
+/*
+ * @brief Hash to represent the commitment to n*kappa blinded keys during a
+ * age-withdrawal. It is the running SHA512 hash over the hashes of the blinded
+ * envelopes of n*kappa coins.
+ */
+struct TALER_AgeWithdrawCommitmentHashP
+{
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Type of online public keys used by the wallet to establish a purse 
and the associated contract meta data.
+ */
+struct TALER_PurseContractPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for purse message signing.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used by the wallet to
+ * bind a purse to a particular contract (and other meta data).
+ */
+struct TALER_PurseContractPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by the wallet to sign purse creation 
messages online.
+ */
+struct TALER_PurseContractSignatureP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of online public keys used by the wallet to
+ * sign a merge of a purse into an account.
+ */
+struct TALER_PurseMergePublicKeyP
+{
+  /**
+   * Taler uses EdDSA for purse message signing.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used by the wallet to
+ * sign a merge of a purse into an account.
+ */
+struct TALER_PurseMergePrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by the wallet to sign purse merge requests 
online.
+ */
+struct TALER_PurseMergeSignatureP
+{
+  /**
+   * Taler uses EdDSA for online signatures sessions.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * @brief Type of online public keys used by AML officers.
+ */
+struct TALER_AmlOfficerPublicKeyP
+{
+  /**
+   * Taler uses EdDSA for AML decision signing.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub;
+};
+
+
+/**
+ * @brief Type of online private keys used to identify
+ * AML officers.
+ */
+struct TALER_AmlOfficerPrivateKeyP
+{
+  /**
+   * Taler uses EdDSA for AML decision signing.
+   */
+  struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv;
+};
+
+
+/**
+ * @brief Type of signatures used by AML officers.
+ */
+struct TALER_AmlOfficerSignatureP
+{
+  /**
+   * Taler uses EdDSA for AML decision signing.
+   */
+  struct GNUNET_CRYPTO_EddsaSignature eddsa_signature;
+};
+
+
+/**
+ * Possible AML decision states.
+ */
+enum TALER_AmlDecisionState
+{
+
+  /**
+   * All AML requirements are currently satisfied.
+   */
+  TALER_AML_NORMAL = 0,
+
+  /**
+   * An AML investigation is pending.
+   */
+  TALER_AML_PENDING = 1,
+
+  /**
+   * An AML decision has concluded that the funds must be frozen.
+   */
+  TALER_AML_FROZEN = 2
+
+};
+
+
+/**
+ * Possible algorithms for confirmation code generation.
+ */
+enum TALER_MerchantConfirmationAlgorithm
+{
+
+  /**
+   * No purchase confirmation.
+   */
+  TALER_MCA_NONE = 0,
+
+  /**
+   * Purchase confirmation without payment
+   */
+  TALER_MCA_WITHOUT_PRICE = 1,
+
+  /**
+   * Purchase confirmation with payment
+   */
+  TALER_MCA_WITH_PRICE = 2
+
+};
+
+
+/**
+ * @brief Type of blinding keys for Taler.
+ * must be 32 bytes (DB)
+ */
+union TALER_DenominationBlindingKeyP
+{
+  /**
+   * Clause Schnorr Signatures have 2 blinding secrets, each containing two 
unpredictable values. (must be 32 bytes)
+   */
+  struct GNUNET_CRYPTO_CsNonce nonce;
+
+  /**
+   * Taler uses RSA for blind signatures.
+   */
+  struct GNUNET_CRYPTO_RsaBlindingKeySecret rsa_bks;
+};
+
+
+/**
+ * Commitment value for the refresh protocol.
+ * See #TALER_refresh_get_commitment().
+ */
+struct TALER_RefreshCommitmentP
+{
+  /**
+   * The commitment is a hash code.
+   */
+  struct GNUNET_HashCode session_hash;
+};
+
+
+/**
+ * Symmetric key we use to encrypt KYC attributes
+ * in our database.
+ */
+struct TALER_AttributeEncryptionKeyP
+{
+  /**
+   * The key is a hash code.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Token used for access control to the merchant's unclaimed
+ * orders.
+ */
+struct TALER_ClaimTokenP
+{
+  /**
+   * The token is a 128-bit UUID.
+   */
+  struct GNUNET_Uuid token;
+};
+
+
+/**
+ * Salt used to hash a merchant's payto:// URI to
+ * compute the "h_wire" (say for deposit requests).
+ */
+struct TALER_WireSaltP
+{
+  /**
+   * Actual 128-bit salt value.
+   */
+  uint32_t salt[4];
+};
+
+
+/**
+ * Hash used to represent an CS public key.  Does not include age
+ * restrictions and is ONLY for CS.  Used ONLY for interactions with the CS
+ * security module.
+ */
+struct TALER_CsPubHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent an RSA public key.  Does not include age
+ * restrictions and is ONLY for RSA.  Used ONLY for interactions with the RSA
+ * security module.
+ */
+struct TALER_RsaPubHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Master key material for the deriviation of
+ * private coins and blinding factors during
+ * withdraw or refresh.
+ */
+struct TALER_PlanchetMasterSecretP
+{
+
+  /**
+   * Key material.
+   */
+  uint32_t key_data[8];
+
+};
+
+
+/**
+ * Master key material for the deriviation of
+ * private coins and blinding factors.
+ */
+struct TALER_RefreshMasterSecretP
+{
+
+  /**
+   * Key material.
+   */
+  uint32_t key_data[8];
+
+};
+
+
+/**
+ * Hash used to represent a denomination public key
+ * and associated age restrictions (if any).
+ */
+struct TALER_DenominationHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the private part
+ * of a contract between merchant and consumer.
+ */
+struct TALER_PrivateContractHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the policy extension to a deposit
+ */
+struct TALER_ExtensionPolicyHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the salted hash of a
+ * merchant's bank account.
+ */
+struct TALER_MerchantWireHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the unsalted hash of a
+ * payto:// URI representing a bank account.
+ */
+struct TALER_PaytoHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_ShortHashCode hash;
+};
+
+
+/**
+ * Hash used to represent a commitment to a blinded
+ * coin, i.e. the hash of the envelope.
+ */
+struct TALER_BlindedCoinHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Hash used to represent the hash of the public
+ * key of a coin (without blinding).
+ */
+struct TALER_CoinPubHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Value that uniquely identifies a reward.
+ */
+struct TALER_RewardIdentifierP
+{
+  /**
+   * The tip identifier is a SHA-512 hash code.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Value that uniquely identifies a tip pick up operation.
+ */
+struct TALER_PickupIdentifierP
+{
+  /**
+   * The pickup identifier is a SHA-512 hash code.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * @brief Salted hash over the JSON object representing the manifests of
+ * extensions.
+ */
+struct TALER_ExtensionManifestsHashP
+{
+  /**
+   * Actual hash value.
+   */
+  struct GNUNET_HashCode hash;
+};
+
+
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSetNBOP
+{
+
+  /**
+   * The fee the exchange charges when a coin of this type is withdrawn.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO withdraw;
+
+  /**
+   * The fee the exchange charges when a coin of this type is deposited.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO deposit;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refreshed.
+   * (can be zero).
+   */
+  struct TALER_AmountNBO refresh;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refunded.
+   * (can be zero).  Note that refund fees are charged to the customer;
+   * if a refund is given, the deposit fee is also refunded.
+   */
+  struct TALER_AmountNBO refund;
+
+};
+
+
+/**
+ * Set of the fees applying for a given
+ * time-range and wire method.
+ */
+struct TALER_WireFeeSetNBOP
+{
+
+  /**
+   * The fee the exchange charges for wiring funds
+   * to a merchant.
+   */
+  struct TALER_AmountNBO wire;
+
+  /**
+   * The fee the exchange charges for closing a reserve
+   * and wiring the funds back to the origin account.
+   */
+  struct TALER_AmountNBO closing;
+
+};
+
+
+/**
+ * Set of the fees applying globally for a given
+ * time-range.
+ */
+struct TALER_GlobalFeeSetNBOP
+{
+
+  /**
+   * The fee the exchange charges for returning the history of a reserve or
+   * account.
+   */
+  struct TALER_AmountNBO history;
+
+  /**
+   * The fee the exchange charges for keeping an account or reserve open for a
+   * year.
+   */
+  struct TALER_AmountNBO account;
+
+  /**
+   * The fee the exchange charges if a purse is abandoned and this was not
+   * covered by the account limit.
+   */
+  struct TALER_AmountNBO purse;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * @brief Builds POS confirmation token to verify payment.
+ *
+ * @param pos_key encoded key for verification payment
+ * @param pos_alg algorithm to compute the payment verification
+ * @param total of the order paid
+ * @param ts is the time given
+ * @return POS token on success, NULL otherwise
+ */
+char *
+TALER_build_pos_confirmation (const char *pos_key,
+                              enum TALER_MerchantConfirmationAlgorithm pos_alg,
+                              const struct TALER_Amount *total,
+                              struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Set of the fees applying to a denomination.
+ */
+struct TALER_DenomFeeSet
+{
+
+  /**
+   * The fee the exchange charges when a coin of this type is withdrawn.
+   * (can be zero).
+   */
+  struct TALER_Amount withdraw;
+
+  /**
+   * The fee the exchange charges when a coin of this type is deposited.
+   * (can be zero).
+   */
+  struct TALER_Amount deposit;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refreshed.
+   * (can be zero).
+   */
+  struct TALER_Amount refresh;
+
+  /**
+   * The fee the exchange charges when a coin of this type is refunded.
+   * (can be zero).  Note that refund fees are charged to the customer;
+   * if a refund is given, the deposit fee is also refunded.
+   */
+  struct TALER_Amount refund;
+
+};
+
+
+/**
+ * Set of the fees applying for a given time-range and wire method.
+ */
+struct TALER_WireFeeSet
+{
+
+  /**
+   * The fee the exchange charges for wiring funds to a merchant.
+   */
+  struct TALER_Amount wire;
+
+  /**
+   * The fee the exchange charges for closing a reserve
+   * and wiring the funds back to the origin account.
+   */
+  struct TALER_Amount closing;
+
+};
+
+
+/**
+ * Set of the fees applying globally for a given
+ * time-range.
+ */
+struct TALER_GlobalFeeSet
+{
+
+  /**
+   * The fee the exchange charges for returning the
+   * history of a reserve or account.
+   */
+  struct TALER_Amount history;
+
+  /**
+   * The fee the exchange charges for keeping
+   * an account or reserve open for a year.
+   */
+  struct TALER_Amount account;
+
+  /**
+   * The fee the exchange charges if a purse
+   * is abandoned and this was not covered by
+   * the account limit.
+   */
+  struct TALER_Amount purse;
+};
+
+
+/**
+ * Convert fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo,
+                          const struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Convert fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees,
+                          const struct TALER_DenomFeeSetNBOP *nbo);
+
+
+/**
+ * Convert global fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo,
+                           const struct TALER_GlobalFeeSet *fees);
+
+
+/**
+ * Convert global fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_global_fee_set_ntoh (struct TALER_GlobalFeeSet *fees,
+                           const struct TALER_GlobalFeeSetNBOP *nbo);
+
+
+/**
+ * Compare global fee sets.
+ *
+ * @param f1 first set to compare
+ * @param f2 second set to compare
+ * @return 0 if sets are equal
+ */
+int
+TALER_global_fee_set_cmp (const struct TALER_GlobalFeeSet *f1,
+                          const struct TALER_GlobalFeeSet *f2);
+
+
+/**
+ * Convert wire fee set from host to network byte order.
+ *
+ * @param[out] nbo where to write the result
+ * @param fees fee set to convert
+ */
+void
+TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo,
+                         const struct TALER_WireFeeSet *fees);
+
+
+/**
+ * Convert wire fee set from network to host network byte order.
+ *
+ * @param[out] fees where to write the result
+ * @param nbo fee set to convert
+ */
+void
+TALER_wire_fee_set_ntoh (struct TALER_WireFeeSet *fees,
+                         const struct TALER_WireFeeSetNBOP *nbo);
+
+
+/**
+ * Compare wire fee sets.
+ *
+ * @param f1 first set to compare
+ * @param f2 second set to compare
+ * @return 0 if sets are equal
+ */
+int
+TALER_wire_fee_set_cmp (const struct TALER_WireFeeSet *f1,
+                        const struct TALER_WireFeeSet *f2);
+
+
+/**
+ * Hash @a rsa.
+ *
+ * @param rsa key to hash
+ * @param[out] h_rsa where to write the result
+ */
+void
+TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa,
+                    struct TALER_RsaPubHashP *h_rsa);
+
+/**
+ * Hash @a cs.
+ *
+ * @param cs key to hash
+ * @param[out] h_cs where to write the result
+ */
+void
+TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs,
+                   struct TALER_CsPubHashP *h_cs);
+
+
+/**
+ * Types of public keys used for denominations in Taler.
+ */
+enum TALER_DenominationCipher
+{
+
+  /**
+   * Invalid type of signature.
+   */
+  TALER_DENOMINATION_INVALID = 0,
+
+  /**
+   * RSA blind signature.
+   */
+  TALER_DENOMINATION_RSA = 1,
+
+  /**
+   * Clause Blind Schnorr signature.
+   */
+  TALER_DENOMINATION_CS = 2
+};
+
+
+/**
+ * @brief Type of (unblinded) coin signatures for Taler.
+ */
+struct TALER_DenominationSignature
+{
+
+  /**
+   * Type of the signature.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     */
+    struct GNUNET_CRYPTO_CsSignature cs_signature;
+
+    /**
+     * If we use #TALER_DENOMINATION_RSA in @a cipher.
+     */
+    struct GNUNET_CRYPTO_RsaSignature *rsa_signature;
+
+  } details;
+
+};
+
+/**
+ * The Sign Answer for Clause Blind Schnorr signature.
+ * The sign operation returns a parameter @param b and the signature
+ * scalar @param s_scalar.
+ */
+struct TALER_BlindedDenominationCsSignAnswer
+{
+  /**
+   * To make ROS problem harder, the signer chooses an unpredictable b and 
only calculates signature of c_b
+   */
+  unsigned int b;
+
+  /**
+   * The blinded s scalar calculated from c_b
+   */
+  struct GNUNET_CRYPTO_CsBlindS s_scalar;
+};
+
+/**
+ * @brief Type for *blinded* denomination signatures for Taler.
+ * Must be unblinded before it becomes valid.
+ */
+struct TALER_BlindedDenominationSignature
+{
+
+  /**
+   * Type of the signature.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     * At this point only the blinded s scalar is used.
+     * The final signature consisting of r,s is built after unblinding.
+     */
+    struct TALER_BlindedDenominationCsSignAnswer blinded_cs_answer;
+
+    /**
+     * If we use #TALER_DENOMINATION_RSA in @a cipher.
+     */
+    struct GNUNET_CRYPTO_RsaSignature *blinded_rsa_signature;
+
+  } details;
+
+};
+
+/* *************** Age Restriction *********************************** */
+
+/*
+ * @brief Type of a list of age groups, represented as bit mask.
+ *
+ * The bits set in the mask mark the edges at the beginning of a next age
+ * group.  F.e. for the age groups
+ *     0-7, 8-9, 10-11, 12-13, 14-15, 16-17, 18-21, 21-*
+ * the following bits are set:
+ *
+ *   31     24        16        8         0
+ *   |      |         |         |         |
+ *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+ *
+ * A value of 0 means that the exchange does not support the extension for
+ * age-restriction.
+ *
+ * For a non-0 age mask, the 0th bit always must be set, otherwise the age
+ * mask is considered invalid.
+ */
+struct TALER_AgeMask
+{
+  uint32_t bits;
+};
+
+/**
+ * @brief Age commitment of a coin.
+ */
+struct TALER_AgeCommitmentHash
+{
+  /**
+   * The commitment is a SHA-256 hash code.
+   */
+  struct GNUNET_ShortHashCode shash;
+};
+
+/**
+ * @brief Signature of an age with the private key for the corresponding age 
group of an age commitment.
+ */
+struct TALER_AgeAttestation
+{
+#ifdef AGE_RESTRICTION_WITH_ECDSA
+  struct GNUNET_CRYPTO_EcdsaSignature signature;
+#else
+  struct GNUNET_CRYPTO_Edx25519Signature signature;
+#endif
+};
+
+#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \
+                                                  GNUNET_is_zero (ph))
+
+/**
+ * @brief Type of public signing keys for verifying blindly signed coins.
+ */
+struct TALER_DenominationPublicKey
+{
+
+  /**
+   * Type of the public key.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Age restriction mask used for the key.
+   */
+  struct TALER_AgeMask age_mask;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     */
+    struct GNUNET_CRYPTO_CsPublicKey cs_public_key;
+
+    /**
+     * If we use #TALER_DENOMINATION_RSA in @a cipher.
+     */
+    struct GNUNET_CRYPTO_RsaPublicKey *rsa_public_key;
+
+  } details;
+};
+
+
+/**
+ * @brief Type of private signing keys for blind signing of coins.
+ */
+struct TALER_DenominationPrivateKey
+{
+
+  /**
+   * Type of the public key.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     */
+    struct GNUNET_CRYPTO_CsPrivateKey cs_private_key;
+
+    /**
+     * If we use #TALER_DENOMINATION_RSA in @a cipher.
+     */
+    struct GNUNET_CRYPTO_RsaPrivateKey *rsa_private_key;
+
+  } details;
+};
+
+/**
+ * @brief RSA Parameters to create blinded signature
+ *
+ */
+struct TALER_BlindedRsaPlanchet
+{
+  /**
+   * Blinded message to be signed
+   * Note: is malloc()'ed!
+   */
+  void *blinded_msg;
+
+  /**
+   * Size of the @e blinded_msg to be signed.
+   */
+  size_t blinded_msg_size;
+};
+
+
+/**
+ * Withdraw nonce for CS denominations
+ */
+struct TALER_CsNonce
+{
+  /**
+   * 32 bit nonce to include in withdrawals when using CS.
+   */
+  struct GNUNET_CRYPTO_CsNonce nonce;
+};
+
+
+/**
+ * @brief CS Parameters to create blinded signature
+ */
+struct TALER_BlindedCsPlanchet
+{
+  /**
+   * The Clause Schnorr c_0 and c_1 containing the blinded message
+   */
+  struct GNUNET_CRYPTO_CsC c[2];
+
+  /**
+   * Public nonce.
+   */
+  struct TALER_CsNonce nonce;
+};
+
+
+/**
+ * @brief Type including Parameters to create blinded signature
+ */
+struct TALER_BlindedPlanchet
+{
+  /**
+   * Type of the sign blinded message
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     */
+    struct TALER_BlindedCsPlanchet cs_blinded_planchet;
+
+    /**
+     * If we use #TALER_DENOMINATION_RSA in @a cipher.
+     */
+    struct TALER_BlindedRsaPlanchet rsa_blinded_planchet;
+
+  } details;
+};
+
+
+/**
+ * Pair of Public R values for Cs denominations
+ */
+struct TALER_DenominationCSPublicRPairP
+{
+  struct GNUNET_CRYPTO_CsRPublic r_pub[2];
+};
+
+
+/**
+ * Secret r for Cs denominations
+ */
+struct TALER_DenominationCSPrivateRPairP
+{
+  struct GNUNET_CRYPTO_CsRSecret r[2];
+};
+
+
+/**
+ * @brief Public information about a coin (including the public key
+ * of the coin, the denomination key and the signature with
+ * the denomination key).
+ */
+struct TALER_CoinPublicInfo
+{
+  /**
+   * The coin's public key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Hash of the public key representing the denomination of the coin that is
+   * being deposited.
+   */
+  struct TALER_DenominationHashP denom_pub_hash;
+
+  /**
+   * Hash of the age commitment.  If no age commitment was provided, it must be
+   * set to all zeroes.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * True, if age commitment is not applicable.
+   */
+  bool no_age_commitment;
+
+  /**
+   * (Unblinded) signature over @e coin_pub with @e denom_pub,
+   * which demonstrates that the coin is valid.
+   */
+  struct TALER_DenominationSignature denom_sig;
+};
+
+
+/**
+ * Details for one of the /deposit operations that the
+ * exchange combined into a single wire transfer.
+ */
+struct TALER_TrackTransferDetails
+{
+  /**
+   * Hash of the proposal data.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Which coin was deposited?
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Value of the deposit (including fee).
+   */
+  struct TALER_Amount coin_value;
+
+  /**
+   * Fee charged by the exchange for the deposit.
+   */
+  struct TALER_Amount coin_fee;
+
+};
+
+
+/**
+ * @brief Type of algorithm specific Values for withdrawal
+ */
+struct TALER_ExchangeWithdrawValues
+{
+
+  /**
+   * Type of the signature.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Details, depending on @e cipher.
+   */
+  union
+  {
+    /**
+     * If we use #TALER_DENOMINATION_CS in @a cipher.
+     */
+    struct TALER_DenominationCSPublicRPairP cs_values;
+
+  } details;
+
+};
+
+
+/**
+ * Free internals of @a denom_pub, but not @a denom_pub itself.
+ *
+ * @param[in] denom_pub key to free
+ */
+void
+TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Create private key for a Taler coin.
+ * @param ps planchet secret to derive coin priv key
+ * @param alg_values includes algorithm specific values
+ * @param[out] coin_priv private key to initialize
+ */
+void
+TALER_planchet_setup_coin_priv (
+  const struct TALER_PlanchetMasterSecretP *ps,
+  const struct TALER_ExchangeWithdrawValues *alg_values,
+  struct TALER_CoinSpendPrivateKeyP *coin_priv);
+
+
+/**
+ * @brief Method to derive withdraw /csr nonce
+ *
+ * @param ps planchet secrets of the coin
+ * @param[out] nonce withdraw nonce included in the request to generate R_0 
and R_1
+ */
+void
+TALER_cs_withdraw_nonce_derive (
+  const struct TALER_PlanchetMasterSecretP *ps,
+  struct TALER_CsNonce *nonce);
+
+
+/**
+ * @brief Method to derive /csr nonce
+ * to be used during refresh/melt operation.
+ *
+ * @param rms secret input for the refresh operation
+ * @param idx index of the fresh coin
+ * @param[out] nonce set to nonce included in the request to generate R_0 and 
R_1
+ */
+void
+TALER_cs_refresh_nonce_derive (
+  const struct TALER_RefreshMasterSecretP *rms,
+  uint32_t idx,
+  struct TALER_CsNonce *nonce);
+
+
+/**
+ * Initialize denomination public-private key pair.
+ *
+ * For #TALER_DENOMINATION_RSA, an additional "unsigned int"
+ * argument with the number of bits for 'n' (e.g. 2048) must
+ * be passed.
+ *
+ * @param[out] denom_priv where to write the private key
+ * @param[out] denom_pub where to write the public key
+ * @param cipher which type of cipher to use
+ * @param ... RSA key size (eg. 2048/3072/4096)
+ * @return #GNUNET_OK on success, #GNUNET_NO if parameters were invalid
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_priv_create (struct TALER_DenominationPrivateKey *denom_priv,
+                         struct TALER_DenominationPublicKey *denom_pub,
+                         enum TALER_DenominationCipher cipher,
+                         ...);
+
+
+/**
+ * Free internals of @a denom_priv, but not @a denom_priv itself.
+ *
+ * @param[in] denom_priv key to free
+ */
+void
+TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv);
+
+
+/**
+ * Free internals of @a denom_sig, but not @a denom_sig itself.
+ *
+ * @param[in] denom_sig signature to free
+ */
+void
+TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig);
+
+
+/**
+ * Blind coin for blind signing with @a dk using blinding secret @a coin_bks.
+ *
+ * NOTE: As a particular oddity, the @a blinded_planchet is only partially
+ * initialized by this function in the case of CS-denominations. Here, the
+ * 'nonce' must be initialized separately!
+ *
+ * @param dk denomination public key to blind for
+ * @param coin_bks blinding secret to use
+ * @param age_commitment_hash hash of the age commitment to be used for the 
coin. NULL if no commitment is made.
+ * @param coin_pub public key of the coin to blind
+ * @param alg_values algorithm specific values to blind the planchet
+ * @param[out] c_hash resulting hashed coin
+ * @param[out] blinded_planchet planchet data to initialize
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_blind (const struct TALER_DenominationPublicKey *dk,
+                   const union TALER_DenominationBlindingKeyP *coin_bks,
+                   const struct TALER_AgeCommitmentHash *age_commitment_hash,
+                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                   const struct TALER_ExchangeWithdrawValues *alg_values,
+                   struct TALER_CoinPubHashP *c_hash,
+                   struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Create blinded signature.
+ *
+ * @param[out] denom_sig where to write the signature
+ * @param denom_priv private key to use for signing
+ * @param for_melt true to use the HKDF for melt
+ * @param blinded_planchet the planchet already blinded
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_sign_blinded (struct TALER_BlindedDenominationSignature *denom_sig,
+                          const struct TALER_DenominationPrivateKey 
*denom_priv,
+                          bool for_melt,
+                          const struct TALER_BlindedPlanchet 
*blinded_planchet);
+
+
+/**
+ * Unblind blinded signature.
+ *
+ * @param[out] denom_sig where to write the unblinded signature
+ * @param bdenom_sig the blinded signature
+ * @param bks blinding secret to use
+ * @param c_hash hash of the coin's public key for verification of the 
signature
+ * @param alg_values algorithm specific values
+ * @param denom_pub public key used for signing
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_sig_unblind (
+  struct TALER_DenominationSignature *denom_sig,
+  const struct TALER_BlindedDenominationSignature *bdenom_sig,
+  const union TALER_DenominationBlindingKeyP *bks,
+  const struct TALER_CoinPubHashP *c_hash,
+  const struct TALER_ExchangeWithdrawValues *alg_values,
+  const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Free internals of @a denom_sig, but not @a denom_sig itself.
+ *
+ * @param[in] denom_sig signature to free
+ */
+void
+TALER_blinded_denom_sig_free (
+  struct TALER_BlindedDenominationSignature *denom_sig);
+
+
+/**
+ * Compute the hash of the given @a denom_pub.
+ *
+ * @param denom_pub public key to hash
+ * @param[out] denom_hash resulting hash value
+ */
+void
+TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub,
+                      struct TALER_DenominationHashP *denom_hash);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_denom_pub_deep_copy (struct TALER_DenominationPublicKey *denom_dst,
+                           const struct TALER_DenominationPublicKey 
*denom_src);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_denom_sig_deep_copy (struct TALER_DenominationSignature *denom_dst,
+                           const struct TALER_DenominationSignature 
*denom_src);
+
+
+/**
+ * Make a (deep) copy of the given @a denom_src to
+ * @a denom_dst.
+ *
+ * @param[out] denom_dst target to copy to
+ * @param denom_src public key to copy
+ */
+void
+TALER_blinded_denom_sig_deep_copy (
+  struct TALER_BlindedDenominationSignature *denom_dst,
+  const struct TALER_BlindedDenominationSignature *denom_src);
+
+
+/**
+ * Compare two denomination public keys.
+ *
+ * @param denom1 first key
+ * @param denom2 second key
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_denom_pub_cmp (const struct TALER_DenominationPublicKey *denom1,
+                     const struct TALER_DenominationPublicKey *denom2);
+
+
+/**
+ * Compare two denomination signatures.
+ *
+ * @param sig1 first signature
+ * @param sig2 second signature
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_denom_sig_cmp (const struct TALER_DenominationSignature *sig1,
+                     const struct TALER_DenominationSignature *sig2);
+
+
+/**
+ * Compare two blinded denomination signatures.
+ *
+ * @param sig1 first signature
+ * @param sig2 second signature
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_blinded_denom_sig_cmp (
+  const struct TALER_BlindedDenominationSignature *sig1,
+  const struct TALER_BlindedDenominationSignature *sig2);
+
+
+/**
+ * Compare two blinded planchets.
+ *
+ * @param bp1 first blinded planchet
+ * @param bp2 second blinded planchet
+ * @return 0 if the keys are equal, otherwise -1 or 1
+ */
+int
+TALER_blinded_planchet_cmp (
+  const struct TALER_BlindedPlanchet *bp1,
+  const struct TALER_BlindedPlanchet *bp2);
+
+
+/**
+ * Obtain denomination public key from a denomination private key.
+ *
+ * @param denom_priv private key to convert
+ * @param age_mask age mask to be applied
+ * @param[out] denom_pub where to return the public key
+ */
+void
+TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv,
+                         const struct TALER_AgeMask age_mask,
+                         struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Verify signature made with a denomination public key
+ * over a coin.
+ *
+ * @param denom_pub public denomination key
+ * @param denom_sig signature made with the private key
+ * @param c_hash hash over the coin
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_pub_verify (const struct TALER_DenominationPublicKey *denom_pub,
+                        const struct TALER_DenominationSignature *denom_sig,
+                        const struct TALER_CoinPubHashP *c_hash);
+
+
+/**
+ * Encrypts KYC attributes for storage in the database.
+ *
+ * @param key encryption key to use
+ * @param attr set of attributes to encrypt
+ * @param[out] enc_attr encrypted attribute data
+ * @param[out] enc_attr_size number of bytes in @a enc_attr
+ */
+void
+TALER_CRYPTO_kyc_attributes_encrypt (
+  const struct TALER_AttributeEncryptionKeyP *key,
+  const json_t *attr,
+  void **enc_attr,
+  size_t *enc_attr_size);
+
+
+/**
+ * Encrypts KYC attributes for storage in the database.
+ *
+ * @param key encryption key to use
+ * @param enc_attr encrypted attribute data
+ * @param enc_attr_size number of bytes in @a enc_attr
+ * @return set of decrypted attributes, NULL on failure
+ */
+json_t *
+TALER_CRYPTO_kyc_attributes_decrypt (
+  const struct TALER_AttributeEncryptionKeyP *key,
+  const void *enc_attr,
+  size_t enc_attr_size);
+
+
+/**
+ * Takes a set of KYC attributes and extracts key
+ * data that we use to detect similar / duplicate
+ * entries in the database.
+ *
+ * @param attr set of KYC attributes
+ * @param[out] kyc_prox set to the proximity hash
+ */
+void
+TALER_CRYPTO_attributes_to_kyc_prox (
+  const json_t *attr,
+  struct GNUNET_ShortHashCode *kyc_prox);
+
+
+/**
+ * Check if a coin is valid; that is, whether the denomination key exists,
+ * is not expired, and the signature is correct.
+ *
+ * @param coin_public_info the coin public info to check for validity
+ * @param denom_pub denomination key, must match @a coin_public_info's 
`denom_pub_hash`
+ * @return #GNUNET_YES if the coin is valid,
+ *         #GNUNET_NO if it is invalid
+ *         #GNUNET_SYSERR if an internal error occurred
+ */
+enum GNUNET_GenericReturnValue
+TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info,
+                       const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Compute the hash of a blinded coin.
+ *
+ * @param blinded_planchet blinded planchet
+ * @param denom_hash hash of the denomination publick key
+ * @param[out] bch where to write the hash
+ * @return #GNUNET_OK when successful, #GNUNET_SYSERR if an internal error 
occurred
+ */
+enum GNUNET_GenericReturnValue
+TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet,
+                    const struct TALER_DenominationHashP *denom_hash,
+                    struct TALER_BlindedCoinHashP *bch);
+
+
+/**
+ * Compute the hash of a coin.
+ *
+ * @param coin_pub public key of the coin
+ * @param age_commitment_hash hash of the age commitment vector. NULL, if no 
age commitment was set
+ * @param[out] coin_h where to write the hash
+ */
+void
+TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                     const struct TALER_AgeCommitmentHash *age_commitment_hash,
+                     struct TALER_CoinPubHashP *coin_h);
+
+
+/**
+ * Compute the hash of a payto URI.
+ *
+ * @param payto URI to hash
+ * @param[out] h_payto where to write the hash
+ */
+void
+TALER_payto_hash (const char *payto,
+                  struct TALER_PaytoHashP *h_payto);
+
+
+/**
+ * Details about a planchet that the customer wants to obtain
+ * a withdrawal authorization.  This is the information that
+ * will need to be sent to the exchange to obtain the blind
+ * signature required to turn a planchet into a coin.
+ */
+struct TALER_PlanchetDetail
+{
+  /**
+   * Hash of the denomination public key.
+   */
+  struct TALER_DenominationHashP denom_pub_hash;
+
+  /**
+   * The blinded planchet
+   */
+  struct TALER_BlindedPlanchet blinded_planchet;
+};
+
+
+/**
+ * Information about a (fresh) coin, returned from the API when we
+ * finished creating a coin.  Note that @e sig needs to be freed
+ * using the appropriate code.
+ */
+struct TALER_FreshCoin
+{
+
+  /**
+   * The exchange's signature over the coin's public key.
+   */
+  struct TALER_DenominationSignature sig;
+
+  /**
+   * The coin's private key.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Optional hash of an age commitment bound to this coin, maybe NULL.
+   */
+  const struct TALER_AgeCommitmentHash *h_age_commitment;
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Secret used to decrypt the key to decrypt link secrets.
+ */
+struct TALER_TransferSecretP
+{
+  /**
+   * Secret used to derive private inputs for refreshed coins.
+   * Must be (currently) a hash as this is what
+   * GNUNET_CRYPTO_ecc_ecdh() returns to us.
+   */
+  struct GNUNET_HashCode key;
+};
+
+
+/**
+ * Length of the raw value in the Taler wire transfer identifier
+ * (in binary representation).
+ */
+#define TALER_BANK_TRANSFER_IDENTIFIER_LEN 32
+
+/**
+ * #TALER_BANK_TRANSFER_IDENTIFIER_LEN as a string.
+ */
+#define TALER_BANK_TRANSFER_IDENTIFIER_LEN_STR "32"
+
+
+/**
+ * Raw value of a wire transfer subjects, without the checksum.
+ */
+struct TALER_WireTransferIdentifierRawP
+{
+
+  /**
+   * Raw value.  Note that typical payment systems (SEPA, ACH) support
+   * at least two lines of 27 ASCII characters to encode a transaction
+   * subject or "details", for a total of 54 characters.  (The payment
+   * system protocols often support more lines, but the forms presented
+   * to customers are usually limited to 54 characters.)
+   *
+   * With a Base32-encoding of 5 bit per character, this gives us 270
+   * bits or (rounded down) 33 bytes.  So we use the first 32 bytes to
+   * encode the actual value (i.e. a 256-bit / 32-byte public key or
+   * a hash code), and the last byte for a minimalistic checksum.
+   */
+  uint8_t raw[TALER_BANK_TRANSFER_IDENTIFIER_LEN];
+};
+
+
+/**
+ * Raw value of a wire transfer subject for a wad.
+ */
+struct TALER_WadIdentifierP
+{
+
+  /**
+   * Wad identifier, in binary encoding.
+   */
+  uint8_t raw[24];
+};
+
+
+/**
+ * Binary information encoded in Crockford's Base32 in wire transfer
+ * subjects of transfers from Taler to a merchant.  The actual value
+ * is chosen by the exchange and has no particular semantics, other than
+ * being unique so that the exchange can lookup details about the wire
+ * transfer when needed.
+ */
+struct TALER_WireTransferIdentifierP
+{
+
+  /**
+   * Raw value.
+   */
+  struct TALER_WireTransferIdentifierRawP raw;
+
+  /**
+   * Checksum using CRC8 over the @e raw data.
+   */
+  uint8_t crc8;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+/**
+ * Setup information for a fresh coin, deriving the coin planchet secrets from
+ * which we will later derive the private key and the blinding factor.  The
+ * planchet secrets derivation is based on the @a secret_seed with a KDF
+ * salted by the @a coin_num_salt.
+ *
+ * @param secret_seed seed to use for KDF to derive coin keys
+ * @param coin_num_salt number of the coin to include in KDF
+ * @param[out] ps value to initialize
+ */
+void
+TALER_transfer_secret_to_planchet_secret (
+  const struct TALER_TransferSecretP *secret_seed,
+  uint32_t coin_num_salt,
+  struct TALER_PlanchetMasterSecretP *ps);
+
+
+/**
+ * Derive the @a coin_num transfer private key @a tpriv from a refresh from
+ * the @a rms seed and the @a old_coin_pub of the refresh operation.  The
+ * transfer private key derivation is based on the @a ps with a KDF salted by
+ * the @a coin_num.
+ *
+ * @param rms seed to use for KDF to derive transfer keys
+ * @param old_coin_priv private key of the old coin
+ * @param cnc_num cut and choose number to include in KDF
+ * @param[out] tpriv value to initialize
+ */
+void
+TALER_planchet_secret_to_transfer_priv (
+  const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+  uint32_t cnc_num,
+  struct TALER_TransferPrivateKeyP *tpriv);
+
+
+/**
+ * Setup secret seed information for fresh coins to be
+ * withdrawn.
+ *
+ * @param[out] ps value to initialize
+ */
+void
+TALER_planchet_master_setup_random (
+  struct TALER_PlanchetMasterSecretP *ps);
+
+
+/**
+ * Setup secret seed for fresh coins to be refreshed.
+ *
+ * @param[out] rms value to initialize
+ */
+void
+TALER_refresh_master_setup_random (
+  struct TALER_RefreshMasterSecretP *rms);
+
+
+/**
+ * Create a blinding secret @a bks given the client's @a ps and the alg_values
+ * from the exchange.
+ *
+ * @param ps secret to derive blindings from
+ * @param alg_values withdraw values containing cipher and additional CS values
+ * @param[out] bks blinding secrets
+ */
+void
+TALER_planchet_blinding_secret_create (
+  const struct TALER_PlanchetMasterSecretP *ps,
+  const struct TALER_ExchangeWithdrawValues *alg_values,
+  union TALER_DenominationBlindingKeyP *bks);
+
+
+/**
+ * Prepare a planchet for withdrawal.  Creates and blinds a coin.
+ *
+ * @param dk denomination key for the coin to be created
+ * @param alg_values algorithm specific values
+ * @param bks blinding secrets
+ * @param coin_priv coin private key
+ * @param ach hash of age commitment to bind to this coin, maybe NULL
+ * @param[out] c_hash set to the hash of the public key of the coin (needed 
later)
+ * @param[out] pd set to the planchet detail for TALER_MERCHANT_tip_pickup() 
and
+ *               other withdraw operations, `pd->blinded_planchet.cipher` will 
be set
+ *               to cipher from @a dk
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk,
+                        const struct TALER_ExchangeWithdrawValues *alg_values,
+                        const union TALER_DenominationBlindingKeyP *bks,
+                        const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+                        const struct TALER_AgeCommitmentHash *ach,
+                        struct TALER_CoinPubHashP *c_hash,
+                        struct TALER_PlanchetDetail *pd);
+
+
+/**
+ * Frees blinded message inside blinded planchet depending on 
`blinded_planchet->cipher`.
+ * Does not free the @a blinded_planchet itself!
+ *
+ * @param[in] blinded_planchet blinded planchet
+ */
+void
+TALER_blinded_planchet_free (struct TALER_BlindedPlanchet *blinded_planchet);
+
+
+/**
+ * Frees blinded message inside planchet detail @a pd.
+ *
+ * @param[in] pd planchet detail to free
+ */
+void
+TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd);
+
+
+/**
+ * Obtain a coin from the planchet's secrets and the blind signature
+ * of the exchange.
+ *
+ * @param dk denomination key, must match what was given to 
#TALER_planchet_prepare()
+ * @param blind_sig blind signature from the exchange
+ * @param bks blinding key secret
+ * @param coin_priv private key of the coin
+ * @param ach hash of age commitment that is bound to this coin, maybe NULL
+ * @param c_hash hash of the coin's public key for verification of the 
signature
+ * @param alg_values values obtained from the exchange for the withdrawal
+ * @param[out] coin set to the details of the fresh coin
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_planchet_to_coin (
+  const struct TALER_DenominationPublicKey *dk,
+  const struct TALER_BlindedDenominationSignature *blind_sig,
+  const union TALER_DenominationBlindingKeyP *bks,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_AgeCommitmentHash *ach,
+  const struct TALER_CoinPubHashP *c_hash,
+  const struct TALER_ExchangeWithdrawValues *alg_values,
+  struct TALER_FreshCoin *coin);
+
+
+/**
+ * Add the hash of the @a bp (in some canonicalized form)
+ * to the @a hash_context.
+ *
+ * @param bp blinded planchet to hash
+ * @param[in,out] hash_context hash context to use
+ */
+void
+TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp,
+                              struct GNUNET_HashContext *hash_context);
+
+
+/**
+ * Given the coin and the transfer private keys, compute the
+ * transfer secret.  (Technically, we only need one of the two
+ * private keys, but the caller currently trivially only has
+ * the two private keys, so we derive one of the public keys
+ * internally to this function.)
+ *
+ * @param coin_priv coin key
+ * @param trans_priv transfer private key
+ * @param[out] ts computed transfer secret
+ */
+void
+TALER_link_derive_transfer_secret (
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_TransferPrivateKeyP *trans_priv,
+  struct TALER_TransferSecretP *ts);
+
+
+/**
+ * Decrypt the shared @a secret from the information in the
+ * @a trans_priv and @a coin_pub.
+ *
+ * @param trans_priv transfer private key
+ * @param coin_pub coin public key
+ * @param[out] transfer_secret set to the shared secret
+ */
+void
+TALER_link_reveal_transfer_secret (
+  const struct TALER_TransferPrivateKeyP *trans_priv,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_TransferSecretP *transfer_secret);
+
+
+/**
+ * Decrypt the shared @a secret from the information in the
+ * @a trans_priv and @a coin_pub.
+ *
+ * @param trans_pub transfer private key
+ * @param coin_priv coin public key
+ * @param[out] transfer_secret set to the shared secret
+ */
+void
+TALER_link_recover_transfer_secret (
+  const struct TALER_TransferPublicKeyP *trans_pub,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_TransferSecretP *transfer_secret);
+
+
+/**
+ * Information about a coin to be created during a refresh operation.
+ */
+struct TALER_RefreshCoinData
+{
+
+  /**
+   * The denomination's public key.
+   */
+  const struct TALER_DenominationPublicKey *dk;
+
+  /**
+   * The blinded planchet (details depend on cipher).
+   */
+  struct TALER_BlindedPlanchet blinded_planchet;
+
+};
+
+
+/**
+ * One of the #TALER_CNC_KAPPA commitments.
+ */
+struct TALER_RefreshCommitmentEntry
+{
+  /**
+   * Transfer public key of this commitment.
+   */
+  struct TALER_TransferPublicKeyP transfer_pub;
+
+  /**
+   * Array of @e num_new_coins new coins to be created.
+   */
+  struct TALER_RefreshCoinData *new_coins;
+};
+
+
+/**
+ * Compute the commitment for a /refresh/melt operation from
+ * the respective public inputs.
+ *
+ * @param[out] rc set to the value the wallet must commit to
+ * @param kappa number of transfer public keys involved (must be 
#TALER_CNC_KAPPA)
+ * @param rms refresh master secret to include, can be NULL!
+ * @param num_new_coins number of new coins to be created
+ * @param rcs array of @a kappa commitments
+ * @param coin_pub public key of the coin to be melted
+ * @param amount_with_fee amount to be melted, including fee
+ */
+void
+TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc,
+                              uint32_t kappa,
+                              const struct TALER_RefreshMasterSecretP *rms,
+                              uint32_t num_new_coins,
+                              const struct TALER_RefreshCommitmentEntry *rcs,
+                              const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                              const struct TALER_Amount *amount_with_fee);
+
+
+/**
+ * Encrypt contract for transmission to a party that will
+ * merge it into a reserve.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param merge_priv merge capability to include
+ * @param contract_terms contract terms to encrypt
+ * @param[out] econtract set to encrypted contract
+ * @param[out] econtract_size set to number of bytes in @a econtract
+ */
+void
+TALER_CRYPTO_contract_encrypt_for_merge (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  const json_t *contract_terms,
+  void **econtract,
+  size_t *econtract_size);
+
+
+/**
+ * Decrypt contract for the party that will
+ * merge it into a reserve.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param econtract encrypted contract
+ * @param econtract_size  number of bytes in @a econtract
+ * @param[out] merge_priv set to merge capability
+ * @return decrypted contract terms, or NULL on failure
+ */
+json_t *
+TALER_CRYPTO_contract_decrypt_for_merge (
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const void *econtract,
+  size_t econtract_size,
+  struct TALER_PurseMergePrivateKeyP *merge_priv);
+
+
+/**
+ * Encrypt contract for transmission to a party that will
+ * pay for it.
+ *
+ * @param purse_pub public key of the purse
+ * @param contract_priv private key of the contract
+ * @param contract_terms contract terms to encrypt
+ * @param[out] econtract set to encrypted contract
+ * @param[out] econtract_size set to number of bytes in @a econtract
+ */
+void
+TALER_CRYPTO_contract_encrypt_for_deposit (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const json_t *contract_terms,
+  void **econtract,
+  size_t *econtract_size);
+
+
+/**
+ * Decrypt contract for the party that will pay for it.
+ *
+ * @param contract_priv private key of the contract
+ * @param econtract encrypted contract
+ * @param econtract_size  number of bytes in @a econtract
+ * @return decrypted contract terms, or NULL on failure
+ */
+json_t *
+TALER_CRYPTO_contract_decrypt_for_deposit (
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const void *econtract,
+  size_t econtract_size);
+
+
+/* **************** AML officer signatures **************** */
+
+/**
+ * Sign AML query. Simple authentication, doesn't actually
+ * sign anything.
+ *
+ * @param officer_priv private key of AML officer
+ * @param[out] officer_sig where to write the signature
+ */
+void
+TALER_officer_aml_query_sign (
+  const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+  struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Verify AML query authorization.
+ *
+ * @param officer_pub public key of AML officer
+ * @param officer_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_query_verify (
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Sign AML decision.
+ *
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ *                      should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ *                      decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements additional KYC requirements to
+ *           impose, can be NULL
+ * @param officer_priv private key of AML officer
+ * @param[out] officer_sig where to write the signature
+ */
+void
+TALER_officer_aml_decision_sign (
+  const char *justification,
+  struct GNUNET_TIME_Timestamp decision_time,
+  const struct TALER_Amount *new_threshold,
+  const struct TALER_PaytoHashP *h_payto,
+  enum TALER_AmlDecisionState new_state,
+  const json_t *kyc_requirements,
+  const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+  struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/**
+ * Verify AML decision.
+ *
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ *                      should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ *                      decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements additional KYC requirements to
+ *           impose, can be NULL
+ * @param officer_pub public key of AML officer
+ * @param officer_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_officer_aml_decision_verify (
+  const char *justification,
+  struct GNUNET_TIME_Timestamp decision_time,
+  const struct TALER_Amount *new_threshold,
+  const struct TALER_PaytoHashP *h_payto,
+  enum TALER_AmlDecisionState new_state,
+  const json_t *kyc_requirements,
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const struct TALER_AmlOfficerSignatureP *officer_sig);
+
+
+/* **************** Helper-based RSA operations **************** */
+
+/**
+ * Handle for talking to an Denomination key signing helper.
+ */
+struct TALER_CRYPTO_RsaDenominationHelper;
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_rsa hash of the RSA @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_RsaDenominationKeyStatusCallback)(
+  void *cls,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_RsaPubHashP *h_rsa,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an denomination key helper.
+ *
+ * @param cfg configuration to use
+ * @param dkc function to call with key information
+ * @param dkc_cls closure for @a dkc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_RsaDenominationHelper *
+TALER_CRYPTO_helper_rsa_connect (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc,
+  void *dkc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request.  This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * denomination keys.
+ *
+ * @param dh helper process connection
+ */
+void
+TALER_CRYPTO_helper_rsa_poll (struct TALER_CRYPTO_RsaDenominationHelper *dh);
+
+
+/**
+ * Information needed for an RSA signature request.
+ */
+struct TALER_CRYPTO_RsaSignRequest
+{
+  /**
+   * Hash of the RSA public key.
+   */
+  const struct TALER_RsaPubHashP *h_rsa;
+
+  /**
+   * Message to be (blindly) signed.
+   */
+  const void *msg;
+
+  /**
+   * Number of bytes in @e msg.
+   */
+  size_t msg_size;
+};
+
+
+/**
+ * Request helper @a dh to sign message in @a rsr using the public key
+ * corresponding to the key in @a rsr.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param rsr details about the requested signature
+ * @param[out] bs set to the blind signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_sign (
+  struct TALER_CRYPTO_RsaDenominationHelper *dh,
+  const struct TALER_CRYPTO_RsaSignRequest *rsr,
+  struct TALER_BlindedDenominationSignature *bs);
+
+
+/**
+ * Request helper @a dh to batch sign messages in @a rsrs using the public key
+ * corresponding to the keys in @a rsrs.
+ *
+ * This operation will block until all the signatures have been obtained.  
Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * Note that in case of errors, the @a bss array may still have been partially
+ * filled with signatures, which in this case must be freed by the caller
+ * (this is in contrast to the #TALER_CRYPTO_helper_rsa_sign() API which never
+ * returns any signatures if there was an error).
+ *
+ * @param dh helper process connection
+ * @param rsrs array with details about the requested signatures
+ * @param rsrs_length length of the @a rsrs array
+ * @param[out] bss array set to the blind signatures, must be of length @a 
rsrs_length!
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_rsa_batch_sign (
+  struct TALER_CRYPTO_RsaDenominationHelper *dh,
+  const struct TALER_CRYPTO_RsaSignRequest *rsrs,
+  unsigned int rsrs_length,
+  struct TALER_BlindedDenominationSignature *bss);
+
+
+/**
+ * Ask the helper to revoke the public key associated with @a h_denom_pub.
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted.  Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the denomination key status
+ * callback.
+ *
+ * @param dh helper to process connection
+ * @param h_rsa hash of the RSA public key to revoke
+ */
+void
+TALER_CRYPTO_helper_rsa_revoke (
+  struct TALER_CRYPTO_RsaDenominationHelper *dh,
+  const struct TALER_RsaPubHashP *h_rsa);
+
+
+/**
+ * Close connection to @a dh.
+ *
+ * @param[in] dh connection to close
+ */
+void
+TALER_CRYPTO_helper_rsa_disconnect (
+  struct TALER_CRYPTO_RsaDenominationHelper *dh);
+
+
+/* **************** Helper-based CS operations **************** */
+
+/**
+ * Handle for talking to an Denomination key signing helper.
+ */
+struct TALER_CRYPTO_CsDenominationHelper;
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param section_name name of the denomination type in the configuration;
+ *                 NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param h_cs hash of the CS @a denom_pub that is available (or was purged)
+ * @param denom_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_CsDenominationKeyStatusCallback)(
+  void *cls,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_CsPubHashP *h_cs,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an denomination key helper.
+ *
+ * @param cfg configuration to use
+ * @param dkc function to call with key information
+ * @param dkc_cls closure for @a dkc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_CsDenominationHelper *
+TALER_CRYPTO_helper_cs_connect (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  TALER_CRYPTO_CsDenominationKeyStatusCallback dkc,
+  void *dkc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request.  This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * denomination keys.
+ *
+ * @param dh helper process connection
+ */
+void
+TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh);
+
+
+/**
+ * Information about what we should sign over.
+ */
+struct TALER_CRYPTO_CsSignRequest
+{
+  /**
+   * Hash of the CS public key to use to sign.
+   */
+  const struct TALER_CsPubHashP *h_cs;
+
+  /**
+   * Blinded planchet containing c and the nonce.
+   */
+  const struct TALER_BlindedCsPlanchet *blinded_planchet;
+};
+
+
+/**
+ * Request helper @a dh to sign @a req.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param req information about the key to sign with and the value to sign
+ * @param for_melt true if for melt operation
+ * @param[out] bs set to the blind signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_sign (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CRYPTO_CsSignRequest *req,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bs);
+
+
+/**
+ * Request helper @a dh to sign batch of @a reqs requests.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper process connection
+ * @param reqs information about the keys to sign with and the values to sign
+ * @param reqs_length length of the @a reqs array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bss array set to the blind signatures, must be of length @a 
reqs_length!
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_batch_sign (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CRYPTO_CsSignRequest *reqs,
+  unsigned int reqs_length,
+  bool for_melt,
+  struct TALER_BlindedDenominationSignature *bss);
+
+
+/**
+ * Ask the helper to revoke the public key associated with @a h_cs.
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted.  Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the denomination key status
+ * callback.
+ *
+ * @param dh helper to process connection
+ * @param h_cs hash of the CS public key to revoke
+ */
+void
+TALER_CRYPTO_helper_cs_revoke (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CsPubHashP *h_cs);
+
+
+/**
+ * Information about what we should derive for.
+ */
+struct TALER_CRYPTO_CsDeriveRequest
+{
+  /**
+   * Hash of the CS public key to use to sign.
+   */
+  const struct TALER_CsPubHashP *h_cs;
+
+  /**
+   * Nonce to use.
+   */
+  const struct TALER_CsNonce *nonce;
+};
+
+
+/**
+ * Ask the helper to derive R using the information
+ * from @a cdr.
+ *
+ * This operation will block until the R has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper to process connection
+ * @param cdr derivation input data
+ * @param for_melt true if this is for a melt operation
+ * @param[out] crp set to the pair of R values
+ * @return set to the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_derive (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CRYPTO_CsDeriveRequest *cdr,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *crp);
+
+
+/**
+ * Ask the helper to derive R using the information from @a cdrs.
+ *
+ * This operation will block until the R has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param dh helper to process connection
+ * @param cdrs array with derivation input data
+ * @param cdrs_length length of the @a cdrs array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] crps array set to the pair of R values, must be of length @a 
cdrs_length
+ * @return set to the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_cs_r_batch_derive (
+  struct TALER_CRYPTO_CsDenominationHelper *dh,
+  const struct TALER_CRYPTO_CsDeriveRequest *cdrs,
+  unsigned int cdrs_length,
+  bool for_melt,
+  struct TALER_DenominationCSPublicRPairP *crps);
+
+
+/**
+ * Close connection to @a dh.
+ *
+ * @param[in] dh connection to close
+ */
+void
+TALER_CRYPTO_helper_cs_disconnect (
+  struct TALER_CRYPTO_CsDenominationHelper *dh);
+
+/**
+ * Handle for talking to an online key signing helper.
+ */
+struct TALER_CRYPTO_ExchangeSignHelper;
+
+/**
+ * Function called with information about available keys for signing.  Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure
+ * @param start_time when does the key become available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ *                 zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself, NULL if the key was revoked or 
purged
+ * @param sm_pub public key of the security module, NULL if the key was 
revoked or purged
+ * @param sm_sig signature from the security module, NULL if the key was 
revoked or purged
+ *               The signature was already verified against @a sm_pub.
+ */
+typedef void
+(*TALER_CRYPTO_ExchangeKeyStatusCallback)(
+  void *cls,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Relative validity_duration,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_SecurityModulePublicKeyP *sm_pub,
+  const struct TALER_SecurityModuleSignatureP *sm_sig);
+
+
+/**
+ * Initiate connection to an online signing key helper.
+ *
+ * @param cfg configuration to use
+ * @param ekc function to call with key information
+ * @param ekc_cls closure for @a ekc
+ * @return NULL on error (such as bad @a cfg).
+ */
+struct TALER_CRYPTO_ExchangeSignHelper *
+TALER_CRYPTO_helper_esign_connect (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  TALER_CRYPTO_ExchangeKeyStatusCallback ekc,
+  void *ekc_cls);
+
+
+/**
+ * Function to call to 'poll' for updates to the available key material.
+ * Should be called whenever it is important that the key material status is
+ * current, like when handling a "/keys" request.  This function basically
+ * briefly checks if there are messages from the helper announcing changes to
+ * exchange online signing keys.
+ *
+ * @param esh helper process connection
+ */
+void
+TALER_CRYPTO_helper_esign_poll (struct TALER_CRYPTO_ExchangeSignHelper *esh);
+
+
+/**
+ * Request helper @a esh to sign @a msg using the current online
+ * signing key.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param esh helper process connection
+ * @param purpose message to sign (must extend beyond the purpose)
+ * @param[out] exchange_pub set to the public key used for the signature upon 
success
+ * @param[out] exchange_sig set to the signature upon success
+ * @return the error code (or #TALER_EC_NONE on success)
+ */
+enum TALER_ErrorCode
+TALER_CRYPTO_helper_esign_sign_ (
+  struct TALER_CRYPTO_ExchangeSignHelper *esh,
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Request helper @a esh to sign @a msg using the current online
+ * signing key.
+ *
+ * This operation will block until the signature has been obtained.  Should
+ * this process receive a signal (that is not ignored) while the operation is
+ * pending, the operation will fail.  Note that the helper may still believe
+ * that it created the signature. Thus, signals may result in a small
+ * differences in the signature counters.  Retrying in this case may work.
+ *
+ * @param esh helper process connection
+ * @param ps message to sign (MUST begin with a purpose)
+ * @param[out] epub set to the public key used for the signature upon success
+ * @param[out] esig set to the signature upon success
+ * @return the error code (or #TALER_EC_NONE on success)
+ */
+#define TALER_CRYPTO_helper_esign_sign(esh,ps,epub,esig) (         \
+    /* check size is set correctly */                              \
+    GNUNET_assert (ntohl ((ps)->purpose.size) == sizeof (*ps)),    \
+    /* check 'ps' begins with the purpose */                       \
+    GNUNET_static_assert (((void*) (ps)) ==                        \
+                          ((void*) &(ps)->purpose)),               \
+    TALER_CRYPTO_helper_esign_sign_ (esh,                          \
+                                     &(ps)->purpose,               \
+                                     epub,                         \
+                                     esig) )
+
+
+/**
+ * Ask the helper to revoke the public key @a exchange_pub .
+ * Will cause the helper to tell all clients that the key is now unavailable,
+ * and to create a replacement key.
+ *
+ * This operation will block until the revocation request has been
+ * transmitted.  Should this process receive a signal (that is not ignored)
+ * while the operation is pending, the operation may fail. If the key is
+ * unknown, this function will also appear to have succeeded. To be sure that
+ * the revocation worked, clients must watch the signing key status callback.
+ *
+ * @param esh helper to process connection
+ * @param exchange_pub the public key to revoke
+ */
+void
+TALER_CRYPTO_helper_esign_revoke (
+  struct TALER_CRYPTO_ExchangeSignHelper *esh,
+  const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Close connection to @a esh.
+ *
+ * @param[in] esh connection to close
+ */
+void
+TALER_CRYPTO_helper_esign_disconnect (
+  struct TALER_CRYPTO_ExchangeSignHelper *esh);
+
+
+/* ********************* wallet signing ************************** */
+
+
+/**
+ * Sign a request to create a purse.
+ *
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param merge_pub public key defining the merge capability
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param amount total amount in the purse (including fees)
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_create_sign (
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  uint32_t min_age,
+  const struct TALER_Amount *amount,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse creation request.
+ *
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param merge_pub public key defining the merge capability
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param amount total amount in the purse (including fees)
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_create_verify (
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  uint32_t min_age,
+  const struct TALER_Amount *amount,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to delete a purse.
+ *
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_delete_sign (
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse deletion request.
+ *
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_DELETE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_delete_verify (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to upload an encrypted contract.
+ *
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_econtract_upload_sign (
+  const void *econtract,
+  size_t econtract_size,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a signature over encrypted contract.
+ *
+ * @param econtract encrypted contract
+ * @param econtract_size number of bytes in @a econtract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify (
+  const void *econtract,
+  size_t econtract_size,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a signature over encrypted contract.
+ *
+ * @param h_econtract hashed encrypted contract
+ * @param contract_pub public key for the DH-encryption
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_CREATE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify2 (
+  const struct GNUNET_HashCode *h_econtract,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to inquire about a purse's status.
+ *
+ * @param purse_priv key identifying the purse
+ * @param[out] purse_sig resulting signature
+ */
+void
+TALER_wallet_purse_status_sign (
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Verify a purse status request signature.
+ *
+ * @param purse_pub purse’s public key
+ * @param purse_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_STATUS
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_status_verify (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Sign a request to deposit a coin into a purse.
+ *
+ * @param exchange_base_url URL of the exchange hosting the purse
+ * @param purse_pub purse’s public key
+ * @param amount amount of the coin's value to transfer to the purse
+ * @param h_denom_pub hash of the coin's denomination
+ * @param h_age_commitment hash of the coin's age commitment
+ * @param coin_priv key identifying the coin to be deposited
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_purse_deposit_sign (
+  const char *exchange_base_url,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_Amount *amount,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a purse deposit request.
+ *
+ * @param exchange_base_url URL of the exchange hosting the purse
+ * @param purse_pub purse’s public key
+ * @param amount amount of the coin's value to transfer to the purse
+ * @param h_denom_pub hash of the coin's denomination
+ * @param h_age_commitment hash of the coin's age commitment
+ * @param coin_pub key identifying the coin that is being deposited
+ * @param[out] coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_deposit_verify (
+  const char *exchange_base_url,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_Amount *amount,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a request by a purse to merge it into an account.
+ *
+ * @param reserve_uri identifies the location of the reserve
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub key identifying the purse
+ * @param merge_priv key identifying the merge capability
+ * @param[out] merge_sig resulting signature
+ */
+void
+TALER_wallet_purse_merge_sign (
+  const char *reserve_uri,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  struct TALER_PurseMergeSignatureP *merge_sig);
+
+
+/**
+ * Verify a purse merge request.
+ *
+ * @param reserve_uri identifies the location of the reserve
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param merge_pub public key of the merge capability
+ * @param merge_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_PURSE_MERGE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_merge_verify (
+  const char *reserve_uri,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  const struct TALER_PurseMergeSignatureP *merge_sig);
+
+
+/**
+ * Flags for a merge signature.
+ */
+enum TALER_WalletAccountMergeFlags
+{
+
+  /**
+   * A mode must be set. None is not a legal mode!
+   */
+  TALER_WAMF_MODE_NONE = 0,
+
+  /**
+   * We are merging a fully paid-up purse into a reserve.
+   */
+  TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE = 1,
+
+  /**
+   * We are creating a fresh purse, from the contingent
+   * of free purses that our account brings.
+   */
+  TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA = 2,
+
+  /**
+   * The account owner is willing to pay the purse_fee for the purse to be
+   * created from the account balance.
+   */
+  TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE = 3,
+
+  /**
+   * Bitmask to AND the full flags with to get the mode.
+   */
+  TALER_WAMF_MERGE_MODE_MASK = 3
+
+};
+
+
+/**
+ * Sign a request by an account to merge a purse.
+ *
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param amount total amount in the purse (including fees)
+ * @param purse_fee purse fee the reserve will pay,
+ *        only used if @a flags is #TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param flags flags for the operation
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_account_merge_sign (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *purse_fee,
+  uint32_t min_age,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify an account's request to merge a purse.
+ *
+ * @param merge_timestamp time when the merge happened
+ * @param purse_pub public key of the purse to merge
+ * @param purse_expiration when should the purse expire
+ * @param h_contract_terms contract the two parties agree on
+ * @param amount total amount in the purse (including fees)
+ * @param purse_fee purse fee the reserve will pay,
+ *        only used if @a flags is #TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE
+ * @param min_age age restriction to apply for deposits into the purse
+ * @param flags flags for the operation
+ * @param reserve_pub account’s public key
+ * @param reserve_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_merge_verify (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *purse_fee,
+  uint32_t min_age,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a request to keep a reserve open.
+ *
+ * @param reserve_payment how much to pay from the
+ *        reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ *       wants to have concurrently open for this reserve
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_open_sign (
+  const struct TALER_Amount *reserve_payment,
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  struct GNUNET_TIME_Timestamp reserve_expiration,
+  uint32_t purse_limit,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify a request to keep a reserve open.
+ *
+ * @param reserve_payment how much to pay from the
+ *        reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ *       wants to have concurrently open for this reserve
+ * @param reserve_pub key identifying the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_verify (
+  const struct TALER_Amount *reserve_payment,
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  struct GNUNET_TIME_Timestamp reserve_expiration,
+  uint32_t purse_limit,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign to deposit coin to pay for keeping a reserve open.
+ *
+ * @param coin_contribution how much the coin should contribute
+ * @param reserve_sig signature over the reserve open operation
+ * @param coin_priv private key of the coin
+ * @param[out] coin_sig signature by the coin
+ */
+void
+TALER_wallet_reserve_open_deposit_sign (
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ReserveSignatureP *reserve_sig,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify signature that deposits coin to pay for keeping a reserve open.
+ *
+ * @param coin_contribution how much the coin should contribute
+ * @param reserve_sig signature over the reserve open operation
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature by the coin
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_deposit_verify (
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ReserveSignatureP *reserve_sig,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a request to close a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param h_payto where to send the funds (NULL allowed to send
+ *        to origin of the reserve)
+ * @param reserve_priv key identifying the reserve
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_close_sign (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify wallet request to close an account.
+ *
+ * @param request_timestamp when was the request created
+ * @param h_payto where to send the funds (NULL/all zeros
+ *        allowed to send to origin of the reserve)
+ * @param reserve_pub account’s public key
+ * @param reserve_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_RESERVE_CLOSE
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_close_verify (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a request by a wallet to perform a KYC check.
+ *
+ * @param reserve_priv key identifying the wallet/account
+ * @param balance_threshold the balance threshold the wallet is about to cross
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_account_setup_sign (
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_Amount *balance_threshold,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify account setup request.
+ *
+ * @param reserve_pub reserve the setup request was for
+ * @param balance_threshold the balance threshold the wallet is about to cross
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_setup_verify (
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *balance_threshold,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign request to the exchange to confirm certain
+ * @a details about the owner of a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param details which attributes are requested
+ * @param reserve_priv private key of the reserve
+ * @param[out] reserve_sig where to store the signature
+ */
+void
+TALER_wallet_reserve_attest_request_sign (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const json_t *details,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify request to the exchange to confirm certain
+ * @a details about the owner of a reserve.
+ *
+ * @param request_timestamp when was the request created
+ * @param details which attributes are requested
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig where to store the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_attest_request_verify (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const json_t *details,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign a deposit permission.  Function for wallets.
+ *
+ * @param amount the amount to be deposited
+ * @param deposit_fee the deposit fee we expect to pay
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param wallet_data_hash hash over wallet inputs into the contract (maybe 
NULL)
+ * @param h_age_commitment hash over the age commitment, if applicable to the 
denomination (maybe NULL)
+ * @param h_policy hash over the policy extension
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param coin_priv coin’s private key
+ * @param wallet_timestamp timestamp when the contract was finalized, must not 
be too far in the future
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the exchange (can be zero if refunds are not allowed); must 
not be after the @a wire_deadline
+ * @param[out] coin_sig set to the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT
+ */
+void
+TALER_wallet_deposit_sign (
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct GNUNET_HashCode *wallet_data_hash,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp wallet_timestamp,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a deposit permission.
+ *
+ * @param amount the amount to be deposited
+ * @param deposit_fee the deposit fee we expect to pay
+ * @param h_wire hash of the merchant’s account details
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param wallet_data_hash hash over wallet inputs into the contract (maybe 
NULL)
+ * @param h_age_commitment hash over the age commitment (maybe all zeroes, if 
not applicable to the denomination)
+ * @param h_policy hash over the policy extension
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param wallet_timestamp timestamp when the contract was finalized, must not 
be too far in the future
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the exchange (can be zero if refunds are not allowed); must 
not be after the @a wire_deadline
+ * @param coin_pub coin’s public key
+ * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_deposit_verify (
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct GNUNET_HashCode *wallet_data_hash,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp wallet_timestamp,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign a melt request.
+ *
+ * @param amount_with_fee the amount to be melted (with fee)
+ * @param melt_fee the melt fee we expect to pay
+ * @param rc refresh session we are committed to
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
+ * @param coin_priv coin’s private key
+ * @param[out] coin_sig set to the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_MELT
+ */
+void
+TALER_wallet_melt_sign (
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *melt_fee,
+  const struct TALER_RefreshCommitmentP *rc,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify a melt request.
+ *
+ * @param amount_with_fee the amount to be melted (with fee)
+ * @param melt_fee the melt fee we expect to pay
+ * @param rc refresh session we are committed to
+ * @param h_denom_pub hash of the coin denomination's public key
+ * @param h_age_commitment hash of the age commitment (may be NULL)
+ * @param coin_pub coin’s public key
+ * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_MELT
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_melt_verify (
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *melt_fee,
+  const struct TALER_RefreshCommitmentP *rc,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign link data.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the new coin
+ * @param transfer_pub transfer public key
+ * @param bch blinded coin hash
+ * @param old_coin_priv private key to sign with
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_link_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_TransferPublicKeyP *transfer_pub,
+  const struct TALER_BlindedCoinHashP *bch,
+  const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify link signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the new coin
+ * @param transfer_pub transfer public key
+ * @param h_coin_ev hash of the coin envelope
+ * @param old_coin_pub old coin key that the link signature is for
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_link_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_TransferPublicKeyP *transfer_pub,
+  const struct TALER_BlindedCoinHashP *h_coin_ev,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Sign withdraw request.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin to 
withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param bch blinded coin hash
+ * @param reserve_priv private key to sign with
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_withdraw_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_BlindedCoinHashP *bch,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify withdraw request.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin to 
withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param bch blinded coin hash
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_withdraw_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_BlindedCoinHashP *bch,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Sign age-withdraw request.
+ *
+ * @param h_commitment hash over all n*kappa blinded coins in the commitment 
for the age-withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param mask the mask that defines the age groups
+ * @param max_age maximum age from which the age group is derived, that the 
withdrawn coins must be restricted to.
+ * @param reserve_priv private key to sign with
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_age_withdraw_sign (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_AgeMask *mask,
+  uint8_t max_age,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+/**
+ * Verify an age-withdraw request.
+ *
+ * @param h_commitment hash all n*kappa blinded coins in the commitment for 
the age-withdraw
+ * @param amount_with_fee amount to debit the reserve for
+ * @param mask the mask that defines the age groups
+ * @param max_age maximum age from which the age group is derived, that the 
withdrawn coins must be restricted to.
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_age_withdraw_verify (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_AgeMask *mask,
+  uint8_t max_age,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+/**
+ * Verify exchange melt confirmation.
+ *
+ * @param rc refresh session this is about
+ * @param noreveal_index gamma value chosen by the exchange
+ * @param exchange_pub public signing key used
+ * @param exchange_sig signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_melt_confirmation_verify (
+  const struct TALER_RefreshCommitmentP *rc,
+  uint32_t noreveal_index,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Verify recoup signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_pub coin key of the coin to be recouped
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Create recoup signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_priv coin key of the coin to be recouped
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_recoup_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify recoup-refresh signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_pub coin key of the coin to be recouped
+ * @param coin_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_refresh_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Create recoup-refresh signature.
+ *
+ * @param h_denom_pub hash of the denomiantion public key of the coin
+ * @param coin_bks blinding factor used when withdrawing the coin
+ * @param coin_priv coin key of the coin to be recouped
+ * @param[out] coin_sig resulting signature
+ */
+void
+TALER_wallet_recoup_refresh_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Verify reserve history request signature.
+ *
+ * @param ts timestamp used
+ * @param history_fee how much did the wallet say it would pay
+ * @param reserve_pub reserve the history request was for
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Create reserve history request signature.
+ *
+ * @param ts timestamp used
+ * @param history_fee how much do we expect to pay
+ * @param reserve_priv private key of the reserve the history request is for
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_history_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Verify reserve status request signature.
+ *
+ * @param ts timestamp used
+ * @param reserve_pub reserve the status request was for
+ * @param reserve_sig resulting signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_status_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Create reserve status request signature.
+ *
+ * @param ts timestamp used
+ * @param reserve_priv private key of the reserve the status request is for
+ * @param[out] reserve_sig resulting signature
+ */
+void
+TALER_wallet_reserve_status_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/* ********************* merchant signing ************************** */
+
+
+/**
+ * Create merchant signature approving a refund.
+ *
+ * @param coin_pub coin to be refunded
+ * @param h_contract_terms contract to be refunded
+ * @param rtransaction_id unique ID for this (partial) refund
+ * @param amount amount to be refunded
+ * @param merchant_priv private key to sign with
+ * @param[out] merchant_sig where to write the signature
+ */
+void
+TALER_merchant_refund_sign (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *amount,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Verify merchant signature approving a refund.
+ *
+ * @param coin_pub coin to be refunded
+ * @param h_contract_terms contract to be refunded
+ * @param rtransaction_id unique ID for this (partial) refund
+ * @param amount amount to be refunded
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_refund_verify (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *amount,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/* ********************* exchange deposit signing ************************* */
+
+/**
+ * Sign a deposit.
+ *
+ * @param h_contract_terms hash of contract terms
+ * @param h_wire hash of the merchant account details
+ * @param coin_pub coin to be deposited
+ * @param merchant_priv private key to sign with
+ * @param[out] merchant_sig where to write the signature
+ */
+void
+TALER_merchant_deposit_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Verify a deposit.
+ *
+ * @param merchant merchant public key
+ * @param coin_pub public key of the deposited coin
+ * @param h_contract_terms hash of contract terms
+ * @param h_wire hash of the merchant account details
+ * @param merchant_sig signature of the merchant
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_deposit_verify (
+  const struct TALER_MerchantPublicKeyP *merchant,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/* ********************* exchange online signing ************************** */
+
+
+/**
+ * Signature of a function that signs the message in @a purpose with the
+ * exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. *
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+typedef enum TALER_ErrorCode
+(*TALER_ExchangeSignCallback)(
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Signature of a function that signs the message in @a purpose with the
+ * exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. *
+ * @param cls closure
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+typedef enum TALER_ErrorCode
+(*TALER_ExchangeSignCallback2)(
+  void *cls,
+  const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create deposit confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param h_wire hash of the merchant’s account details
+ * @param h_policy hash over the policy extension, can be NULL
+ * @param exchange_timestamp timestamp when the contract was finalized, must 
not be too far off
+ * @param wire_deadline date until which the exchange should wire the funds
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the exchange (can be zero if refunds are not allowed); must 
not be after the @a wire_deadline
+ * @param amount_without_fee the amount to be deposited after fees
+ * @param coin_pub public key of the deposited coin
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_deposit_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Timestamp wire_deadline,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify deposit confirmation signature.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer (further details are never disclosed to the exchange)
+ * @param h_wire hash of the merchant’s account details
+ * @param h_policy hash over the policy extension, can be NULL
+ * @param exchange_timestamp timestamp when the contract was finalized, must 
not be too far off
+ * @param wire_deadline date until which the exchange should wire the funds
+ * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the exchange (can be zero if refunds are not allowed); must 
not be after the @a wire_deadline
+ * @param amount_without_fee the amount to be deposited after fees
+ * @param coin_pub public key of the deposited coin
+ * @param merchant_pub the public key of the merchant (used to identify the 
merchant for refund requests)
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_deposit_confirmation_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Timestamp wire_deadline,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create refund confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_contract_terms hash of contract being refunded
+ * @param coin_pub public key of the coin receiving the refund
+ * @param merchant public key of the merchant that granted the refund
+ * @param rtransaction_id refund transaction ID used by the merchant
+ * @param refund_amount amount refunded
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_refund_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *refund_amount,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify refund confirmation signature.
+ *
+ * @param h_contract_terms hash of contract being refunded
+ * @param coin_pub public key of the coin receiving the refund
+ * @param merchant public key of the merchant that granted the refund
+ * @param rtransaction_id refund transaction ID used by the merchant
+ * @param refund_amount amount refunded
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_refund_confirmation_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *refund_amount,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create refresh melt confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param rc refresh commitment that identifies the melt operation
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_melt_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_RefreshCommitmentP *rc,
+  uint32_t noreveal_index,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify refresh melt confirmation signature.
+ *
+ * @param rc refresh commitment that identifies the melt operation
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_melt_confirmation_verify (
+  const struct TALER_RefreshCommitmentP *rc,
+  uint32_t noreveal_index,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create exchange purse refund confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param amount_without_fee refunded amount
+ * @param refund_fee refund fee charged
+ * @param coin_pub coin that was refunded
+ * @param purse_pub public key of the expired purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_refund_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature of exchange affirming purse refund
+ * from purse expiration.
+ *
+ * @param amount_without_fee refunded amount
+ * @param refund_fee refund fee charged
+ * @param coin_pub coin that was refunded
+ * @param purse_pub public key of the expired purse
+ * @param pub public key to verify signature against
+ * @param sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_refund_verify (
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create exchange key set signature.
+ *
+ * @param scb function to call to create the signature
+ * @param cls closure for @a scb
+ * @param timestamp time when the key set was issued
+ * @param hc hash over all the keys
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_key_set_sign (
+  TALER_ExchangeSignCallback2 scb,
+  void *cls,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct GNUNET_HashCode *hc,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify key set signature.
+ *
+ * @param timestamp time when the key set was issued
+ * @param hc hash over all the keys
+ * @param pub public key to verify signature against
+ * @param sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_key_set_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct GNUNET_HashCode *hc,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create account KYC setup success signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_payto target of the KYC account
+ * @param kyc JSON data describing which KYC checks
+ *            were satisfied
+ * @param timestamp time when the KYC was confirmed
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_account_setup_success_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PaytoHashP *h_payto,
+  const json_t *kyc,
+  struct GNUNET_TIME_Timestamp timestamp,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify account KYC setup success signature.
+ *
+ * @param h_payto target of the KYC account
+ * @param kyc JSON data describing which KYC checks
+ *            were satisfied
+ * @param timestamp time when the KYC was confirmed
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_account_setup_success_verify (
+  const struct TALER_PaytoHashP *h_payto,
+  const json_t *kyc,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Hash normalized @a j JSON object or array and
+ * store the result in @a hc.
+ *
+ * @param j JSON to hash
+ * @param[out] hc where to write the hash
+ */
+void
+TALER_json_hash (const json_t *j,
+                 struct GNUNET_HashCode *hc);
+
+
+/**
+ * Update the @a hash_context in the computation of the
+ * h_details for a wire status signature.
+ *
+ * @param[in,out] hash_context context to update
+ * @param h_contract_terms hash of the contract
+ * @param execution_time when was the wire transfer initiated
+ * @param coin_pub deposited coin
+ * @param deposit_value contribution of the coin
+ * @param deposit_fee how high was the deposit fee
+ */
+void
+TALER_exchange_online_wire_deposit_append (
+  struct GNUNET_HashContext *hash_context,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *deposit_value,
+  const struct TALER_Amount *deposit_fee);
+
+
+/**
+ * Create wire deposit signature.
+ *
+ * @param scb function to call to create the signature
+ * @param total amount the merchant was credited
+ * @param wire_fee fee charged by the exchange for the wire transfer
+ * @param merchant_pub which merchant was credited
+ * @param payto payto://-URI of the merchant account
+ * @param h_details hash over the aggregation details
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_wire_deposit_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_Amount *total,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const char *payto,
+  const struct GNUNET_HashCode *h_details,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify wire deposit signature.
+ *
+ * @param total amount the merchant was credited
+ * @param wire_fee fee charged by the exchange for the wire transfer
+ * @param merchant_pub which merchant was credited
+ * @param h_payto hash of the payto://-URI of the merchant account
+ * @param h_details hash over the aggregation details
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_wire_deposit_verify (
+  const struct TALER_Amount *total,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct GNUNET_HashCode *h_details,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create wire confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_wire hash of the merchant's account
+ * @param h_contract_terms hash of the contract
+ * @param wtid wire transfer this deposit was aggregated into
+ * @param coin_pub public key of the deposited coin
+ * @param execution_time when was wire transfer initiated
+ * @param coin_contribution what was @a coin_pub's contribution to the wire 
transfer
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_wire_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_Amount *coin_contribution,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm wire signature.
+ *
+ * @param h_wire hash of the merchant's account
+ * @param h_contract_terms hash of the contract
+ * @param wtid wire transfer this deposit was aggregated into
+ * @param coin_pub public key of the deposited coin
+ * @param execution_time when was wire transfer initiated
+ * @param coin_contribution what was @a coin_pub's contribution to the wire 
transfer
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_wire_verify (
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create confirm recoup signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param reserve_pub reserve that was credited
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm recoup signature.
+ *
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param reserve_pub reserve that was credited
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create confirm recoup refresh signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param old_coin_pub old coin that was credited
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_refresh_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify confirm recoup refresh signature.
+ *
+ * @param timestamp when was the recoup done
+ * @param recoup_amount how much was recouped
+ * @param coin_pub coin that was recouped
+ * @param old_coin_pub old coin that was credited
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_refresh_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create denomination unknown signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is unknown
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_unknown_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify denomination unknown signature.
+ *
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is unknown
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_unknown_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create denomination expired signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is expired
+ * @param op character string describing the operation for which
+ *           the denomination is expired
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_expired_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const char *op,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify denomination expired signature.
+ *
+ * @param timestamp when was the error created
+ * @param h_denom_pub hash of denomination that is expired
+ * @param op character string describing the operation for which
+ *           the denomination is expired
+ * @param pub where to write the public key
+ * @param sig where to write the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_expired_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const char *op,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create reserve closure signature.
+ *
+ * @param scb function to call to create the signature
+ * @param timestamp time when the reserve was closed
+ * @param closing_amount amount left in the reserve
+ * @param closing_fee closing fee charged
+ * @param payto target of the wire transfer
+ * @param wtid wire transfer subject used
+ * @param reserve_pub public key of the closed reserve
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_closed_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *closing_amount,
+  const struct TALER_Amount *closing_fee,
+  const char *payto,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify reserve closure signature.
+ *
+ * @param timestamp time when the reserve was closed
+ * @param closing_amount amount left in the reserve
+ * @param closing_fee closing fee charged
+ * @param payto target of the wire transfer
+ * @param wtid wire transfer subject used
+ * @param reserve_pub public key of the closed reserve
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_closed_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *closing_amount,
+  const struct TALER_Amount *closing_fee,
+  const char *payto,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create signature by exchange affirming that a reserve
+ * has had certain attributes verified via KYC.
+ *
+ * @param scb function to call to create the signature
+ * @param attest_timestamp our time
+ * @param expiration_time when does the KYC data expire
+ * @param reserve_pub for which reserve are attributes attested
+ * @param attributes JSON object with attributes being attested to
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_attest_details_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp attest_timestamp,
+  struct GNUNET_TIME_Timestamp expiration_time,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const json_t *attributes,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature by exchange affirming that a reserve
+ * has had certain attributes verified via KYC.
+ *
+ * @param attest_timestamp our time
+ * @param expiration_time when does the KYC data expire
+ * @param reserve_pub for which reserve are attributes attested
+ * @param attributes JSON object with attributes being attested to
+ * @param pub exchange public key
+ * @param sig exchange signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_attest_details_verify (
+  struct GNUNET_TIME_Timestamp attest_timestamp,
+  struct GNUNET_TIME_Timestamp expiration_time,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const json_t *attributes,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Create signature by exchange affirming that a purse was created.
+ *
+ * @param scb function to call to create the signature
+ * @param exchange_time our time
+ * @param purse_expiration when will the purse expire
+ * @param amount_without_fee total amount to be put into the purse (without 
deposit fees)
+ * @param total_deposited total currently in the purse
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract for the purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_created_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *total_deposited,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify exchange signature about a purse creation and balance.
+ *
+ * @param exchange_time our time
+ * @param purse_expiration when will the purse expire
+ * @param amount_without_fee total amount to be put into the purse (without 
deposit fees)
+ * @param total_deposited total currently in the purse
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract for the purse
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_created_verify (
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *total_deposited,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign affirmation that a purse was merged.
+ *
+ * @param scb function to call to create the signature
+ * @param exchange_time our time
+ * @param purse_expiration when does the purse expire
+ * @param amount_without_fee total amount that should be in the purse without 
deposit fees
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract of the purse
+ * @param reserve_pub reserve the purse will be merged into
+ * @param exchange_url exchange at which the @a reserve_pub lives
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_merged_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *exchange_url,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify affirmation that a purse will be merged.
+ *
+ * @param exchange_time our time
+ * @param purse_expiration when does the purse expire
+ * @param amount_without_fee total amount that should be in the purse without 
deposit fees
+ * @param purse_pub public key of the purse
+ * @param h_contract_terms hash of the contract of the purse
+ * @param reserve_pub reserve the purse will be merged into
+ * @param exchange_url exchange at which the @a reserve_pub lives
+ * @param pub the public key of the exchange to check against
+ * @param sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_merged_verify (
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *exchange_url,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign information about the status of a purse.
+ *
+ * @param scb function to call to create the signature
+ * @param merge_timestamp when was the purse merged (can be never)
+ * @param deposit_timestamp when was the purse fully paid up (can be never)
+ * @param balance current balance of the purse
+ * @param[out] pub where to write the public key
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_purse_status_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  struct GNUNET_TIME_Timestamp deposit_timestamp,
+  const struct TALER_Amount *balance,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verify signature over information about the status of a purse.
+ *
+ * @param merge_timestamp when was the purse merged (can be never)
+ * @param deposit_timestamp when was the purse fully paid up (can be never)
+ * @param balance current balance of the purse
+ * @param exchange_pub the public key of the exchange to check against
+ * @param exchange_sig the signature to check
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_status_verify (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  struct GNUNET_TIME_Timestamp deposit_timestamp,
+  const struct TALER_Amount *balance,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/**
+ * Create age-withdraw confirmation signature.
+ *
+ * @param scb function to call to create the signature
+ * @param h_commitment age-withdraw commitment that identifies the n*kappa 
blinded coins
+ * @param noreveal_index gamma cut-and-choose value chosen by the exchange
+ * @param[out] pub where to write the exchange public key
+ * @param[out] sig where to write the exchange signature
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TALER_exchange_online_age_withdraw_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  uint32_t noreveal_index,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Verfiy an exchange age-withdraw confirmation
+ *
+ * @param h_commitment Commitment over all n*kappa coin candidates from the 
original request to age-withdraw
+ * @param noreveal_index The index returned by the exchange
+ * @param exchange_pub The public key used for signing
+ * @param exchange_sig The signature from the exchange
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_age_withdraw_confirmation_verify (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  uint32_t noreveal_index,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig);
+
+
+/* ********************* offline signing ************************** */
+
+
+/**
+ * Create AML officer status change signature.
+ *
+ * @param officer_pub public key of the AML officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_aml_officer_status_sign (
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const char *officer_name,
+  struct GNUNET_TIME_Timestamp change_date,
+  bool is_active,
+  bool read_only,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify AML officer status change signature.
+ *
+ * @param officer_pub public key of the AML officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_aml_officer_status_verify (
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const char *officer_name,
+  struct GNUNET_TIME_Timestamp change_date,
+  bool is_active,
+  bool read_only,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create auditor addition signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param auditor_url URL of the auditor
+ * @param start_date when to enable the auditor (for replay detection)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_auditor_add_sign (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  struct GNUNET_TIME_Timestamp start_date,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify auditor add signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param auditor_url URL of the auditor
+ * @param start_date when to enable the auditor (for replay detection)
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_add_verify (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  struct GNUNET_TIME_Timestamp start_date,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create auditor deletion signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param end_date when to disable the auditor (for replay detection)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_auditor_del_sign (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify auditor del signature.
+ *
+ * @param auditor_pub public key of the auditor
+ * @param end_date when to disable the auditor (for replay detection)
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_del_verify (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create denomination revocation signature.
+ *
+ * @param h_denom_pub hash of public denomination key to revoke
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_denomination_revoke_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify denomination revocation signature.
+ *
+ * @param h_denom_pub hash of public denomination key to revoke
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denomination_revoke_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create signkey revocation signature.
+ *
+ * @param exchange_pub public signing key to revoke
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_signkey_revoke_sign (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signkey revocation signature.
+ *
+ * @param exchange_pub public signkey key to revoke
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_revoke_verify (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create signkey validity signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param end_sign end point (exclusive) for validity for signing
+ * @param end_legal legal end point of signature validity
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_signkey_validity_sign (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Timestamp end_sign,
+  struct GNUNET_TIME_Timestamp end_legal,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signkey validitity signature.
+ *
+ * @param exchange_pub public signkey key to validate
+ * @param start_sign starting point of validity for signing
+ * @param end_sign end point (exclusive) for validity for signing
+ * @param end_legal legal end point of signature validity
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_validity_verify (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Timestamp end_sign,
+  struct GNUNET_TIME_Timestamp end_legal,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create denomination key validity signature.
+ *
+ * @param h_denom_pub hash of the denomination's public key
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this 
key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees for this denomination
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_denom_validity_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify denomination key validity signature.
+ *
+ * @param h_denom_pub hash of the denomination's public key
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this 
key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees for this denomination
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denom_validity_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create offline signature about an exchange's partners.
+ *
+ * @param partner_pub master public key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_partner_details_sign (
+  const struct TALER_MasterPublicKeyP *partner_pub,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  struct GNUNET_TIME_Relative wad_frequency,
+  const struct TALER_Amount *wad_fee,
+  const char *partner_base_url,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify signature about an exchange's partners.
+ *
+ * @param partner_pub master public key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_partner_details_verify (
+  const struct TALER_MasterPublicKeyP *partner_pub,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  struct GNUNET_TIME_Relative wad_frequency,
+  const struct TALER_Amount *wad_fee,
+  const char *partner_base_url,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create offline signature about wiring profits to a
+ * regular non-escrowed account of the exchange.
+ *
+ * @param wtid (random) wire transfer ID to be used
+ * @param date when was the profit drain approved (not exact time of execution)
+ * @param amount how much should be wired
+ * @param account_section configuration section of the
+ *        exchange specifying the account to be debited
+ * @param payto_uri target account to be credited
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_profit_drain_sign (
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  struct GNUNET_TIME_Timestamp date,
+  const struct TALER_Amount *amount,
+  const char *account_section,
+  const char *payto_uri,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify offline signature about wiring profits to a
+ * regular non-escrowed account of the exchange.
+ *
+ * @param wtid (random) wire transfer ID to be used
+ * @param date when was the profit drain approved (not exact time of execution)
+ * @param amount how much should be wired
+ * @param account_section configuration section of the
+ *        exchange specifying the account to be debited
+ * @param payto_uri target account to be credited
+ * @param master_pub public key to verify signature against
+ * @param master_sig the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_profit_drain_verify (
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  struct GNUNET_TIME_Timestamp date,
+  const struct TALER_Amount *amount,
+  const char *account_section,
+  const char *payto_uri,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create security module EdDSA signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_eddsa_sign (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+  struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module EdDSA signature.
+ *
+ * @param exchange_pub public signing key to validate
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_eddsa_verify (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePublicKeyP *secm_pub,
+  const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create security module denomination signature.
+ *
+ * @param h_rsa hash of the RSA public key to sign
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_rsa_sign (
+  const struct TALER_RsaPubHashP *h_rsa,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+  struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module denomination signature.
+ *
+ * @param h_rsa hash of the public key to validate
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_rsa_verify (
+  const struct TALER_RsaPubHashP *h_rsa,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePublicKeyP *secm_pub,
+  const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create security module denomination signature.
+ *
+ * @param h_cs hash of the CS public key to sign
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_priv security module key to sign with
+ * @param[out] secm_sig where to write the signature
+ */
+void
+TALER_exchange_secmod_cs_sign (
+  const struct TALER_CsPubHashP *h_cs,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePrivateKeyP *secm_priv,
+  struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Verify security module denomination signature.
+ *
+ * @param h_cs hash of the public key to validate
+ * @param section_name name of the section in the configuration
+ * @param start_sign starting point of validity for signing
+ * @param duration how long will the key be in use
+ * @param secm_pub public key to verify against
+ * @param secm_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_secmod_cs_verify (
+  const struct TALER_CsPubHashP *h_cs,
+  const char *section_name,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Relative duration,
+  const struct TALER_SecurityModulePublicKeyP *secm_pub,
+  const struct TALER_SecurityModuleSignatureP *secm_sig);
+
+
+/**
+ * Create denomination key validity signature by the auditor.
+ *
+ * @param auditor_url BASE URL of the auditor's API
+ * @param h_denom_pub hash of the denomination's public key
+ * @param master_pub master public key of the exchange
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this 
key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees the exchange charges for this denomination
+ * @param auditor_priv private key to sign with
+ * @param[out] auditor_sig where to write the signature
+ */
+void
+TALER_auditor_denom_validity_sign (
+  const char *auditor_url,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_AuditorPrivateKeyP *auditor_priv,
+  struct TALER_AuditorSignatureP *auditor_sig);
+
+
+/**
+ * Verify denomination key validity signature from auditor.
+ *
+ * @param auditor_url BASE URL of the auditor's API
+ * @param h_denom_pub hash of the denomination's public key
+ * @param master_pub master public key of the exchange
+ * @param stamp_start when does the exchange begin signing with this key
+ * @param stamp_expire_withdraw when does the exchange end signing with this 
key
+ * @param stamp_expire_deposit how long does the exchange accept the deposit 
of coins with this key
+ * @param stamp_expire_legal how long does the exchange preserve information 
for legal disputes with this key
+ * @param coin_value what is the value of coins signed with this key
+ * @param fees fees the exchange charges for this denomination
+ * @param auditor_pub public key to verify against
+ * @param auditor_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_auditor_denom_validity_verify (
+  const char *auditor_url,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const struct TALER_AuditorSignatureP *auditor_sig);
+
+
+/* **************** /wire account offline signing **************** */
+
+
+/**
+ * Create wire fee signature.
+ *
+ * @param payment_method the payment method
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the wire fees
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_wire_fee_sign (
+  const char *payment_method,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_WireFeeSet *fees,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire fee signature.
+ *
+ * @param payment_method the payment method
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the wire fees
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_fee_verify (
+  const char *payment_method,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_WireFeeSet *fees,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create global fees signature.
+ *
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the global fees
+ * @param purse_timeout how long do unmerged purses stay around
+ * @param history_expiration how long do we keep the history of an account
+ * @param purse_account_limit how many concurrent purses are free per account 
holder
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_global_fee_sign (
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify global fees signature.
+ *
+ * @param start_time when do the fees start to apply
+ * @param end_time when do the fees start to apply
+ * @param fees the global fees
+ * @param purse_timeout how long do unmerged purses stay around
+ * @param history_expiration how long do we keep the history of an account
+ * @param purse_account_limit how many concurrent purses are free per account 
holder
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_global_fee_verify (
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create wire account addition signature.
+ *
+ * @param payto_uri bank account
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the 
account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the 
account; see AccountRestriction in the spec
+ * @param now timestamp to use for the signature (rounded)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_wire_add_sign (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire account addition signature.
+ *
+ * @param payto_uri bank account
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the 
account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the 
account; see AccountRestriction in the spec
+ * @param sign_time timestamp when signature was created
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_add_verify (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  struct GNUNET_TIME_Timestamp sign_time,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create wire account removal signature.
+ *
+ * @param payto_uri bank account
+ * @param now timestamp to use for the signature (rounded)
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_wire_del_sign (
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify wire account deletion signature.
+ *
+ * @param payto_uri bank account
+ * @param sign_time timestamp when signature was created
+ * @param master_pub public key to verify against
+ * @param master_sig the signature the signature
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_del_verify (
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp sign_time,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Check the signature in @a master_sig.
+ *
+ * @param payto_uri URI that is signed
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the 
account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the 
account; see AccountRestriction in the spec
+ * @param master_pub master public key of the exchange
+ * @param master_sig signature of the exchange
+ * @return #GNUNET_OK if signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_wire_signature_check (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Create a signed wire statement for the given account.
+ *
+ * @param payto_uri account specification
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the 
account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the 
account; see AccountRestriction in the spec
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_wire_signature_make (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Compute the hash of the given wire details.   The resulting
+ * @a hc is what will be put into the contract between customer
+ * and merchant for signing by both parties.
+ *
+ * @param payto_uri bank account
+ * @param salt salt used to eliminate brute-force inversion
+ * @param[out] hc set to the hash
+ */
+void
+TALER_merchant_wire_signature_hash (const char *payto_uri,
+                                    const struct TALER_WireSaltP *salt,
+                                    struct TALER_MerchantWireHashP *hc);
+
+
+/**
+ * Check the signature in @a wire_s.
+ *
+ * @param payto_uri URL that is signed
+ * @param salt the salt used to salt the @a payto_uri when hashing
+ * @param merch_pub public key of the merchant
+ * @param merch_sig signature of the merchant
+ * @return #GNUNET_OK if signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_wire_signature_check (
+  const char *payto_uri,
+  const struct TALER_WireSaltP *salt,
+  const struct TALER_MerchantPublicKeyP *merch_pub,
+  const struct TALER_MerchantSignatureP *merch_sig);
+
+
+/**
+ * Create a signed wire statement for the given account.
+ *
+ * @param payto_uri account specification
+ * @param salt the salt used to salt the @a payto_uri when hashing
+ * @param merch_priv private key to sign with
+ * @param[out] merch_sig where to write the signature
+ */
+void
+TALER_merchant_wire_signature_make (
+  const char *payto_uri,
+  const struct TALER_WireSaltP *salt,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct TALER_MerchantSignatureP *merch_sig);
+
+
+/**
+ * Sign a payment confirmation.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer
+ * @param merch_priv private key to sign with
+ * @param[out] merch_sig where to write the signature
+ */
+void
+TALER_merchant_pay_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct GNUNET_CRYPTO_EddsaSignature *merch_sig);
+
+
+/**
+ * Verify payment confirmation signature.
+ *
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature to verify
+ * @return #GNUNET_OK if the signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_merchant_pay_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig);
+
+
+/**
+ * Sign contract sent by the merchant to the wallet.
+ *
+ * @param h_contract_terms hash of the contract terms
+ * @param merch_priv private key to sign with
+ * @param[out] merch_sig where to write the signature
+ */
+void
+TALER_merchant_contract_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct GNUNET_CRYPTO_EddsaSignature *merch_sig);
+
+
+/* **************** /management/extensions offline signing **************** */
+
+/**
+ * Create a signature for the hash of the manifests of extensions
+ *
+ * @param h_manifests hash of the JSON object representing the manifests
+ * @param master_priv private key to sign with
+ * @param[out] master_sig where to write the signature
+ */
+void
+TALER_exchange_offline_extension_manifests_hash_sign (
+  const struct TALER_ExtensionManifestsHashP *h_manifests,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Verify the signature in @a master_sig of the given hash, taken over the JSON
+ * blob representing the manifests of extensions
+ *
+ * @param h_manifest hash of the JSON blob of manifests of extensions
+ * @param master_pub master public key of the exchange
+ * @param master_sig signature of the exchange
+ * @return #GNUNET_OK if signature is valid
+ */
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_extension_manifests_hash_verify (
+  const struct TALER_ExtensionManifestsHashP *h_manifest,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig
+  );
+
+
+/**
+ * @brief Representation of an age commitment:  one public key per age group.
+ *
+ * The number of keys must be be the same as the number of bits set in the
+ * corresponding age mask.
+ */
+struct TALER_AgeCommitment
+{
+
+  /**
+   * The age mask defines the age groups that were a parameter during the
+   * generation of this age commitment
+   */
+  struct TALER_AgeMask mask;
+
+  /**
+   * The number of public keys, which must be the same as the number of
+   * groups in the mask.
+   */
+  size_t num;
+
+  /**
+   * The list of @e num public keys.  In must have same size as the number of
+   * age groups defined in the mask.
+   *
+   * A hash of this list is the hashed commitment that goes into FDC
+   * calculation during the withdraw and refresh operations for new coins. That
+   * way, the particular age commitment becomes mandatory and bound to a coin.
+   *
+   * The list has been allocated via GNUNET_malloc().
+   */
+  struct TALER_AgeCommitmentPublicKeyP *keys;
+};
+
+
+/**
+ * @brief Proof for a particular age commitment, used in age attestation
+ *
+ * This struct is used in a call to TALER_age_commitment_attest to create an
+ * attestation for a minimum age (if that minimum age is less or equal to the
+ * committed age for this proof).  It consists of a list private keys, one per
+ * age group, for which the committed age is either lager or within that
+ * particular group.
+ */
+struct TALER_AgeProof
+{
+  /**
+   * The number of private keys, which must be at most num_pub_keys.  One minus
+   * this number corresponds to the largest age group that is supported with
+   * this age commitment.
+   * **Note**, that this and the next field are only relevant on the wallet
+   * side for attestation and derive operations.
+   */
+  size_t num;
+
+  /**
+   * List of @e num private keys.
+   *
+   * Note that the list can be _smaller_ than the corresponding list of public
+   * keys. In that case, the wallet can sign off only for a subset of the age
+   * groups.
+   *
+   * The list has been allocated via GNUNET_malloc.
+   */
+  struct TALER_AgeCommitmentPrivateKeyP *keys;
+};
+
+
+/**
+ * @brief Commitment and Proof for a maximum age
+ *
+ * Calling TALER_age_restriction_commit on an (maximum) age value returns this
+ * data structure.  It consists of the proof, which is used to create
+ * attestations for compatible minimum ages, and the commitment, which is used
+ * to verify the attestations and derived commitments.
+ *
+ * The hash value of the commitment is bound to a particular coin with age
+ * restriction.
+ */
+struct TALER_AgeCommitmentProof
+{
+  /**
+   * The commitment is used to verify a particular attestation.  Its hash value
+   * is bound to a particular coin with age restriction.  This structure is
+   * sent to the merchant in order to verify a particular attestation for a
+   * minimum age.
+   * In itself, it does not convey any information about the maximum age that
+   * went into the call to TALER_age_restriction_commit.
+   */
+  struct TALER_AgeCommitment commitment;
+
+  /**
+   * The proof is used to create an attestation for a (compatible) minimum age.
+   */
+  struct TALER_AgeProof proof;
+};
+
+
+/**
+ * @brief Generates a hash of the public keys in the age commitment.
+ *
+ * @param commitment the age commitment - one public key per age group
+ * @param[out] hash resulting hash
+ */
+void
+TALER_age_commitment_hash (
+  const struct TALER_AgeCommitment *commitment,
+  struct TALER_AgeCommitmentHash *hash);
+
+
+/**
+ * @brief Generates an age commitent for the given age.
+ *
+ * @param mask The age mask the defines the age groups
+ * @param age The actual age for which an age commitment is generated
+ * @param seed The seed that goes into the key generation.  MUST be chosen 
uniformly random.
+ * @param[out] comm_proof The generated age commitment, ->priv and ->pub 
allocated via GNUNET_malloc() on success
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_commit (
+  const struct TALER_AgeMask *mask,
+  uint8_t age,
+  const struct GNUNET_HashCode *seed,
+  struct TALER_AgeCommitmentProof *comm_proof);
+
+
+/**
+ * @brief Derives another, equivalent age commitment for a given one.
+ *
+ * @param orig Original age commitment
+ * @param salt Salt to randomly move the points on the elliptic curve in order 
to generate another, equivalent commitment.
+ * @param[out] derived The resulting age commitment, ->priv and ->pub 
allocated via GNUNET_malloc() on success.
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_derive (
+  const struct TALER_AgeCommitmentProof *orig,
+  const struct GNUNET_HashCode *salt,
+  struct TALER_AgeCommitmentProof *derived);
+
+
+/**
+ * @brief Provide attestation for a given age, from a given age commitment, if 
possible.
+ *
+ * @param comm_proof The age commitment to be used for attestation.  For 
successful attestation, it must contain the private key for the corresponding 
age group.
+ * @param age Age (not age group) for which the an attestation should be done
+ * @param[out] attest Signature of the age with the appropriate key from the 
age commitment for the corresponding age group, if applicaple.
+ * @return #GNUNET_OK on success, #GNUNET_NO when no attestation can be made 
for that age with the given commitment, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_attest (
+  const struct TALER_AgeCommitmentProof *comm_proof,
+  uint8_t age,
+  struct TALER_AgeAttestation *attest);
+
+
+/**
+ * @brief Verify the attestation for an given age and age commitment
+ *
+ * @param commitment The age commitment that went into the attestation.  Only 
the public keys are needed.
+ * @param age Age (not age group) for which the an attestation should be done
+ * @param attest Signature of the age with the appropriate key from the age 
commitment for the corresponding age group, if applicaple.
+ * @return #GNUNET_OK when the attestation was successful, #GNUNET_NO no 
attestation couldn't be verified, #GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_commitment_verify (
+  const struct TALER_AgeCommitment *commitment,
+  uint8_t age,
+  const struct TALER_AgeAttestation *attest);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeCommitment
+ *
+ * @param ac the commitment from which all memory should be freed.
+ */
+void
+TALER_age_commitment_free (
+  struct TALER_AgeCommitment *ac);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeProof
+ *
+ * @param ap the proof of commitment from which all memory should be freed.
+ */
+void
+TALER_age_proof_free (
+  struct TALER_AgeProof *ap);
+
+
+/**
+ * @brief helper function to free memory of a struct TALER_AgeCommitmentProof
+ *
+ * @param acp the commitment and its proof from which all memory should be 
freed.
+ */
+void
+TALER_age_commitment_proof_free (
+  struct TALER_AgeCommitmentProof *acp);
+
+
+/**
+ * @brief helper function to allocate and copy a struct 
TALER_AgeCommitmentProof
+ *
+ * @param[in] acp The original age commitment proof
+ * @return The deep copy of @e acp, allocated
+ */
+struct TALER_AgeCommitmentProof *
+TALER_age_commitment_proof_duplicate (
+  const struct TALER_AgeCommitmentProof *acp);
+
+/**
+ * @brief helper function to copy a struct TALER_AgeCommitmentProof
+ *
+ * @param[in] acp The original age commitment proof
+ * @param[out] nacp The struct to copy the data into, with freshly allocated 
and copied keys.
+ */
+void
+TALER_age_commitment_proof_deep_copy (
+  const struct TALER_AgeCommitmentProof *acp,
+  struct TALER_AgeCommitmentProof *nacp);
+
+/**
+ * @brief For age-withdraw, clients have to prove that the public keys for all
+ * age groups larger than the allowed maximum age group are derived by scalar
+ * multiplication from this Edx25519 public key (in Crockford Base32 encoding):
+ *
+ *       DZJRF6HXN520505XDAWM8NMH36QV9J3VH77265WQ09EBQ76QSKCG
+ *
+ * Its private key was chosen randomly and then deleted.
+ */
+extern struct
+#ifndef AGE_RESTRICTION_WITH_ECDSA
+GNUNET_CRYPTO_Edx25519PublicKey
+#else
+GNUNET_CRYPTO_EcdsaPublicKey
+#endif
+TALER_age_commitment_base_public_key;
+
+/**
+ * @brief Similar to TALER_age_restriction_commit, but takes the coin's
+ * private key as seed input and calculates the public keys in the slots larger
+ * than the given age as derived from TALER_age_commitment_base_public_key.
+ *
+ * See 
https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction
+ *
+ * @param secret The master secret of the coin from which we derive the age 
restriction
+ * @param mask The age mask, defining the age groups
+ * @param max_age The maximum age for this coin.
+ * @param[out] comm_proof The commitment and proof for age restriction for age 
@a max_age
+ */
+enum GNUNET_GenericReturnValue
+TALER_age_restriction_from_secret (
+  const struct TALER_PlanchetMasterSecretP *secret,
+  const struct TALER_AgeMask *mask,
+  const uint8_t max_age,
+  struct TALER_AgeCommitmentProof *comm_proof);
+
+
+/**
+ * Group of Denominations.  These are the common fields of an array of
+ * denominations.
+ *
+ * The corresponding JSON-blob will also contain an array of particular
+ * denominations with only the timestamps, cipher-specific public key and the
+ * master signature.
+ */
+struct TALER_DenominationGroup
+{
+
+  /**
+   * XOR of all SHA-512 hashes of the public keys in this
+   * group.
+   */
+  struct GNUNET_HashCode hash;
+
+  /**
+   * Value of coins in this denomination group.
+   */
+  struct TALER_Amount value;
+
+  /**
+   * Fee structure for all coins in the group.
+   */
+  struct TALER_DenomFeeSet fees;
+
+  /**
+   * Cipher used for the denomination.
+   */
+  enum TALER_DenominationCipher cipher;
+
+  /**
+   * Age mask for the denomiation.
+   */
+  struct TALER_AgeMask age_mask;
+
+};
+
+
+/**
+ * Compute a unique key for the meta data of a denomination group.
+ *
+ * @param dg denomination group to evaluate
+ * @param[out] key key to set
+ */
+void
+TALER_denomination_group_get_key (
+  const struct TALER_DenominationGroup *dg,
+  struct GNUNET_HashCode *key);
+
+
+#endif
diff --git a/src/include/taler_exchange_service.h 
b/src/include/taler_exchange_service.h
new file mode 100644
index 0000000..31f41e1
--- /dev/null
+++ b/src/include/taler_exchange_service.h
@@ -0,0 +1,7261 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2014-2023 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU Affero General Public License as published by the Free 
Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more 
details.
+
+   You should have received a copy of the GNU Affero General Public License 
along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file include/taler_exchange_service.h
+ * @brief C interface of libtalerexchange, a C library to use exchange's HTTP 
API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#ifndef _TALER_EXCHANGE_SERVICE_H
+#define _TALER_EXCHANGE_SERVICE_H
+
+#include <jansson.h>
+#include "taler_util.h"
+#include "taler_error_codes.h"
+#include "taler_kyclogic_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/* *********************  /keys *********************** */
+
+
+/**
+ * @brief Exchange's signature key
+ */
+struct TALER_EXCHANGE_SigningPublicKey
+{
+  /**
+   * The signing public key
+   */
+  struct TALER_ExchangePublicKeyP key;
+
+  /**
+   * Signature over this signing key by the exchange's master signature.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * Validity start time
+   */
+  struct GNUNET_TIME_Timestamp valid_from;
+
+  /**
+   * Validity expiration time (how long the exchange may use it).
+   */
+  struct GNUNET_TIME_Timestamp valid_until;
+
+  /**
+   * Validity expiration time for legal disputes.
+   */
+  struct GNUNET_TIME_Timestamp valid_legal;
+};
+
+
+/**
+ * @brief Public information about a exchange's denomination key
+ */
+struct TALER_EXCHANGE_DenomPublicKey
+{
+  /**
+   * The public key
+   */
+  struct TALER_DenominationPublicKey key;
+
+  /**
+   * The hash of the public key.
+   */
+  struct TALER_DenominationHashP h_key;
+
+  /**
+   * Exchange's master signature over this denomination record.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * Timestamp indicating when the denomination key becomes valid
+   */
+  struct GNUNET_TIME_Timestamp valid_from;
+
+  /**
+   * Timestamp indicating when the denomination key can’t be used anymore to
+   * withdraw new coins.
+   */
+  struct GNUNET_TIME_Timestamp withdraw_valid_until;
+
+  /**
+   * Timestamp indicating when coins of this denomination become invalid.
+   */
+  struct GNUNET_TIME_Timestamp expire_deposit;
+
+  /**
+   * When do signatures with this denomination key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_deposit (by a year or more).
+   */
+  struct GNUNET_TIME_Timestamp expire_legal;
+
+  /**
+   * The value of this denomination
+   */
+  struct TALER_Amount value;
+
+  /**
+   * The applicable fees for this denomination
+   */
+  struct TALER_DenomFeeSet fees;
+
+  /**
+   * Set to true if the private denomination key has been
+   * lost by the exchange and thus the key cannot be
+   * used for withdrawing at this time.
+   */
+  bool lost;
+
+  /**
+   * Set to true if this denomination key has been
+   * revoked by the exchange.
+   */
+  bool revoked;
+
+};
+
+
+/**
+ * Information we track per denomination audited by the auditor.
+ */
+struct TALER_EXCHANGE_AuditorDenominationInfo
+{
+
+  /**
+   * Signature by the auditor affirming that it is monitoring this
+   * denomination.
+   */
+  struct TALER_AuditorSignatureP auditor_sig;
+
+  /**
+   * Offsets into the key's main `denom_keys` array identifying the
+   * denomination being audited by this auditor.
+   */
+  unsigned int denom_key_offset;
+
+};
+
+
+/**
+ * @brief Information we get from the exchange about auditors.
+ */
+struct TALER_EXCHANGE_AuditorInformation
+{
+  /**
+   * Public key of the auditing institution.  Wallets and merchants
+   * are expected to be configured with a set of public keys of
+   * auditors that they deem acceptable.  These public keys are
+   * the roots of the Taler PKI.
+   */
+  struct TALER_AuditorPublicKeyP auditor_pub;
+
+  /**
+   * URL of the auditing institution.  Signed by the auditor's public
+   * key, this URL is a place where applications can direct users for
+   * additional information about the auditor.  In the future, there
+   * should also be an auditor API for automated submission about
+   * claims of misbehaving exchange providers.
+   */
+  char *auditor_url;
+
+  /**
+   * Array of length @a num_denom_keys with the denomination
+   * keys audited by this auditor.
+   */
+  struct TALER_EXCHANGE_AuditorDenominationInfo *denom_keys;
+
+  /**
+   * Number of denomination keys audited by this auditor.
+   */
+  unsigned int num_denom_keys;
+};
+
+
+/**
+ * Global fees and options of an exchange for a given time period.
+ */
+struct TALER_EXCHANGE_GlobalFee
+{
+
+  /**
+   * Signature affirming all of the data.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+  /**
+   * Starting time of the validity period (inclusive).
+   */
+  struct GNUNET_TIME_Timestamp start_date;
+
+  /**
+   * End time of the validity period (exclusive).
+   */
+  struct GNUNET_TIME_Timestamp end_date;
+
+  /**
+   * Unmerged purses will be timed out after at most this time.
+   */
+  struct GNUNET_TIME_Relative purse_timeout;
+
+  /**
+   * Account history is limited to this timeframe.
+   */
+  struct GNUNET_TIME_Relative history_expiration;
+
+  /**
+   * Fees that apply globally, independent of denomination
+   * and wire method.
+   */
+  struct TALER_GlobalFeeSet fees;
+
+  /**
+   * Number of free purses per account.
+   */
+  uint32_t purse_account_limit;
+};
+
+
+/**
+ * List sorted by @a start_date with fees to be paid for aggregate wire 
transfers.
+ */
+struct TALER_EXCHANGE_WireAggregateFees
+{
+  /**
+   * This is a linked list.
+   */
+  struct TALER_EXCHANGE_WireAggregateFees *next;
+
+  /**
+   * Fee to be paid whenever the exchange wires funds to the merchant.
+   */
+  struct TALER_WireFeeSet fees;
+
+  /**
+   * Time when this fee goes into effect (inclusive)
+   */
+  struct GNUNET_TIME_Timestamp start_date;
+
+  /**
+   * Time when this fee stops being in effect (exclusive).
+   */
+  struct GNUNET_TIME_Timestamp end_date;
+
+  /**
+   * Signature affirming the above fee structure.
+   */
+  struct TALER_MasterSignatureP master_sig;
+};
+
+
+/**
+ * Information about wire fees by wire method.
+ */
+struct TALER_EXCHANGE_WireFeesByMethod
+{
+  /**
+   * Wire method with the given @e fees.
+   */
+  char *method;
+
+  /**
+   * Linked list of wire fees the exchange charges for
+   * accounts of the wire @e method.
+   */
+  struct TALER_EXCHANGE_WireAggregateFees *fees_head;
+
+};
+
+
+/**
+ * Type of an account restriction.
+ */
+enum TALER_EXCHANGE_AccountRestrictionType
+{
+  /**
+   * Invalid restriction.
+   */
+  TALER_EXCHANGE_AR_INVALID = 0,
+
+  /**
+   * Account must not be used for this operation.
+   */
+  TALER_EXCHANGE_AR_DENY = 1,
+
+  /**
+   * Other account must match given regular expression.
+   */
+  TALER_EXCHANGE_AR_REGEX = 2
+};
+
+/**
+ * Restrictions that apply to using a given exchange bank account.
+ */
+struct TALER_EXCHANGE_AccountRestriction
+{
+
+  /**
+   * Type of the account restriction.
+   */
+  enum TALER_EXCHANGE_AccountRestrictionType type;
+
+  /**
+   * Restriction details depending on @e type.
+   */
+  union
+  {
+    /**
+     * Details if type is #TALER_EXCHANGE_AR_REGEX.
+     */
+    struct
+    {
+      /**
+       * Regular expression that the payto://-URI of the partner account must
+       * follow.  The regular expression should follow posix-egrep, but
+       * without support for character classes, GNU extensions,
+       * back-references or intervals. See
+       * 
https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html
+       * for a description of the posix-egrep syntax. Applications may support
+       * regexes with additional features, but exchanges must not use such
+       * regexes.
+       */
+      char *posix_egrep;
+
+      /**
+       * Hint for a human to understand the restriction.
+       */
+      char *human_hint;
+
+      /**
+       * Internationalizations for the @e human_hint.  Map from IETF BCP 47
+       * language tax to localized human hints.
+       */
+      json_t *human_hint_i18n;
+    } regex;
+  } details;
+
+};
+
+
+/**
+ * Information about a wire account of the exchange.
+ */
+struct TALER_EXCHANGE_WireAccount
+{
+  /**
+   * payto://-URI of the exchange.
+   */
+  char *payto_uri;
+
+  /**
+   * URL of a conversion service in case using this account is subject to
+   * currency conversion.  NULL for no conversion needed.
+   */
+  char *conversion_url;
+
+  /**
+   * Array of restrictions that apply when crediting
+   * this account.
+   */
+  struct TALER_EXCHANGE_AccountRestriction *credit_restrictions;
+
+  /**
+   * Array of restrictions that apply when debiting
+   * this account.
+   */
+  struct TALER_EXCHANGE_AccountRestriction *debit_restrictions;
+
+  /**
+   * Length of the @e credit_restrictions array.
+   */
+  unsigned int credit_restrictions_length;
+
+  /**
+   * Length of the @e debit_restrictions array.
+   */
+  unsigned int debit_restrictions_length;
+
+  /**
+   * Signature of the exchange over the account (was checked by the API).
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+};
+
+
+/**
+ * @brief Information about keys from the exchange.
+ */
+struct TALER_EXCHANGE_Keys
+{
+
+  /**
+   * Long-term offline signing key of the exchange.
+   */
+  struct TALER_MasterPublicKeyP master_pub;
+
+  /**
+   * Signature over extension configuration data, if any.
+   */
+  struct TALER_MasterSignatureP extensions_sig;
+
+  /**
+   * Array of the exchange's online signing keys.
+   */
+  struct TALER_EXCHANGE_SigningPublicKey *sign_keys;
+
+  /**
+   * Array of the exchange's denomination keys.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey *denom_keys;
+
+  /**
+   * Array of the keys of the auditors of the exchange.
+   */
+  struct TALER_EXCHANGE_AuditorInformation *auditors;
+
+  /**
+   * Array with the global fees of the exchange.
+   */
+  struct TALER_EXCHANGE_GlobalFee *global_fees;
+
+  /**
+   * Configuration data for extensions.
+   */
+  json_t *extensions;
+
+  /**
+   * Supported Taler protocol version by the exchange.
+   * String in the format current:revision:age using the
+   * semantics of GNU libtool.  See
+   * 
https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+   */
+  char *version;
+
+  /**
+   * Supported currency of the exchange.
+   */
+  char *currency;
+
+  /**
+   * What is the base URL of the exchange that returned
+   * these keys?
+   */
+  char *exchange_url;
+
+  /**
+   * Asset type used by the exchange. Typical values
+   * are "fiat" or "crypto" or "regional" or "stock".
+   * Wallets should adjust their UI/UX based on this
+   * value.
+   */
+  char *asset_type;
+
+  /**
+   * Array of amounts a wallet is allowed to hold from
+   * this exchange before it must undergo further KYC checks.
+   */
+  struct TALER_Amount *wallet_balance_limit_without_kyc;
+
+  /**
+   * Array of accounts of the exchange.
+   */
+  struct TALER_EXCHANGE_WireAccount *accounts;
+
+  /**
+   * Array of wire fees by wire method.
+   */
+  struct TALER_EXCHANGE_WireFeesByMethod *fees;
+
+  /**
+   * How long after a reserve went idle will the exchange close it?
+   * This is an approximate number, not cryptographically signed by
+   * the exchange (advisory-only, may change anytime).
+   */
+  struct GNUNET_TIME_Relative reserve_closing_delay;
+
+  /**
+   * Timestamp indicating the /keys generation.
+   */
+  struct GNUNET_TIME_Timestamp list_issue_date;
+
+  /**
+   * When does this keys data expire?
+   */
+  struct GNUNET_TIME_Timestamp key_data_expiration;
+
+  /**
+   * Timestamp indicating the creation time of the last
+   * denomination key in /keys.
+   * Used to fetch /keys incrementally.
+   */
+  struct GNUNET_TIME_Timestamp last_denom_issue_date;
+
+  /**
+   * If age restriction is enabled on the exchange, we get an non-zero age_mask
+   */
+  struct TALER_AgeMask age_mask;
+
+  /**
+   * Absolute STEFAN parameter.
+   */
+  struct TALER_Amount stefan_abs;
+
+  /**
+   * Logarithmic STEFAN parameter.
+   */
+  struct TALER_Amount stefan_log;
+
+  /**
+   * Linear STEFAN parameter.
+   */
+  struct TALER_Amount stefan_lin;
+
+  /**
+   * Default number of fractional digits to render
+   * amounts with.
+   */
+  uint32_t currency_fraction_digits;
+
+  /**
+   * Length of @e accounts array.
+   */
+  unsigned int accounts_len;
+
+  /**
+   * Length of @e fees array.
+   */
+  unsigned int fees_len;
+
+  /**
+   * Length of the @e wallet_balance_limit_without_kyc
+   * array.
+   */
+  unsigned int wblwk_length;
+
+  /**
+   * Length of the @e global_fees array.
+   */
+  unsigned int num_global_fees;
+
+  /**
+   * Length of the @e sign_keys array (number of valid entries).
+   */
+  unsigned int num_sign_keys;
+
+  /**
+   * Length of the @e denom_keys array.
+   */
+  unsigned int num_denom_keys;
+
+  /**
+   * Length of the @e auditors array.
+   */
+  unsigned int num_auditors;
+
+  /**
+   * Actual length of the @e auditors array (size of allocation).
+   */
+  unsigned int auditors_size;
+
+  /**
+   * Actual length of the @e denom_keys array (size of allocation).
+   */
+  unsigned int denom_keys_size;
+
+  /**
+   * Reference counter for this structure.
+   * Freed when it reaches 0.
+   */
+  unsigned int rc;
+
+  /**
+   * Set to true if rewards are allowed at this exchange.
+   */
+  bool rewards_allowed;
+};
+
+
+/**
+ * How compatible are the protocol version of the exchange and this
+ * client?  The bits (1,2,4) can be used to test if the exchange's
+ * version is incompatible, older or newer respectively.
+ */
+enum TALER_EXCHANGE_VersionCompatibility
+{
+
+  /**
+   * The exchange runs exactly the same protocol version.
+   */
+  TALER_EXCHANGE_VC_MATCH = 0,
+
+  /**
+   * The exchange is too old or too new to be compatible with this
+   * implementation (bit)
+   */
+  TALER_EXCHANGE_VC_INCOMPATIBLE = 1,
+
+  /**
+   * The exchange is older than this implementation (bit)
+   */
+  TALER_EXCHANGE_VC_OLDER = 2,
+
+  /**
+   * The exchange is too old to be compatible with
+   * this implementation.
+   */
+  TALER_EXCHANGE_VC_INCOMPATIBLE_OUTDATED
+    = TALER_EXCHANGE_VC_INCOMPATIBLE
+      | TALER_EXCHANGE_VC_OLDER,
+
+  /**
+   * The exchange is more recent than this implementation (bit).
+   */
+  TALER_EXCHANGE_VC_NEWER = 4,
+
+  /**
+   * The exchange is too recent for this implementation.
+   */
+  TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER
+    = TALER_EXCHANGE_VC_INCOMPATIBLE
+      | TALER_EXCHANGE_VC_NEWER,
+
+  /**
+   * We could not even parse the version data.
+   */
+  TALER_EXCHANGE_VC_PROTOCOL_ERROR = 8
+
+};
+
+
+/**
+ * General information about the HTTP response we obtained
+ * from the exchange for a request.
+ */
+struct TALER_EXCHANGE_HttpResponse
+{
+
+  /**
+   * The complete JSON reply. NULL if we failed to parse the
+   * reply (too big, invalid JSON).
+   */
+  const json_t *reply;
+
+  /**
+   * Set to the human-readable 'hint' that is optionally
+   * provided by the exchange together with errors. NULL
+   * if no hint was provided or if there was no error.
+   */
+  const char *hint;
+
+  /**
+   * HTTP status code for the response.  0 if the
+   * HTTP request failed and we did not get any answer, or
+   * if the answer was invalid and we set @a ec to a
+   * client-side error code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Taler error code.  #TALER_EC_NONE if everything was
+   * OK.  Usually set to the "code" field of an error
+   * response, but may be set to values created at the
+   * client side, for example when the response was
+   * not in JSON format or was otherwise ill-formed.
+   */
+  enum TALER_ErrorCode ec;
+
+};
+
+
+/**
+ * Response from /keys.
+ */
+struct TALER_EXCHANGE_KeysResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP status code.
+   */
+  union
+  {
+
+    /**
+     * Details on #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Information about the various keys used by the exchange.
+       */
+      const struct TALER_EXCHANGE_Keys *keys;
+
+      /**
+       * Protocol compatibility information
+       */
+      enum TALER_EXCHANGE_VersionCompatibility compat;
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ * The ownership over the @a keys object is passed to
+ * the callee, thus it is given explicitly and not
+ * (only) via @a kr.
+ *
+ * @param cls closure
+ * @param kr response from /keys
+ * @param[in] keys keys object passed to callback with
+ *  reference counter of 1. Must be freed by callee
+ *  using #TALER_EXCHANGE_keys_decref(). NULL on failure.
+ */
+typedef void
+(*TALER_EXCHANGE_GetKeysCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_KeysResponse *kr,
+  struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
+ * @brief Handle for a GET /keys request.
+ */
+struct TALER_EXCHANGE_GetKeysHandle;
+
+
+/**
+ * Fetch the main /keys resources from an exchange.  Does an incremental
+ * fetch if @a last_keys is given.  The obtained information will be passed to
+ * the @a cert_cb (possibly after first merging it with @a last_keys to
+ * produce a full picture; expired keys (for deposit) will be removed from @a
+ * last_keys if there are any).
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param[in,out] last_keys previous keys object, NULL for none
+ * @param cert_cb function to call with the exchange's certification 
information,
+ *                possibly called repeatedly if the information changes
+ * @param cert_cb_cls closure for @a cert_cb
+ * @return the exchange handle; NULL upon error
+ */
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *last_keys,
+  TALER_EXCHANGE_GetKeysCallback cert_cb,
+  void *cert_cb_cls);
+
+
+/**
+ * Serialize the latest data from @a keys to be persisted
+ * (for example, to be used as @a last_keys later).
+ *
+ * @param kd the key data to serialize
+ * @return NULL on error; otherwise JSON object owned by the caller
+ */
+json_t *
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd);
+
+
+/**
+ * Deserialize keys data stored in @a j.
+ *
+ * @param j JSON keys data previously returned from 
#TALER_EXCHANGE_keys_to_json()
+ * @return NULL on error (i.e. invalid JSON); otherwise
+ *         keys object with reference counter 1 owned by the caller
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_from_json (const json_t *j);
+
+
+/**
+ * Cancel GET /keys operation.
+ *
+ * @param[in] gkh the GET /keys handle
+ */
+void
+TALER_EXCHANGE_get_keys_cancel (struct TALER_EXCHANGE_GetKeysHandle *gkh);
+
+
+/**
+ * Increment reference counter for @a keys
+ *
+ * @param[in,out] keys object to increment reference counter for
+ * @return keys, with incremented reference counter
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
+ * Deccrement reference counter for @a keys.
+ * Frees @a keys if reference counter becomes zero.
+ *
+ * @param[in,out] keys object to decrement reference counter for
+ */
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys);
+
+
+/**
+ * Use STEFAN curve in @a keys to convert @a brut to @a net.  Computes the
+ * expected minimum (!) @a net amount that should for sure arrive in the
+ * target amount at cost of @a brut to the wallet. Note that STEFAN curves by
+ * design over-estimate actual fees and a wallet may be able to achieve the
+ * same @a net amount with less fees --- or if the available coins are
+ * abnormal in structure, it may take more.
+ *
+ * @param keys exchange key data
+ * @param brut gross amount (actual cost including fees)
+ * @param[out] net net amount (effective amount)
+ * @return #GNUNET_OK on success, #GNUNET_NO if the
+ *   resulting @a net is zero (or lower)
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_b2n (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_Amount *brut,
+  struct TALER_Amount *net);
+
+
+/**
+ * Use STEFAN curve in @a keys to convert @a net to @a brut.  Computes the
+ * expected maximum (!) @a brut amount that should be needed in the wallet to
+ * transfer @a net amount to the target account.  Note that STEFAN curves by
+ * design over-estimate actual fees and a wallet may be able to achieve the
+ * same @a net amount with less fees --- or if the available coins are
+ * abnormal in structure, it may take more.
+ *
+ * @param keys exchange key data
+ * @param net net amount (effective amount)
+ * @param[out] brut gross amount (actual cost including fees)
+ * @return #GNUNET_OK on success, #GNUNET_NO if the
+ *   resulting @a brut is zero (only if @a net was zero)
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_keys_stefan_n2b (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_Amount *net,
+  struct TALER_Amount *brut);
+
+
+/**
+ * Round brutto or netto value computed via STEFAN
+ * curve to decimal places commonly used at the exchange.
+ *
+ * @param keys exchange keys response data
+ * @param[in,out] val value to round
+ */
+void
+TALER_EXCHANGE_keys_stefan_round (
+  const struct TALER_EXCHANGE_Keys *keys,
+  struct TALER_Amount *val);
+
+
+/**
+ * Test if the given @a pub is a the current signing key from the exchange
+ * according to @a keys.
+ *
+ * @param keys the exchange's key set
+ * @param pub claimed current online signing key for the exchange
+ * @return #GNUNET_OK if @a pub is (according to /keys) a current signing key
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_test_signing_key (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ExchangePublicKeyP *pub);
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param pk public key of the denomination to lookup
+ * @return details about the given denomination key, NULL if the key is not
+ * found
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_DenominationPublicKey *pk);
+
+
+/**
+ * Obtain the global fee details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param ts time for when to fetch the fees
+ * @return details about the fees, NULL if no fees are known at @a ts
+ */
+const struct TALER_EXCHANGE_GlobalFee *
+TALER_EXCHANGE_get_global_fee (
+  const struct TALER_EXCHANGE_Keys *keys,
+  struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Create a copy of a denomination public key.
+ *
+ * @param key key to copy
+ * @returns a copy, must be freed with 
#TALER_EXCHANGE_destroy_denomination_key()
+ * @deprecated
+ */
+struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_copy_denomination_key (
+  const struct TALER_EXCHANGE_DenomPublicKey *key);
+
+
+/**
+ * Destroy a denomination public key.
+ * Should only be called with keys created by 
#TALER_EXCHANGE_copy_denomination_key().
+ *
+ * @param key key to destroy.
+ * @deprecated
+ */
+void
+TALER_EXCHANGE_destroy_denomination_key (
+  struct TALER_EXCHANGE_DenomPublicKey *key);
+
+
+/**
+ * Obtain the denomination key details from the exchange.
+ *
+ * @param keys the exchange's key set
+ * @param hc hash of the public key of the denomination to lookup
+ * @return details about the given denomination key
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key_by_hash (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_DenominationHashP *hc);
+
+
+/**
+ * Obtain meta data about an exchange (online) signing
+ * key.
+ *
+ * @param keys from where to obtain the meta data
+ * @param exchange_pub public key to lookup
+ * @return NULL on error (@a exchange_pub not known)
+ */
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_signing_key_info (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/* *********************  wire helpers *********************** */
+
+
+/**
+ * Parse array of @a accounts of the exchange into @a was.
+ *
+ * @param master_pub master public key of the exchange, NULL to not verify 
signatures
+ * @param accounts array of accounts to parse
+ * @param[out] was where to write the result (already allocated)
+ * @param was_length length of the @a was array, must match the length of @a 
accounts
+ * @return #GNUNET_OK if parsing @a accounts succeeded
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_accounts (
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const json_t *accounts,
+  unsigned int was_length,
+  struct TALER_EXCHANGE_WireAccount was[static was_length]);
+
+
+/**
+ * Free data within @a was, but not @a was itself.
+ *
+ * @param was array of wire account data
+ * @param was_len length of the @a was array
+ */
+void
+TALER_EXCHANGE_free_accounts (
+  unsigned int was_len,
+  struct TALER_EXCHANGE_WireAccount was[static was_len]);
+
+
+/* *********************  /coins/$COIN_PUB/deposit *********************** */
+
+
+/**
+ * Information needed for a coin to be deposited.
+ */
+struct TALER_EXCHANGE_CoinDepositDetail
+{
+
+  /**
+   * The amount to be deposited.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Hash over the age commitment of the coin.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * The coin’s public key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * The signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made
+   * by the customer with the coin’s private key.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Exchange’s unblinded signature of the coin.
+   */
+  struct TALER_DenominationSignature denom_sig;
+
+  /**
+   * Hash of the public key of the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+};
+
+
+/**
+ * Meta information about the contract relevant for a coin's deposit
+ * operation.
+ */
+struct TALER_EXCHANGE_DepositContractDetail
+{
+
+  /**
+   * Hash of the contact of the merchant with the customer (further details
+   * are never disclosed to the exchange)
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * The public key of the merchant (used to identify the merchant for refund
+   * requests).
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Salt used to hash the @e merchant_payto_uri.
+   */
+  struct TALER_WireSaltP wire_salt;
+
+  /**
+   * Hash over data provided by the wallet to customize the contract.
+   * All zero if not used.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+  /**
+   * Date until which the merchant can issue a refund to the customer via the
+   * exchange (can be zero if refunds are not allowed); must not be after the
+   * @e wire_deadline.
+   */
+  struct GNUNET_TIME_Timestamp refund_deadline;
+
+  /**
+   * Execution date, until which the merchant would like the exchange to
+   * settle the balance (advisory, the exchange cannot be forced to settle in
+   * the past or upon very short notice, but of course a well-behaved exchange
+   * will limit aggregation based on the advice received).
+   */
+  struct GNUNET_TIME_Timestamp wire_deadline;
+
+  /**
+   * Timestamp when the contract was finalized, must match approximately the
+   * current time of the exchange.
+   */
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+  /**
+   * The merchant’s account details, in the payto://-format supported by the
+   * exchange.
+   */
+  const char *merchant_payto_uri;
+
+  /**
+   * Policy extension specific details about the deposit relevant to the 
exchange.
+   */
+  const json_t *policy_details;
+
+};
+
+
+/**
+ * @brief A Batch Deposit Handle
+ */
+struct TALER_EXCHANGE_BatchDepositHandle;
+
+
+/**
+ * Structure with information about a batch deposit
+ * operation's result.
+ */
+struct TALER_EXCHANGE_BatchDepositResult
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  union
+  {
+
+    /**
+     * Information returned if the HTTP status is
+     * #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Time when the exchange generated the batch deposit confirmation
+       */
+      struct GNUNET_TIME_Timestamp deposit_timestamp;
+
+      /**
+       * Array of signatures provided by the exchange
+       */
+      const struct TALER_ExchangeSignatureP *exchange_sigs;
+
+      /**
+       * exchange key used to sign @a exchange_sig.
+       */
+      const struct TALER_ExchangePublicKeyP *exchange_pub;
+
+      /**
+       * Base URL for looking up wire transfers, or
+       * NULL to use the default base URL.
+       */
+      const char *transaction_base_url;
+
+      /**
+       * Length of the @e exchange_sigs array.
+       */
+      unsigned int num_signatures;
+
+    } ok;
+
+    /**
+     * Information returned if the HTTP status is
+     * #MHD_HTTP_CONFLICT.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } conflict;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * deposit permission request to a exchange.
+ *
+ * @param cls closure
+ * @param dr deposit response details
+ */
+typedef void
+(*TALER_EXCHANGE_BatchDepositResultCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_BatchDepositResult *dr);
+
+
+/**
+ * Submit a batch of deposit permissions to the exchange and get the
+ * exchange's response.  This API is typically used by a merchant.  Note that
+ * while we return the response verbatim to the caller for further processing,
+ * we do already verify that the response is well-formed (i.e. that signatures
+ * included in the response are all valid).  If the exchange's reply is not
+ * well-formed, we return an HTTP status code of zero to @a cb.
+ *
+ * We also verify that the @a cdds.coin_sig are valid for this deposit
+ * request, and that the @a cdds.ub_sig are a valid signatures for @a
+ * coin_pub.  Also, the @a exchange must be ready to operate (i.e.  have
+ * finished processing the /keys reply).  If either check fails, we do
+ * NOT initiate the transaction with the exchange and instead return NULL.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param dcd details about the contract the deposit is for
+ * @param num_cdds length of the @a cdds array
+ * @param cdds array with details about the coins to be deposited
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @param[out] ec if NULL is returned, set to the error code explaining why 
the operation failed
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_BatchDepositHandle *
+TALER_EXCHANGE_batch_deposit (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+  unsigned int num_cdds,
+  const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
+  TALER_EXCHANGE_BatchDepositResultCallback cb,
+  void *cb_cls,
+  enum TALER_ErrorCode *ec);
+
+
+/**
+ * Change the chance that our deposit confirmation will be given to the
+ * auditor to 100%.
+ *
+ * @param[in,out] deposit the batch deposit permission request handle
+ */
+void
+TALER_EXCHANGE_batch_deposit_force_dc (
+  struct TALER_EXCHANGE_BatchDepositHandle *deposit);
+
+
+/**
+ * Cancel a batch deposit permission request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param[in] deposit the deposit permission request handle
+ */
+void
+TALER_EXCHANGE_batch_deposit_cancel (
+  struct TALER_EXCHANGE_BatchDepositHandle *deposit);
+
+
+/* *********************  /coins/$COIN_PUB/refund *********************** */
+
+/**
+ * @brief A Refund Handle
+ */
+struct TALER_EXCHANGE_RefundHandle;
+
+/**
+ * Response from the /refund API.
+ */
+struct TALER_EXCHANGE_RefundResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status code.
+   */
+  union
+  {
+    /**
+     * Details on #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Exchange key used to sign.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * The actual signature
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+    } ok;
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * refund request to an exchange.
+ *
+ * @param cls closure
+ * @param rr refund response
+ */
+typedef void
+(*TALER_EXCHANGE_RefundCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_RefundResponse *rr);
+
+/**
+ * Submit a refund request to the exchange and get the exchange's response.
+ * This API is used by a merchant.  Note that while we return the response
+ * verbatim to the caller for further processing, we do already verify that
+ * the response is well-formed (i.e. that signatures included in the response
+ * are all valid).  If the exchange's reply is not well-formed, we return an
+ * HTTP status code of zero to @a cb.
+ *
+ * The @a exchange must be ready to operate (i.e.  have
+ * finished processing the /keys reply).  If this check fails, we do
+ * NOT initiate the transaction with the exchange and instead return NULL.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param amount the amount to be refunded; must be larger than the refund fee
+ *        (as that fee is still being subtracted), and smaller than the amount
+ *        (with deposit fee) of the original deposit contribution of this coin
+ * @param h_contract_terms hash of the contact of the merchant with the 
customer that is being refunded
+ * @param coin_pub coin’s public key of the coin from the original deposit 
operation
+ * @param rtransaction_id transaction id for the transaction between merchant 
and customer (of refunding operation);
+ *                        this is needed as we may first do a partial refund 
and later a full refund.  If both
+ *                        refunds are also over the same amount, we need the 
@a rtransaction_id to make the disjoint
+ *                        refund requests different (as requests are 
idempotent and otherwise the 2nd refund might not work).
+ * @param merchant_priv the private key of the merchant, used to generate 
signature for refund request
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_RefundHandle *
+TALER_EXCHANGE_refund (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_Amount *amount,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  uint64_t rtransaction_id,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  TALER_EXCHANGE_RefundCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a refund permission request.  This function cannot be used
+ * on a request handle if a response is already served for it.  If
+ * this function is called, the refund may or may not have happened.
+ * However, it is fine to try to refund the coin a second time.
+ *
+ * @param refund the refund request handle
+ */
+void
+TALER_EXCHANGE_refund_cancel (struct TALER_EXCHANGE_RefundHandle *refund);
+
+
+/* ********************* POST /csr-melt *********************** */
+
+
+/**
+ * @brief A /csr-melt Handle
+ */
+struct TALER_EXCHANGE_CsRMeltHandle;
+
+
+/**
+ * Details about a response for a CS R request.
+ */
+struct TALER_EXCHANGE_CsRMeltResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Length of the @e alg_values array.
+       */
+      unsigned int alg_values_len;
+
+      /**
+       * Values contributed by the exchange for the
+       * respective coin's withdraw operation.
+       */
+      const struct TALER_ExchangeWithdrawValues *alg_values;
+    } ok;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R request to a exchange.
+ *
+ * @param cls closure
+ * @param csrr response details
+ */
+typedef void
+(*TALER_EXCHANGE_CsRMeltCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRMeltResponse *csrr);
+
+
+/**
+ * Information we pass per coin to a /csr-melt request.
+ */
+struct TALER_EXCHANGE_NonceKey
+{
+  /**
+   * Which denomination key is the /csr-melt request for?
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+  /**
+   * What is number to derive the client nonce for the
+   * fresh coin?
+   */
+  uint32_t cnc_num;
+};
+
+
+/**
+ * Get a set of CS R values using a /csr-melt request.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param rms master key used for the derivation of the CS values
+ * @param nks_len length of the @a nks array
+ * @param nks array of denominations and nonces
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_CsRMeltHandle *
+TALER_EXCHANGE_csr_melt (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_RefreshMasterSecretP *rms,
+  unsigned int nks_len,
+  struct TALER_EXCHANGE_NonceKey nks[static nks_len],
+  TALER_EXCHANGE_CsRMeltCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ *
+ * Cancel a CS R melt request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param csrh the withdraw handle
+ */
+void
+TALER_EXCHANGE_csr_melt_cancel (struct TALER_EXCHANGE_CsRMeltHandle *csrh);
+
+
+/* ********************* POST /csr-withdraw *********************** */
+
+
+/**
+ * @brief A /csr-withdraw Handle
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle;
+
+
+/**
+ * Details about a response for a CS R request.
+ */
+struct TALER_EXCHANGE_CsRWithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Values contributed by the exchange for the
+       * respective coin's withdraw operation.
+       */
+      struct TALER_ExchangeWithdrawValues alg_values;
+    } ok;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * CS R withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param csrr response details
+ */
+typedef void
+(*TALER_EXCHANGE_CsRWithdrawCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr);
+
+
+/**
+ * Get a CS R using a /csr-withdraw request.
+ *
+ * @param curl_ctx The curl context to use for the requests
+ * @param exchange_url Base-URL to the excnange
+ * @param pk Which denomination key is the /csr request for
+ * @param nonce client nonce for the request
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for the above callback
+ * @return handle for the operation on success, NULL on error, i.e.
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_DenomPublicKey *pk,
+  const struct TALER_CsNonce *nonce,
+  TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ *
+ * Cancel a CS R withdraw request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param csrh the withdraw handle
+ */
+void
+TALER_EXCHANGE_csr_withdraw_cancel (
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh);
+
+
+/* ********************* GET /reserves/$RESERVE_PUB *********************** */
+
+/**
+ * Ways how a reserve's balance may change.
+ */
+enum TALER_EXCHANGE_ReserveTransactionType
+{
+
+  /**
+   * Deposit into the reserve.
+   */
+  TALER_EXCHANGE_RTT_CREDIT,
+
+  /**
+   * Withdrawal from the reserve.
+   */
+  TALER_EXCHANGE_RTT_WITHDRAWAL,
+
+  /**
+   * Age-Withdrawal from the reserve.
+   */
+  TALER_EXCHANGE_RTT_AGEWITHDRAWAL,
+
+  /**
+   * /recoup operation.
+   */
+  TALER_EXCHANGE_RTT_RECOUP,
+
+  /**
+   * Reserve closed operation.
+   */
+  TALER_EXCHANGE_RTT_CLOSING,
+
+  /**
+   * Reserve history request.
+   */
+  TALER_EXCHANGE_RTT_HISTORY,
+
+  /**
+   * Reserve purse merge operation.
+   */
+  TALER_EXCHANGE_RTT_MERGE,
+
+  /**
+   * Reserve open request operation.
+   */
+  TALER_EXCHANGE_RTT_OPEN,
+
+  /**
+   * Reserve close request operation.
+   */
+  TALER_EXCHANGE_RTT_CLOSE
+
+};
+
+
+/**
+ * @brief Entry in the reserve's transaction history.
+ */
+struct TALER_EXCHANGE_ReserveHistoryEntry
+{
+
+  /**
+   * Type of the transaction.
+   */
+  enum TALER_EXCHANGE_ReserveTransactionType type;
+
+  /**
+   * Amount transferred (in or out).
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Details depending on @e type.
+   */
+  union
+  {
+
+    /**
+     * Information about a deposit that filled this reserve.
+     * @e type is #TALER_EXCHANGE_RTT_CREDIT.
+     */
+    struct
+    {
+      /**
+       * Sender account payto://-URL of the incoming transfer.
+       */
+      char *sender_url;
+
+      /**
+       * Information that uniquely identifies the wire transfer.
+       */
+      uint64_t wire_reference;
+
+      /**
+       * When did the wire transfer happen?
+       */
+      struct GNUNET_TIME_Timestamp timestamp;
+
+    } in_details;
+
+    /**
+     * Information about withdraw operation.
+     * @e type is #TALER_EXCHANGE_RTT_WITHDRAWAL.
+     */
+    struct
+    {
+      /**
+       * Signature authorizing the withdrawal for outgoing transaction.
+       */
+      json_t *out_authorization_sig;
+
+      /**
+       * Fee that was charged for the withdrawal.
+       */
+      struct TALER_Amount fee;
+    } withdraw;
+
+    /**
+     * Information about withdraw operation.
+     * @e type is #TALER_EXCHANGE_RTT_AGEWITHDRAWAL.
+     */
+    struct
+    {
+      /**
+       * Signature authorizing the withdrawal for outgoing transaction.
+       */
+      json_t *out_authorization_sig;
+
+      /**
+       * Maximum age committed
+       */
+      uint8_t max_age;
+
+      /**
+       * Fee that was charged for the withdrawal.
+       */
+      struct TALER_Amount fee;
+    } age_withdraw;
+
+    /**
+     * Information provided if the reserve was filled via /recoup.
+     * @e type is #TALER_EXCHANGE_RTT_RECOUP.
+     */
+    struct
+    {
+
+      /**
+       * Public key of the coin that was paid back.
+       */
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+
+      /**
+       * Signature of the coin of type
+       * #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * Public key of the exchange that was used for @e exchange_sig.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * When did the /recoup operation happen?
+       */
+      struct GNUNET_TIME_Timestamp timestamp;
+
+    } recoup_details;
+
+    /**
+     * Information about a close operation of the reserve.
+     * @e type is #TALER_EXCHANGE_RTT_CLOSE.
+     */
+    struct
+    {
+      /**
+       * Receiver account information for the outgoing wire transfer.
+       */
+      const char *receiver_account_details;
+
+      /**
+       * Wire transfer details for the outgoing wire transfer.
+       */
+      struct TALER_WireTransferIdentifierRawP wtid;
+
+      /**
+       * Signature of the coin of type
+       * #TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * Public key of the exchange that was used for @e exchange_sig.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * When did the wire transfer happen?
+       */
+      struct GNUNET_TIME_Timestamp timestamp;
+
+      /**
+       * Fee that was charged for the closing.
+       */
+      struct TALER_Amount fee;
+
+    } close_details;
+
+    /**
+     * Information about a history operation of the reserve.
+     * @e type is #TALER_EXCHANGE_RTT_HISTORY.
+     */
+    struct
+    {
+
+      /**
+       * When was the request made.
+       */
+      struct GNUNET_TIME_Timestamp request_timestamp;
+
+      /**
+       * Signature by the reserve approving the history request.
+       */
+      struct TALER_ReserveSignatureP reserve_sig;
+
+    } history_details;
+
+    /**
+     * Information about a merge operation on the reserve.
+     * @e type is #TALER_EXCHANGE_RTT_MERGE.
+     */
+    struct
+    {
+
+      /**
+       * Fee paid for the purse.
+       */
+      struct TALER_Amount purse_fee;
+
+      /**
+       * Hash over the contract.
+       */
+      struct TALER_PrivateContractHashP h_contract_terms;
+
+      /**
+       * Merge capability key.
+       */
+      struct TALER_PurseMergePublicKeyP merge_pub;
+
+      /**
+       * Purse public key.
+       */
+      struct TALER_PurseContractPublicKeyP purse_pub;
+
+      /**
+       * Signature by the reserve approving the merge.
+       */
+      struct TALER_ReserveSignatureP reserve_sig;
+
+      /**
+       * When was the merge made.
+       */
+      struct GNUNET_TIME_Timestamp merge_timestamp;
+
+      /**
+       * When was the purse set to expire.
+       */
+      struct GNUNET_TIME_Timestamp purse_expiration;
+
+      /**
+       * Minimum age required for depositing into the purse.
+       */
+      uint32_t min_age;
+
+      /**
+       * Flags of the purse.
+       */
+      enum TALER_WalletAccountMergeFlags flags;
+
+      /**
+       * True if the purse was actually merged, false
+       * if only the @e purse_fee was charged.
+       */
+      bool merged;
+
+    } merge_details;
+
+    /**
+     * Information about an open request operation on the reserve.
+     * @e type is #TALER_EXCHANGE_RTT_OPEN.
+     */
+    struct
+    {
+
+      /**
+       * Signature by the reserve approving the open.
+       */
+      struct TALER_ReserveSignatureP reserve_sig;
+
+      /**
+       * Amount to be paid from the reserve balance to open
+       * the reserve.
+       */
+      struct TALER_Amount reserve_payment;
+
+      /**
+       * When was the request created.
+       */
+      struct GNUNET_TIME_Timestamp request_timestamp;
+
+      /**
+       * For how long should the reserve be kept open.
+       * (Determines amount to be paid.)
+       */
+      struct GNUNET_TIME_Timestamp reserve_expiration;
+
+      /**
+       * How many open purses should be included with the
+       * open reserve?
+       * (Determines amount to be paid.)
+       */
+      uint32_t purse_limit;
+
+    } open_request;
+
+    /**
+     * Information about an close request operation on the reserve.
+     * @e type is #TALER_EXCHANGE_RTT_CLOSE.
+     */
+    struct
+    {
+
+      /**
+       * Signature by the reserve approving the close.
+       */
+      struct TALER_ReserveSignatureP reserve_sig;
+
+      /**
+       * When was the request created.
+       */
+      struct GNUNET_TIME_Timestamp request_timestamp;
+
+      /**
+       * Hash of the payto://-URI of the target account
+       * for the closure, or all zeros for the reserve
+       * origin account.
+       */
+      struct TALER_PaytoHashP target_account_h_payto;
+
+    } close_request;
+
+
+  } details;
+
+};
+
+
+/**
+ * @brief A /reserves/ GET Handle
+ */
+struct TALER_EXCHANGE_ReservesGetHandle;
+
+
+/**
+ * @brief Reserve summary.
+ */
+struct TALER_EXCHANGE_ReserveSummary
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+
+      /**
+       * Reserve balance.
+       */
+      struct TALER_Amount balance;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve status request to a exchange.
+ *
+ * @param cls closure
+ * @param rs HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesGetCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveSummary *rs);
+
+
+/**
+ * Submit a request to obtain the transaction history of a reserve
+ * from the exchange.  Note that while we return the full response to the
+ * caller for further processing, we do already verify that the
+ * response is well-formed (i.e. that signatures included in the
+ * response are all valid and add up to the balance).  If the exchange's
+ * reply is not well-formed, we return an HTTP status code of zero to
+ * @a cb.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param reserve_pub public key of the reserve to inspect
+ * @param timeout how long to wait for an affirmative reply
+ *        (enables long polling if the reserve does not yet exist)
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesGetHandle *
+TALER_EXCHANGE_reserves_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_EXCHANGE_ReservesGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve GET request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rgh the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_get_cancel (
+  struct TALER_EXCHANGE_ReservesGetHandle *rgh);
+
+
+/**
+ * @brief A /reserves/$RID/status Handle
+ */
+struct TALER_EXCHANGE_ReservesStatusHandle;
+
+
+/**
+ * @brief Reserve status details.
+ */
+struct TALER_EXCHANGE_ReserveStatus
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+
+      /**
+       * Current reserve balance.  May not be the difference between
+       * @e total_in and @e total_out because the @e may be truncated.
+       */
+      struct TALER_Amount balance;
+
+      /**
+       * Total of all inbound transactions in @e history.
+       */
+      struct TALER_Amount total_in;
+
+      /**
+       * Total of all outbound transactions in @e history.
+       */
+      struct TALER_Amount total_out;
+
+      /**
+       * Reserve history.
+       */
+      const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+      /**
+       * Length of the @e history array.
+       */
+      unsigned int history_len;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve status request to a exchange.
+ *
+ * @param cls closure
+ * @param rs HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesStatusCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveStatus *rs);
+
+
+/**
+ * Submit a request to obtain the reserve status.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve to inspect
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesStatusHandle *
+TALER_EXCHANGE_reserves_status (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesStatusCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve status request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rsh the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_status_cancel (
+  struct TALER_EXCHANGE_ReservesStatusHandle *rsh);
+
+
+/**
+ * @brief A /reserves/$RID/history Handle
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle;
+
+
+/**
+ * @brief Reserve history details.
+ */
+struct TALER_EXCHANGE_ReserveHistory
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Timestamp of when we made the history request
+   * (client-side).
+   */
+  struct GNUNET_TIME_Timestamp ts;
+
+  /**
+   * Reserve signature affirming the history request
+   * (generated as part of the request).
+   */
+  const struct TALER_ReserveSignatureP *reserve_sig;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+
+      /**
+       * Reserve balance. May not be the difference between
+       * @e total_in and @e total_out because the @e may be truncated
+       * due to expiration.
+       */
+      struct TALER_Amount balance;
+
+      /**
+       * Total of all inbound transactions in @e history.
+       */
+      struct TALER_Amount total_in;
+
+      /**
+       * Total of all outbound transactions in @e history.
+       */
+      struct TALER_Amount total_out;
+
+      /**
+       * Reserve history.
+       */
+      const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+      /**
+       * Length of the @e history array.
+       */
+      unsigned int history_len;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve history request to a exchange.
+ *
+ * @param cls closure
+ * @param rs HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesHistoryCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveHistory *rs);
+
+
+/**
+ * Submit a request to obtain the reserve history.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve to inspect
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesHistoryCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve history request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rsh the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_history_cancel (
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rsh);
+
+
+/* ********************* POST /reserves/$RESERVE_PUB/withdraw 
*********************** */
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/withdraw Handle
+ */
+struct TALER_EXCHANGE_WithdrawHandle;
+
+
+/**
+ * Information input into the withdraw process per coin.
+ */
+struct TALER_EXCHANGE_WithdrawCoinInput
+{
+  /**
+   * Denomination of the coin.
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+  /**
+   * Master key material for the coin.
+   */
+  const struct TALER_PlanchetMasterSecretP *ps;
+
+  /**
+   * Age commitment for the coin.
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
+};
+
+
+/**
+ * All the details about a coin that are generated during withdrawal and that
+ * may be needed for future operations on the coin.
+ */
+struct TALER_EXCHANGE_PrivateCoinDetails
+{
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Value used to blind the key for the signature.
+   * Needed for recoup operations.
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Signature over the coin.
+   */
+  struct TALER_DenominationSignature sig;
+
+  /**
+   * Values contributed from the exchange during the
+   * withdraw protocol.
+   */
+  struct TALER_ExchangeWithdrawValues exchange_vals;
+};
+
+
+/**
+ * Details about a response for a withdraw request.
+ */
+struct TALER_EXCHANGE_WithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct TALER_EXCHANGE_PrivateCoinDetails ok;
+
+    /**
+     * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+      /**
+       * Requirement row that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t requirement_row;
+
+      /**
+       * Hash of the payto-URI of the account to KYC;
+       */
+      struct TALER_PaytoHashP h_payto;
+
+    } unavailable_for_legal_reasons;
+
+    /**
+     * Details if the status is #MHD_HTTP_CONFLICT.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } conflict;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param wr response details
+ */
+typedef void
+(*TALER_EXCHANGE_WithdrawCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_WithdrawResponse *wr);
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
+ * request.  This API is typically used by a wallet to withdraw from a
+ * reserve.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param wci inputs that determine the planchet
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
+  TALER_EXCHANGE_WithdrawCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a withdraw status request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the withdraw handle
+ */
+void
+TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle;
+
+
+/**
+ * Details about a response for a batch withdraw request.
+ */
+struct TALER_EXCHANGE_BatchWithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * Array of coins returned by the batch withdraw operation.
+       */
+      struct TALER_EXCHANGE_PrivateCoinDetails *coins;
+
+      /**
+       * Length of the @e coins array.
+       */
+      unsigned int num_coins;
+    } ok;
+
+    /**
+     * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+
+      /**
+       * Hash of the payto-URI of the account to KYC;
+       */
+      struct TALER_PaytoHashP h_payto;
+
+      /**
+       * Legitimization requirement that the merchant should use
+       * to check for its KYC status, 0 if not known.
+       */
+      uint64_t requirement_row;
+    } unavailable_for_legal_reasons;
+
+    /**
+     * Details if the status is #MHD_HTTP_CONFLICT.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } conflict;
+
+    /**
+     * Details if the status is #MHD_HTTP_GONE.
+     */
+    struct
+    {
+      /* TODO: returning full details is not implemented */
+    } gone;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * batch withdraw request to a exchange.
+ *
+ * @param cls closure
+ * @param wr response details
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdrawCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_BatchWithdrawResponse *wr);
+
+
+/**
+ * Withdraw multiple coins from the exchange using a 
/reserves/$RESERVE_PUB/batch-withdraw
+ * request.  This API is typically used by a wallet to withdraw many coins 
from a
+ * reserve.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param wci_length number of entries in @a wcis
+ * @param wcis inputs that determine the planchets
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  unsigned int wci_length,
+  const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length],
+  TALER_EXCHANGE_BatchWithdrawCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a batch withdraw status request.  This function cannot be used on a
+ * request handle if a response is already served for it.
+ *
+ * @param wh the batch withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh);
+
+
+/**
+ * Response from a withdraw2 request.
+ */
+struct TALER_EXCHANGE_Withdraw2Response
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * blind signature over the coin
+       */
+      struct TALER_BlindedDenominationSignature blind_sig;
+    } ok;
+  } details;
+
+};
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * withdraw request to a exchange without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param w2r response data
+ */
+typedef void
+(*TALER_EXCHANGE_Withdraw2Callback) (
+  void *cls,
+  const struct TALER_EXCHANGE_Withdraw2Response *w2r);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/withdraw Handle, 2nd variant.
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signature on the already blinded planchet.
+ * Used internally by the `struct TALER_EXCHANGE_WithdrawHandle`
+ * implementation as well as for the tipping logic of merchants.
+ */
+struct TALER_EXCHANGE_Withdraw2Handle;
+
+
+/**
+ * Withdraw a coin from the exchange using a /reserves/$RESERVE_PUB/withdraw
+ * request.  This API is typically used by a merchant to withdraw a tip
+ * where the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl-context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param pd planchet details of the planchet to withdraw
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_Withdraw2Handle *
+TALER_EXCHANGE_withdraw2 (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_PlanchetDetail *pd,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_Withdraw2Callback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a withdraw status request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the withdraw handle
+ */
+void
+TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh);
+
+
+/**
+ * Response from a batch-withdraw request (2nd variant).
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Response
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * array of blind signatures over the coins.
+       */
+      const struct TALER_BlindedDenominationSignature *blind_sigs;
+
+      /**
+       * length of @e blind_sigs
+       */
+      unsigned int blind_sigs_length;
+
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a batch
+ * withdraw request to a exchange without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param bw2r response data
+ */
+typedef void
+(*TALER_EXCHANGE_BatchWithdraw2Callback) (
+  void *cls,
+  const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/batch-withdraw Handle, 2nd variant.
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signatures on the already blinded planchets.
+ * Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * implementation as well as for the tipping logic of merchants.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle;
+
+
+/**
+ * Withdraw a coin from the exchange using a 
/reserves/$RESERVE_PUB/batch-withdraw
+ * request.  This API is typically used by a merchant to withdraw a tip
+ * where the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param pds array of planchet details of the planchet to withdraw
+ * @param pds_length number of entries in the @a pds array
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  unsigned int pds_length,
+  const struct TALER_PlanchetDetail pds[static pds_length],
+  TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel a batch withdraw request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param wh the withdraw handle
+ */
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh);
+
+
+/* ********************* /reserve/$RESERVE_PUB/age-withdraw *************** */
+
+/**
+ * @brief Information needed to withdraw (and reveal) age restricted coins.
+ */
+struct TALER_EXCHANGE_AgeWithdrawCoinInput
+{
+  /**
+   * The master secret from which we derive all other relevant values for
+   * the coin: private key, nonces (if applicable) and age restriction
+   */
+  struct TALER_PlanchetMasterSecretP secrets[TALER_CNC_KAPPA];
+
+  /**
+   * The denomination of the coin.  Must support age restriction, i.e
+   * its .keys.age_mask MUST not be 0
+   */
+  struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+};
+
+
+/**
+ * All the details about a coin that are generated during age-withdrawal and
+ * that may be needed for future operations on the coin.
+ */
+struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails
+{
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Hash of the public key of the coin.
+   */
+  struct TALER_CoinPubHashP h_coin_pub;
+
+  /**
+   * Value used to blind the key for the signature.
+   * Needed for recoup operations.
+   */
+  union TALER_DenominationBlindingKeyP blinding_key;
+
+  /**
+   * The age commitment, proof for the coin, derived from the
+   * Master secret and maximum age in the originating request
+   */
+  struct TALER_AgeCommitmentProof age_commitment_proof;
+
+  /**
+   * The hash of the age commitment
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Values contributed from the exchange during the
+   * withdraw protocol.
+   */
+  struct TALER_ExchangeWithdrawValues alg_values;
+
+  /**
+   * The planchet constructed
+   */
+  struct TALER_PlanchetDetail planchet;
+};
+
+/**
+ * @brief A handle to a /reserves/$RESERVE_PUB/age-withdraw request
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle;
+
+/**
+ * @brief Details about the response for a age withdraw request.
+ */
+struct TALER_EXCHANGE_AgeWithdrawResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Index that should not be revealed during the age-withdraw reveal
+       * phase.
+       */
+      uint8_t noreveal_index;
+
+      /**
+       * The commitment of the age-withdraw request, needed for the
+       * subsequent call to /age-withdraw/$ACH/reveal
+       */
+      struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+      /**
+       * The number of elements in @e coins, each referring to
+       * TALER_CNC_KAPPA elements
+       */
+      size_t num_coins;
+
+      /**
+       * The computed details of the non-revealed @e num_coins coins to keep.
+       */
+      const struct TALER_EXCHANGE_AgeWithdrawCoinPrivateDetails *coin_details;
+
+      /**
+       * The array of blinded hashes of the non-revealed
+       * @e num_coins coins, needed for the reveal step;
+       */
+      const struct TALER_BlindedCoinHashP *blinded_coin_hs;
+
+      /**
+       * Key used by the exchange to sign the response.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+    } ok;
+  } details;
+};
+
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_AgeWithdrawResponse *awr);
+
+/**
+ * Submit an age-withdraw request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet.  Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument @a rd should be committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param curl_ctx The curl context
+ * @param exchange_url The base url of the exchange
+ * @param keys The denomination keys from the exchange
+ * @param reserve_priv The pivate key to the reserve
+ * @param num_coins The number of elements in @e coin_inputs
+ * @param coin_inputs The input for the coins to withdraw
+ * @param max_age The maximum age we commit to.
+ * @param res_cb A callback for the result, maybe NULL
+ * @param res_cb_cls A closure for @e res_cb, maybe NULL
+ * @return a handle for this request; NULL if the argument was invalid.
+ *         In this case, the callback will not be called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawHandle *
+TALER_EXCHANGE_age_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  struct TALER_EXCHANGE_Keys *keys,
+  const char *exchange_url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  size_t num_coins,
+  const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[static
+                                                               num_coins],
+  uint8_t max_age,
+  TALER_EXCHANGE_AgeWithdrawCallback res_cb,
+  void *res_cb_cls);
+
+/**
+ * Cancel a age-withdraw request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param awh the age-withdraw handle
+ */
+void
+TALER_EXCHANGE_age_withdraw_cancel (
+  struct TALER_EXCHANGE_AgeWithdrawHandle *awh);
+
+
+/**++++++ age-withdraw with pre-blinded planchets ***************************/
+
+/**
+ * @brief Information needed to withdraw (and reveal) age restricted coins.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedInput
+{
+  /**
+   * The denomination of the coin.  Must support age restriction, i.e
+   * its .keys.age_mask MUST not be 0
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+  /**
+   * Blinded Planchets
+   */
+  struct TALER_PlanchetDetail planchet_details[TALER_CNC_KAPPA];
+};
+
+/**
+ * Response from an age-withdraw request with pre-blinded planchets
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Index that should not be revealed during the age-withdraw reveal 
phase.
+       * The struct TALER_PlanchetMasterSecretP * from the request
+       * with this index are the ones to keep.
+       */
+      uint8_t noreveal_index;
+
+      /**
+       * The commitment of the call to age-withdraw, needed for the subsequent
+       * call to /age-withdraw/$ACH/reveal.
+       */
+      struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+      /**
+       * Key used by the exchange to sign the response.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting an
+ * age-withdraw request to a exchange with pre-blinded planchets
+ * without the (un)blinding factor.
+ *
+ * @param cls closure
+ * @param awbr response data
+ */
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawBlindedCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AgeWithdrawBlindedResponse *awbr);
+
+
+/**
+ * @brief A /reserves/$RESERVE_PUB/age-withdraw Handle, 2nd variant with
+ * pre-blinded planchets.
+ *
+ * This variant does not do the blinding/unblinding and only
+ * fetches the blind signatures on the already blinded planchets.
+ * Used internally by the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * implementation as well as for the reward logic of merchants.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle;
+
+/**
+ * Withdraw age-restricted coins from the exchange using a
+ * /reserves/$RESERVE_PUB/age-withdraw request.  This API is typically used
+ * by a merchant to withdraw a reward where the planchets are pre-blinded and
+ * the blinding factor is unknown to the merchant.
+ *
+ * Note that to ensure that no money is lost in case of hardware
+ * failures, the caller must have committed (most of) the arguments to
+ * disk before calling, and be ready to repeat the request with the
+ * same arguments in case of failures.
+ *
+ * @param curl_ctx The curl context to use
+ * @param exchange_url The base-URL of the exchange
+ * @param keys The /keys material from the exchange
+ * @param max_age The maximum age that the coins are committed to.
+ * @param num_input number of entries in the @a blinded_input array
+ * @param blinded_input array of planchet details of the planchet to withdraw
+ * @param reserve_priv private key of the reserve to withdraw from
+ * @param res_cb the callback to call when the final result for this request 
is available
+ * @param res_cb_cls closure for @a res_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *
+TALER_EXCHANGE_age_withdraw_blinded (
+  struct GNUNET_CURL_Context *curl_ctx,
+  struct TALER_EXCHANGE_Keys *keys,
+  const char *exchange_url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  uint8_t max_age,
+  unsigned int num_input,
+  const struct TALER_EXCHANGE_AgeWithdrawBlindedInput blinded_input[static
+                                                                    num_input],
+  TALER_EXCHANGE_AgeWithdrawBlindedCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * Cancel an age-withdraw request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param awbh the age-withdraw handle
+ */
+void
+TALER_EXCHANGE_age_withdraw_blinded_cancel (
+  struct TALER_EXCHANGE_AgeWithdrawBlindedHandle *awbh);
+
+
+/* ********************* /age-withdraw/$ACH/reveal ************************ */
+
+/**
+ * @brief A handle to a /age-withdraw/$ACH/reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle;
+
+/**
+ * The response from a /age-withdraw/$ACH/reveal request
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response
+   */
+  union
+  {
+    /**
+     * Details if the status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Number of signatures returned.
+       */
+      unsigned int num_sigs;
+
+      /**
+       * Array of @e num_coins blinded denomination signatures, giving each
+       * coin its value and validity. The array give these coins in the same
+       * order (and should have the same length) in which the original
+       * age-withdraw request specified the respective denomination keys.
+       */
+      const struct TALER_BlindedDenominationSignature *blinded_denom_sigs;
+
+    } ok;
+  } details;
+
+};
+
+typedef void
+(*TALER_EXCHANGE_AgeWithdrawRevealCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_AgeWithdrawRevealResponse *awr);
+
+/**
+ * Submit an age-withdraw-reveal request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet.  Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument @a rd should be committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param curl_ctx The curl context
+ * @param exchange_url The base url of the exchange
+ * @param num_coins The number of elements in @e coin_inputs and @e alg_values
+ * @param coin_inputs The input for the coins to withdraw, same as in the 
previous call to /age-withdraw
+ * @param noreveal_index The index into each of the kappa coin candidates, 
that should not be revealed to the exchange
+ * @param h_commitment The commmitment from the previous call to /age-withdraw
+ * @param reserve_pub The public key of the reserve the original call to 
/age-withdraw was made to
+ * @param res_cb A callback for the result, maybe NULL
+ * @param res_cb_cls A closure for @e res_cb, maybe NULL
+ * @return a handle for this request; NULL if the argument was invalid.
+ *         In this case, the callback will not be called.
+ */
+struct TALER_EXCHANGE_AgeWithdrawRevealHandle *
+TALER_EXCHANGE_age_withdraw_reveal (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  size_t num_coins,
+  const struct TALER_EXCHANGE_AgeWithdrawCoinInput coin_inputs[static
+                                                               num_coins],
+  uint8_t noreveal_index,
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  TALER_EXCHANGE_AgeWithdrawRevealCallback res_cb,
+  void *res_cb_cls);
+
+
+/**
+ * @brief Cancel an age-withdraw-reveal request
+ *
+ * @param awrh Handle to an age-withdraw-reqveal request
+ */
+void
+TALER_EXCHANGE_age_withdraw_reveal_cancel (
+  struct TALER_EXCHANGE_AgeWithdrawRevealHandle *awrh);
+
+
+/* ********************* /refresh/melt+reveal ***************************** */
+
+
+/**
+ * Information needed to melt (partially spent) coins to obtain fresh coins
+ * that are unlinkable to the original coin(s).  Note that melting more than
+ * one coin in a single request will make those coins linkable, so we only melt
+ * one coin at a time.
+ */
+struct TALER_EXCHANGE_RefreshData
+{
+  /**
+   * private key of the coin to melt
+   */
+  struct TALER_CoinSpendPrivateKeyP melt_priv;
+
+  /*
+   * age commitment and proof and its hash that went into the original coin,
+   * might be NULL.
+   */
+  const struct TALER_AgeCommitmentProof *melt_age_commitment_proof;
+  const struct TALER_AgeCommitmentHash *melt_h_age_commitment;
+
+  /**
+   * amount specifying how much the coin will contribute to the melt
+   * (including fee)
+   */
+  struct TALER_Amount melt_amount;
+
+  /**
+   * signatures affirming the validity of the public keys corresponding to the
+   * @e melt_priv private key
+   */
+  struct TALER_DenominationSignature melt_sig;
+
+  /**
+   * denomination key information record corresponding to the @e melt_sig
+   * validity of the keys
+   */
+  struct TALER_EXCHANGE_DenomPublicKey melt_pk;
+
+  /**
+   * array of @e pks_len denominations of fresh coins to create
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *fresh_pks;
+
+  /**
+   * length of the @e pks array
+   */
+  unsigned int fresh_pks_len;
+};
+
+
+/* ********************* /coins/$COIN_PUB/melt ***************************** */
+
+/**
+ * @brief A /coins/$COIN_PUB/melt Handle
+ */
+struct TALER_EXCHANGE_MeltHandle;
+
+
+/**
+ * Information we obtain per coin during melting.
+ */
+struct TALER_EXCHANGE_MeltBlindingDetail
+{
+  /**
+   * Exchange values contributed to the refresh operation
+   */
+  struct TALER_ExchangeWithdrawValues alg_value;
+
+};
+
+
+/**
+ * Response returned to a /melt request.
+ */
+struct TALER_EXCHANGE_MeltResponse
+{
+  /**
+   * Full HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Parsed response details, variant depending on the
+   * @e hr.http_status.
+   */
+  union
+  {
+    /**
+     * Results for status #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * Information returned per coin.
+       */
+      const struct TALER_EXCHANGE_MeltBlindingDetail *mbds;
+
+      /**
+       * Key used by the exchange to sign the response.
+       */
+      struct TALER_ExchangePublicKeyP sign_key;
+
+      /**
+       * Length of the @a mbds array with the exchange values
+       * and blinding keys we are using.
+       */
+      unsigned int num_mbds;
+
+      /**
+       * Gamma value chosen by the exchange.
+       */
+      uint32_t noreveal_index;
+    } ok;
+
+  } details;
+};
+
+
+/**
+ * Callbacks of this type are used to notify the application about the result
+ * of the /coins/$COIN_PUB/melt stage.  If successful, the @a noreveal_index
+ * should be committed to disk prior to proceeding
+ * #TALER_EXCHANGE_refreshes_reveal().
+ *
+ * @param cls closure
+ * @param mr response details
+ */
+typedef void
+(*TALER_EXCHANGE_MeltCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_MeltResponse *mr);
+
+
+/**
+ * Submit a melt request to the exchange and get the exchange's
+ * response.
+ *
+ * This API is typically used by a wallet.  Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * argument @a rd should be committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param rms the fresh secret that defines the refresh operation
+ * @param rd the refresh data specifying the characteristics of the operation
+ * @param melt_cb the callback to call with the result
+ * @param melt_cb_cls closure for @a melt_cb
+ * @return a handle for this request; NULL if the argument was invalid.
+ *         In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_MeltHandle *
+TALER_EXCHANGE_melt (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_EXCHANGE_RefreshData *rd,
+  TALER_EXCHANGE_MeltCallback melt_cb,
+  void *melt_cb_cls);
+
+
+/**
+ * Cancel a melt request.  This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param mh the melt handle
+ */
+void
+TALER_EXCHANGE_melt_cancel (struct TALER_EXCHANGE_MeltHandle *mh);
+
+
+/* ********************* /refreshes/$RCH/reveal ***************************** 
*/
+
+
+/**
+ * Information about a coin obtained via /refreshes/$RCH/reveal.
+ */
+struct TALER_EXCHANGE_RevealedCoinInfo
+{
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Master secret of this coin.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * Age commitment and its hash of the coin, might be NULL.
+   */
+  struct TALER_AgeCommitmentProof *age_commitment_proof;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Blinding keys used to blind the fresh coin.
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Signature affirming the validity of the coin.
+   */
+  struct TALER_DenominationSignature sig;
+
+};
+
+
+/**
+ * Result of a /refreshes/$RCH/reveal request.
+ */
+struct TALER_EXCHANGE_RevealResult
+{
+  /**
+   * HTTP status.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Parsed response details, variant depending on the
+   * @e hr.http_status.
+   */
+  union
+  {
+    /**
+     * Results for status #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Array of @e num_coins values about the coins obtained via the refresh
+       * operation.  The array give the coins in the same order (and should
+       * have the same length) in which the original melt request specified the
+       * respective denomination keys.
+       */
+      const struct TALER_EXCHANGE_RevealedCoinInfo *coins;
+
+      /**
+       * Number of coins returned.
+       */
+      unsigned int num_coins;
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to return the final result of
+ * submitting a refresh request to a exchange.  If the operation was
+ * successful, this function returns the signatures over the coins
+ * that were remelted.
+ *
+ * @param cls closure
+ * @param rr result of the reveal operation
+ */
+typedef void
+(*TALER_EXCHANGE_RefreshesRevealCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_RevealResult *rr);
+
+
+/**
+ * @brief A /refreshes/$RCH/reveal Handle
+ */
+struct TALER_EXCHANGE_RefreshesRevealHandle;
+
+
+/**
+ * Submit a /refreshes/$RCH/reval request to the exchange and get the 
exchange's
+ * response.
+ *
+ * This API is typically used by a wallet.  Note that to ensure that
+ * no money is lost in case of hardware failures, the provided
+ * arguments should have been committed to persistent storage
+ * prior to calling this function.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param rms the fresh secret that defines the refresh operation
+ * @param rd the refresh data that characterizes the refresh operation
+ * @param num_coins number of fresh coins to be created, length of the @a 
exchange_vals array, must match value in @a rd
+ * @param alg_values array @a num_coins of exchange values contributed to the 
refresh operation
+ * @param noreveal_index response from the exchange to the
+ *        #TALER_EXCHANGE_melt() invocation
+ * @param reveal_cb the callback to call with the final result of the
+ *        refresh operation
+ * @param reveal_cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the argument was invalid.
+ *         In this case, neither callback will be called.
+ */
+struct TALER_EXCHANGE_RefreshesRevealHandle *
+TALER_EXCHANGE_refreshes_reveal (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_EXCHANGE_RefreshData *rd,
+  unsigned int num_coins,
+  const struct TALER_ExchangeWithdrawValues alg_values[static num_coins],
+  uint32_t noreveal_index,
+  TALER_EXCHANGE_RefreshesRevealCallback reveal_cb,
+  void *reveal_cb_cls);
+
+
+/**
+ * Cancel a refresh reveal request.  This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param rrh the refresh reval handle
+ */
+void
+TALER_EXCHANGE_refreshes_reveal_cancel (
+  struct TALER_EXCHANGE_RefreshesRevealHandle *rrh);
+
+
+/* ********************* /coins/$COIN_PUB/link ***************************** */
+
+
+/**
+ * @brief A /coins/$COIN_PUB/link Handle
+ */
+struct TALER_EXCHANGE_LinkHandle;
+
+
+/**
+ * Information about a coin obtained via /link.
+ */
+struct TALER_EXCHANGE_LinkedCoinInfo
+{
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Age commitment and its hash, if applicable.
+   */
+  bool has_age_commitment;
+  struct TALER_AgeCommitmentProof age_commitment_proof;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Master secret of this coin.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * Signature affirming the validity of the coin.
+   */
+  struct TALER_DenominationSignature sig;
+
+  /**
+   * Denomination public key of the coin.
+   */
+  struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Result of a /link request.
+ */
+struct TALER_EXCHANGE_LinkResult
+{
+  /**
+   * HTTP status.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Parsed response details, variant depending on the
+   * @e hr.http_status.
+   */
+  union
+  {
+    /**
+     * Results for status #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Array of @e num_coins values about the
+       * coins obtained via linkage.
+       */
+      const struct TALER_EXCHANGE_LinkedCoinInfo *coins;
+
+      /**
+       * Number of coins returned.
+       */
+      unsigned int num_coins;
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to return the final result of submitting a
+ * /coins/$COIN_PUB/link request to a exchange.  If the operation was
+ * successful, this function returns the signatures over the coins that were
+ * created when the original coin was melted.
+ *
+ * @param cls closure
+ * @param lr result of the /link operation
+ */
+typedef void
+(*TALER_EXCHANGE_LinkCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_LinkResult *lr);
+
+
+/**
+ * Submit a link request to the exchange and get the exchange's response.
+ *
+ * This API is typically not used by anyone, it is more a threat against those
+ * trying to receive a funds transfer by abusing the refresh protocol.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param coin_priv private key to request link data for
+ * @param age_commitment_proof age commitment to the corresponding coin, might 
be NULL
+ * @param link_cb the callback to call with the useful result of the
+ *        refresh operation the @a coin_priv was involved in (if any)
+ * @param link_cb_cls closure for @a link_cb
+ * @return a handle for this request
+ */
+struct TALER_EXCHANGE_LinkHandle *
+TALER_EXCHANGE_link (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  const struct TALER_AgeCommitmentProof *age_commitment_proof,
+  TALER_EXCHANGE_LinkCallback link_cb,
+  void *link_cb_cls);
+
+
+/**
+ * Cancel a link request.  This function cannot be used
+ * on a request handle if the callback was already invoked.
+ *
+ * @param lh the link handle
+ */
+void
+TALER_EXCHANGE_link_cancel (struct TALER_EXCHANGE_LinkHandle *lh);
+
+
+/* ********************* /transfers/$WTID *********************** */
+
+/**
+ * @brief A /transfers/$WTID Handle
+ */
+struct TALER_EXCHANGE_TransfersGetHandle;
+
+
+/**
+ * Information the exchange returns per wire transfer.
+ */
+struct TALER_EXCHANGE_TransferData
+{
+
+  /**
+   * exchange key used to sign
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * exchange signature over the transfer data
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+  /**
+   * hash of the payto:// URI the transfer went to
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * time when the exchange claims to have performed the wire transfer
+   */
+  struct GNUNET_TIME_Timestamp execution_time;
+
+  /**
+   * Actual amount of the wire transfer, excluding the wire fee.
+   */
+  struct TALER_Amount total_amount;
+
+  /**
+   * wire fee that was charged by the exchange
+   */
+  struct TALER_Amount wire_fee;
+
+  /**
+   * length of the @e details array
+   */
+  unsigned int details_length;
+
+  /**
+   * array with details about the combined transactions
+   */
+  const struct TALER_TrackTransferDetails *details;
+
+};
+
+
+/**
+ * Response for a GET /transfers request.
+ */
+struct TALER_EXCHANGE_TransfersGetResponse
+{
+  /**
+   * HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on HTTP status code.
+   */
+  union
+  {
+    /**
+     * Details if status code is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      struct TALER_EXCHANGE_TransferData td;
+    } ok;
+
+  } details;
+};
+
+
+/**
+ * Function called with detailed wire transfer data, including all
+ * of the coin transactions that were combined into the wire transfer.
+ *
+ * @param cls closure
+ * @param tgr response data
+ */
+typedef void
+(*TALER_EXCHANGE_TransfersGetCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_TransfersGetResponse *tgr);
+
+
+/**
+ * Query the exchange about which transactions were combined
+ * to create a wire transfer.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param wtid raw wire transfer identifier to get information about
+ * @param cb callback to call
+ * @param cb_cls closure for @a cb
+ * @return handle to cancel operation
+ */
+struct TALER_EXCHANGE_TransfersGetHandle *
+TALER_EXCHANGE_transfers_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  TALER_EXCHANGE_TransfersGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel wire deposits request.  This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param wdh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_transfers_get_cancel (
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh);
+
+
+/* ********************* GET /deposits/ *********************** */
+
+
+/**
+ * @brief A /deposits/ GET Handle
+ */
+struct TALER_EXCHANGE_DepositGetHandle;
+
+
+/**
+ * Data returned for a successful GET /deposits/ request.
+ */
+struct TALER_EXCHANGE_GetDepositResponse
+{
+
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details about the response.
+   */
+  union
+  {
+
+    /**
+     * Response if the status was #MHD_HTTP_OK
+     */
+    struct TALER_EXCHANGE_DepositData
+    {
+      /**
+       * exchange key used to sign, all zeros if exchange did not
+       * yet execute the transaction
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * signature from the exchange over the deposit data, all zeros if 
exchange did not
+       * yet execute the transaction
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * wire transfer identifier used by the exchange, all zeros if exchange 
did not
+       * yet execute the transaction
+       */
+      struct TALER_WireTransferIdentifierRawP wtid;
+
+      /**
+       * actual execution time for the wire transfer
+       */
+      struct GNUNET_TIME_Timestamp execution_time;
+
+      /**
+       * contribution to the total amount by this coin, all zeros if exchange 
did not
+       * yet execute the transaction
+       */
+      struct TALER_Amount coin_contribution;
+
+    } ok;
+
+    /**
+     * Response if the status was #MHD_HTTP_ACCEPTED
+     */
+    struct
+    {
+
+      /**
+       * planned execution time for the wire transfer
+       */
+      struct GNUNET_TIME_Timestamp execution_time;
+
+      /**
+       * KYC legitimization requirement that the merchant should use to check
+       * for its KYC status.
+       */
+      uint64_t requirement_row;
+
+      /**
+       * Current AML state for the account. May explain why transfers are
+       * not happening.
+       */
+      enum TALER_AmlDecisionState aml_decision;
+
+      /**
+       * Set to 'true' if the KYC check is already finished and
+       * the exchange is merely waiting for the @e execution_time.
+       */
+      bool kyc_ok;
+    } accepted;
+
+  } details;
+};
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure
+ * @param dr details about the deposit response
+ */
+typedef void
+(*TALER_EXCHANGE_DepositGetCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_GetDepositResponse *dr);
+
+
+/**
+ * Obtain the wire transfer details for a given transaction.  Tells the client
+ * which aggregate wire transfer the deposit operation identified by @a 
coin_pub,
+ * @a merchant_priv and @a h_contract_terms contributed to.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param merchant_priv the merchant's private key
+ * @param h_wire hash of merchant's wire transfer details
+ * @param h_contract_terms hash of the proposal data
+ * @param coin_pub public key of the coin
+ * @param timeout timeout to use for long-polling, 0 for no long polling
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return handle to abort request
+ */
+struct TALER_EXCHANGE_DepositGetHandle *
+TALER_EXCHANGE_deposits_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_EXCHANGE_DepositGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel deposit wtid request.  This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param dwh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_deposits_get_cancel (
+  struct TALER_EXCHANGE_DepositGetHandle *dwh);
+
+
+/**
+ * Convenience function.  Verifies a coin's transaction history as
+ * returned by the exchange.
+ *
+ * @param dk fee structure for the coin
+ * @param coin_pub public key of the coin
+ * @param history history of the coin in json encoding
+ * @param[out] total how much of the coin has been spent according to @a 
history
+ * @return #GNUNET_OK if @a history is valid, #GNUNET_SYSERR if not
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_coin_history (
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const json_t *history,
+  struct TALER_Amount *total);
+
+
+/**
+ * Parse history given in JSON format and return it in binary
+ * format.
+ *
+ * @param keys exchange keys
+ * @param history JSON array with the history
+ * @param reserve_pub public key of the reserve to inspect
+ * @param currency currency we expect the balance to be in
+ * @param[out] total_in set to value of credits to reserve
+ * @param[out] total_out set to value of debits from reserve
+ * @param history_length number of entries in @a history
+ * @param[out] rhistory array of length @a history_length, set to the
+ *             parsed history entries
+ * @return #GNUNET_OK if history was valid and @a rhistory and @a balance
+ *         were set,
+ *         #GNUNET_SYSERR if there was a protocol violation in @a history
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_reserve_history (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *history,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *currency,
+  struct TALER_Amount *total_in,
+  struct TALER_Amount *total_out,
+  unsigned int history_length,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length]);
+
+
+/**
+ * Free memory (potentially) allocated by 
#TALER_EXCHANGE_parse_reserve_history().
+ *
+ * @param len number of entries in @a rhistory
+ * @param[in] rhistory result to free
+ */
+void
+TALER_EXCHANGE_free_reserve_history (
+  unsigned int len,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len]);
+
+
+/* ********************* /recoup *********************** */
+
+
+/**
+ * @brief A /recoup Handle
+ */
+struct TALER_EXCHANGE_RecoupHandle;
+
+
+/**
+ * Response from a recoup request.
+ */
+struct TALER_EXCHANGE_RecoupResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * public key of the reserve receiving the recoup
+       */
+      struct TALER_ReservePublicKeyP reserve_pub;
+
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to return the final result of
+ * submitting a recoup request to a exchange.  If the operation was
+ * successful, this function returns the @a reserve_pub of the
+ * reserve that was credited.
+ *
+ * @param cls closure
+ * @param rr response data
+ */
+typedef void
+(*TALER_EXCHANGE_RecoupResultCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_RecoupResponse *rr);
+
+
+/**
+ * Ask the exchange to pay back a coin due to the exchange triggering
+ * the emergency recoup protocol for a given denomination.  The value
+ * of the coin will be refunded to the original customer (without fees).
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param pk kind of coin to pay back
+ * @param denom_sig signature over the coin by the exchange using @a pk
+ * @param exchange_vals contribution from the exchange on the withdraw
+ * @param ps secret internals of the original planchet
+ * @param recoup_cb the callback to call when the final result for this 
request is available
+ * @param recoup_cb_cls closure for @a recoup_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_RecoupHandle *
+TALER_EXCHANGE_recoup (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_EXCHANGE_DenomPublicKey *pk,
+  const struct TALER_DenominationSignature *denom_sig,
+  const struct TALER_ExchangeWithdrawValues *exchange_vals,
+  const struct TALER_PlanchetMasterSecretP *ps,
+  TALER_EXCHANGE_RecoupResultCallback recoup_cb,
+  void *recoup_cb_cls);
+
+
+/**
+ * Cancel a recoup request.  This function cannot be used on a
+ * request handle if the callback was already invoked.
+ *
+ * @param ph the recoup handle
+ */
+void
+TALER_EXCHANGE_recoup_cancel (struct TALER_EXCHANGE_RecoupHandle *ph);
+
+
+/* ********************* /recoup-refresh *********************** */
+
+
+/**
+ * @brief A /recoup-refresh Handle
+ */
+struct TALER_EXCHANGE_RecoupRefreshHandle;
+
+
+/**
+ * Response from a /recoup-refresh request.
+ */
+struct TALER_EXCHANGE_RecoupRefreshResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * public key of the dirty coin that was credited
+       */
+      struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to return the final result of
+ * submitting a recoup-refresh request to a exchange.
+ *
+ * @param cls closure
+ * @param rrr response data
+ */
+typedef void
+(*TALER_EXCHANGE_RecoupRefreshResultCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_RecoupRefreshResponse *rrr);
+
+
+/**
+ * Ask the exchange to pay back a coin due to the exchange triggering
+ * the emergency recoup protocol for a given denomination.  The value
+ * of the coin will be refunded to the original coin that the
+ * revoked coin was refreshed from. The original coin is then
+ * considered a zombie.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param pk kind of coin to pay back
+ * @param denom_sig signature over the coin by the exchange using @a pk
+ * @param exchange_vals contribution from the exchange on the withdraw
+ * @param rms melt secret of the refreshing operation
+ * @param ps coin-specific secrets derived for this coin during the refreshing 
operation
+ * @param idx index of the fresh coin in the refresh operation that is now 
being recouped
+ * @param recoup_cb the callback to call when the final result for this 
request is available
+ * @param recoup_cb_cls closure for @a recoup_cb
+ * @return NULL
+ *         if the inputs are invalid (i.e. denomination key not with this 
exchange).
+ *         In this case, the callback is not called.
+ */
+struct TALER_EXCHANGE_RecoupRefreshHandle *
+TALER_EXCHANGE_recoup_refresh (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_EXCHANGE_DenomPublicKey *pk,
+  const struct TALER_DenominationSignature *denom_sig,
+  const struct TALER_ExchangeWithdrawValues *exchange_vals,
+  const struct TALER_RefreshMasterSecretP *rms,
+  const struct TALER_PlanchetMasterSecretP *ps,
+  unsigned int idx,
+  TALER_EXCHANGE_RecoupRefreshResultCallback recoup_cb,
+  void *recoup_cb_cls);
+
+
+/**
+ * Cancel a recoup-refresh request.  This function cannot be used on a request
+ * handle if the callback was already invoked.
+ *
+ * @param ph the recoup handle
+ */
+void
+TALER_EXCHANGE_recoup_refresh_cancel (
+  struct TALER_EXCHANGE_RecoupRefreshHandle *ph);
+
+
+/* *********************  /kyc* *********************** */
+
+/**
+ * Handle for a ``/kyc-check`` operation.
+ */
+struct TALER_EXCHANGE_KycCheckHandle;
+
+
+/**
+ * KYC status response details.
+ */
+struct TALER_EXCHANGE_KycStatus
+{
+  /**
+   * HTTP status code returned by the exchange.
+   */
+  unsigned int http_status;
+
+  /**
+   * Taler error code, if any.
+   */
+  enum TALER_ErrorCode ec;
+
+  union
+  {
+
+    /**
+     * KYC is OK, affirmation returned by the exchange.
+     */
+    struct
+    {
+
+      /**
+       * Details about which KYC check(s) were passed.
+       */
+      const json_t *kyc_details;
+
+      /**
+       * Time of the affirmation.
+       */
+      struct GNUNET_TIME_Timestamp timestamp;
+
+      /**
+       * The signing public key used for @e exchange_sig.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * Signature of purpose
+       * #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS affirming
+       * the successful KYC process.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * AML status for the account.
+       */
+      enum TALER_AmlDecisionState aml_status;
+
+    } ok;
+
+    /**
+     * KYC is required.
+     */
+    struct
+    {
+
+      /**
+       * URL the user should open in a browser if
+       * the KYC process is to be run. Returned if
+       * @e http_status is #MHD_HTTP_ACCEPTED.
+       */
+      const char *kyc_url;
+
+      /**
+       * AML status for the account.
+       */
+      enum TALER_AmlDecisionState aml_status;
+
+    } accepted;
+
+    /**
+     * KYC is OK, but account needs positive AML decision.
+     */
+    struct
+    {
+
+      /**
+       * AML status for the account.
+       */
+      enum TALER_AmlDecisionState aml_status;
+
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+/**
+ * Function called with the result of a KYC check.
+ *
+ * @param cls closure
+ * @param ks the account's KYC status details
+ */
+typedef void
+(*TALER_EXCHANGE_KycStatusCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_KycStatus *ks);
+
+
+/**
+ * Run interaction with exchange to check KYC status
+ * of a merchant.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param keys keys of the exchange
+ * @param requirement_row number identifying the KYC requirement
+ * @param h_payto hash of the payto:// URI at @a payment_target
+ * @param ut type of the entity performing the KYC check
+ * @param timeout how long to wait for a positive KYC status
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycCheckHandle *
+TALER_EXCHANGE_kyc_check (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  uint64_t requirement_row,
+  const struct TALER_PaytoHashP *h_payto,
+  enum TALER_KYCLOGIC_KycUserType ut,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_EXCHANGE_KycStatusCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel KYC check operation.
+ *
+ * @param kyc handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_check_cancel (struct TALER_EXCHANGE_KycCheckHandle *kyc);
+
+
+/**
+ * KYC proof response details.
+ */
+struct TALER_EXCHANGE_KycProofResponse
+{
+  /**
+   * HTTP status code returned by the exchange.
+   */
+  unsigned int http_status;
+
+  union
+  {
+
+    /**
+     * KYC is OK, affirmation returned by the exchange.
+     */
+    struct
+    {
+
+      /**
+       * Where to redirect the client next.
+       */
+      const char *redirect_url;
+
+    } found;
+
+  } details;
+
+};
+
+/**
+ * Function called with the result of a KYC check.
+ *
+ * @param cls closure
+ * @param kpr the account's KYC status details
+ */
+typedef void
+(*TALER_EXCHANGE_KycProofCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_KycProofResponse *kpr);
+
+
+/**
+ * Handle for a /kyc-proof operation.
+ */
+struct TALER_EXCHANGE_KycProofHandle;
+
+
+/**
+ * Run interaction with exchange to provide proof of KYC status.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param h_payto hash of payto URI identifying the target account
+ * @param logic name of the KYC logic to run
+ * @param args additional args to pass, can be NULL
+ *        or a string to append to the URL. Must then begin with '&'.
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycProofHandle *
+TALER_EXCHANGE_kyc_proof (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_PaytoHashP *h_payto,
+  const char *logic,
+  const char *args,
+  TALER_EXCHANGE_KycProofCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel KYC proof operation.
+ *
+ * @param kph handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_proof_cancel (struct TALER_EXCHANGE_KycProofHandle *kph);
+
+
+/**
+ * Handle for a ``/kyc-wallet`` operation.
+ */
+struct TALER_EXCHANGE_KycWalletHandle;
+
+
+/**
+ * KYC status response details.
+ */
+struct TALER_EXCHANGE_WalletKycResponse
+{
+
+  /**
+   * HTTP status code returned by the exchange.
+   */
+  unsigned int http_status;
+
+  /**
+   * Taler error code, if any.
+   */
+  enum TALER_ErrorCode ec;
+
+  /**
+   * Variants depending on @e http_status.
+   */
+  union
+  {
+
+    /**
+     * In case @e http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+      /**
+       * Wallet's KYC requirement row.
+       */
+      uint64_t requirement_row;
+
+      /**
+       * Hash of the payto-URI identifying the wallet to KYC.
+       */
+      struct TALER_PaytoHashP h_payto;
+
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+/**
+ * Function called with the result for a wallet looking
+ * up its KYC payment target.
+ *
+ * @param cls closure
+ * @param ks the wallets KYC payment target details
+ */
+typedef void
+(*TALER_EXCHANGE_KycWalletCallback)(
+  void *cls,
+  const struct TALER_EXCHANGE_WalletKycResponse *ks);
+
+
+/**
+ * Run interaction with exchange to find out the wallet's KYC
+ * identifier.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param reserve_priv wallet private key to check
+ * @param balance balance (or balance threshold) crossed by the wallet
+ * @param cb function to call with the result
+ * @param cb_cls closure for @a cb
+ * @return NULL on error
+ */
+struct TALER_EXCHANGE_KycWalletHandle *
+TALER_EXCHANGE_kyc_wallet (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_Amount *balance,
+  TALER_EXCHANGE_KycWalletCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel KYC wallet operation
+ *
+ * @param kwh handle for operation to cancel
+ */
+void
+TALER_EXCHANGE_kyc_wallet_cancel (struct TALER_EXCHANGE_KycWalletHandle *kwh);
+
+
+/* *********************  /management *********************** */
+
+
+/**
+ * @brief Future Exchange's signature key
+ */
+struct TALER_EXCHANGE_FutureSigningPublicKey
+{
+  /**
+   * The signing public key
+   */
+  struct TALER_ExchangePublicKeyP key;
+
+  /**
+   * Signature by the security module affirming it owns this key.
+   */
+  struct TALER_SecurityModuleSignatureP signkey_secmod_sig;
+
+  /**
+   * Validity start time
+   */
+  struct GNUNET_TIME_Timestamp valid_from;
+
+  /**
+   * Validity expiration time (how long the exchange may use it).
+   */
+  struct GNUNET_TIME_Timestamp valid_until;
+
+  /**
+   * Validity expiration time for legal disputes.
+   */
+  struct GNUNET_TIME_Timestamp valid_legal;
+};
+
+
+/**
+ * @brief Public information about a future exchange's denomination key
+ */
+struct TALER_EXCHANGE_FutureDenomPublicKey
+{
+  /**
+   * The public key
+   */
+  struct TALER_DenominationPublicKey key;
+
+  /**
+   * Signature by the security module affirming it owns this key.
+   */
+  struct TALER_SecurityModuleSignatureP denom_secmod_sig;
+
+  /**
+   * Timestamp indicating when the denomination key becomes valid
+   */
+  struct GNUNET_TIME_Timestamp valid_from;
+
+  /**
+   * Timestamp indicating when the denomination key can’t be used anymore to
+   * withdraw new coins.
+   */
+  struct GNUNET_TIME_Timestamp withdraw_valid_until;
+
+  /**
+   * Timestamp indicating when coins of this denomination become invalid.
+   */
+  struct GNUNET_TIME_Timestamp expire_deposit;
+
+  /**
+   * When do signatures with this denomination key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_deposit (by a year or more).
+   */
+  struct GNUNET_TIME_Timestamp expire_legal;
+
+  /**
+   * The value of this denomination
+   */
+  struct TALER_Amount value;
+
+  /**
+   * The applicable fee for withdrawing a coin of this denomination
+   */
+  struct TALER_Amount fee_withdraw;
+
+  /**
+   * The applicable fee to spend a coin of this denomination
+   */
+  struct TALER_Amount fee_deposit;
+
+  /**
+   * The applicable fee to melt/refresh a coin of this denomination
+   */
+  struct TALER_Amount fee_refresh;
+
+  /**
+   * The applicable fee to refund a coin of this denomination
+   */
+  struct TALER_Amount fee_refund;
+
+};
+
+
+/**
+ * @brief Information about future keys from the exchange.
+ */
+struct TALER_EXCHANGE_FutureKeys
+{
+
+  /**
+   * Array of the exchange's online signing keys.
+   */
+  struct TALER_EXCHANGE_FutureSigningPublicKey *sign_keys;
+
+  /**
+   * Array of the exchange's denomination keys.
+   */
+  struct TALER_EXCHANGE_FutureDenomPublicKey *denom_keys;
+
+  /**
+   * Public key of the signkey security module.
+   */
+  struct TALER_SecurityModulePublicKeyP signkey_secmod_public_key;
+
+  /**
+   * Public key of the RSA denomination security module.
+   */
+  struct TALER_SecurityModulePublicKeyP denom_secmod_public_key;
+
+  /**
+   * Public key of the CS denomination security module.
+   */
+  struct TALER_SecurityModulePublicKeyP denom_secmod_cs_public_key;
+
+  /**
+   * Offline master public key used by this exchange.
+   */
+  struct TALER_MasterPublicKeyP master_pub;
+
+  /**
+   * Length of the @e sign_keys array (number of valid entries).
+   */
+  unsigned int num_sign_keys;
+
+  /**
+   * Length of the @e denom_keys array.
+   */
+  unsigned int num_denom_keys;
+
+};
+
+
+/**
+ * Response from a /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementGetKeysResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Response details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details if HTTP status is #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * information about the various keys used
+       * by the exchange
+       */
+      struct TALER_EXCHANGE_FutureKeys keys;
+
+    } ok;
+  } details;
+
+};
+
+
+/**
+ * Function called with information about future keys.
+ *
+ * @param cls closure
+ * @param mgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementGetKeysCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementGetKeysResponse *mgr);
+
+
+/**
+ * @brief Handle for a GET /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementGetKeysHandle;
+
+
+/**
+ * Request future keys from the exchange.  The obtained information will be
+ * passed to the @a cb.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param cb function to call with the exchange's future keys result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementGetKeysHandle *
+TALER_EXCHANGE_get_management_keys (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  TALER_EXCHANGE_ManagementGetKeysCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_get_management_keys() operation.
+ *
+ * @param gh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_get_management_keys_cancel (
+  struct TALER_EXCHANGE_ManagementGetKeysHandle *gh);
+
+
+/**
+ * @brief Public information about a signature on an exchange's online signing 
key
+ */
+struct TALER_EXCHANGE_SigningKeySignature
+{
+  /**
+   * The signing public key
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature over this signing key by the exchange's master signature.
+   * Of purpose #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+};
+
+
+/**
+ * @brief Public information about a signature on an exchange's denomination 
key
+ */
+struct TALER_EXCHANGE_DenominationKeySignature
+{
+  /**
+   * The hash of the denomination's public key
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Signature over this denomination key by the exchange's master signature.
+   * Of purpose #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY.
+   */
+  struct TALER_MasterSignatureP master_sig;
+
+};
+
+
+/**
+ * Information needed for a POST /management/keys operation.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysData
+{
+
+  /**
+   * Array of the master signatures for the exchange's online signing keys.
+   */
+  struct TALER_EXCHANGE_SigningKeySignature *sign_sigs;
+
+  /**
+   * Array of the master signatures for the exchange's denomination keys.
+   */
+  struct TALER_EXCHANGE_DenominationKeySignature *denom_sigs;
+
+  /**
+   * Length of the @e sign_keys array (number of valid entries).
+   */
+  unsigned int num_sign_sigs;
+
+  /**
+   * Length of the @e denom_keys array.
+   */
+  unsigned int num_denom_sigs;
+};
+
+
+/**
+ * Response from a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post keys operation result.
+ *
+ * @param cls closure
+ * @param mr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementPostKeysCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementPostKeysResponse *mr);
+
+
+/**
+ * @brief Handle for a POST /management/keys request.
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle;
+
+
+/**
+ * Provide master-key signatures to the exchange.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param pkd signature data to POST
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementPostKeysHandle *
+TALER_EXCHANGE_post_management_keys (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_EXCHANGE_ManagementPostKeysData *pkd,
+  TALER_EXCHANGE_ManagementPostKeysCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_post_management_keys() operation.
+ *
+ * @param ph handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_post_management_keys_cancel (
+  struct TALER_EXCHANGE_ManagementPostKeysHandle *ph);
+
+
+/**
+ * Information needed for a POST /management/extensions operation.
+ *
+ * It represents the interface ExchangeKeysResponse as defined in
+ * https://docs.taler.net/design-documents/006-extensions.html#exchange
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsData
+{
+  const json_t *extensions;
+  struct TALER_MasterSignatureP extensions_sig;
+};
+
+
+/**
+ * Response from a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post extensions operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementPostExtensionsCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementPostExtensionsResponse *hr);
+
+/**
+ * @brief Handle for a POST /management/extensions request.
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle;
+
+
+/**
+ * Uploads the configurations of enabled extensions to the exchange, signed
+ * with the master key.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param ped signature data to POST
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementPostExtensionsHandle *
+TALER_EXCHANGE_management_post_extensions (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_EXCHANGE_ManagementPostExtensionsData *ped,
+  TALER_EXCHANGE_ManagementPostExtensionsCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_post_extensions() operation.
+ *
+ * @param ph handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_post_extensions_cancel (
+  struct TALER_EXCHANGE_ManagementPostExtensionsHandle *ph);
+
+
+/**
+ * Response from a POST /management/drain request.
+ */
+struct TALER_EXCHANGE_ManagementDrainResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the drain profits result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementDrainProfitsCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementDrainResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/drain request.
+ */
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle;
+
+
+/**
+ * Uploads the drain profits request.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param wtid wire transfer identifier to use
+ * @param amount total to transfer
+ * @param date when was the request created
+ * @param account_section configuration section identifying account to debit
+ * @param payto_uri RFC 8905 URI of the account to credit
+ * @param master_sig signature affirming the operation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementDrainProfitsHandle *
+TALER_EXCHANGE_management_drain_profits (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Timestamp date,
+  const char *account_section,
+  const char *payto_uri,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementDrainProfitsCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_drain_profits() operation.
+ *
+ * @param dp handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_drain_profits_cancel (
+  struct TALER_EXCHANGE_ManagementDrainProfitsHandle *dp);
+
+
+/**
+ * Response from a POST /management/denominations/$DENOM/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/denominations/$H_DENOM_PUB/revoke 
request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle;
+
+
+/**
+ * Inform the exchange that a denomination key was revoked.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param h_denom_pub hash of the denomination public key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *
+TALER_EXCHANGE_management_revoke_denomination_key (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementRevokeDenominationKeyCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_revoke_denomination_key() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_revoke_denomination_key_cancel (
+  struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *rh);
+
+
+/**
+ * Response from a POST /management/signkeys/$SK/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementRevokeSigningKeyCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/signkeys/$H_DENOM_PUB/revoke request.
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle;
+
+
+/**
+ * Inform the exchange that a signing key was revoked.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param exchange_pub the public signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *
+TALER_EXCHANGE_management_revoke_signing_key (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementRevokeSigningKeyCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_revoke_signing_key() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_revoke_signing_key_cancel (
+  struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *rh);
+
+
+/**
+ * Response from a POST /management/aml-officers request.
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse
+{
+  /**
+   * HTTP response data
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+};
+
+/**
+ * Function called with information about the change to
+ * an AML officer status.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementUpdateAmlOfficerResponse *hr);
+
+
+/**
+ * @brief Handle for a POST /management/aml-officers/$OFFICER_PUB request.
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer;
+
+
+/**
+ * Inform the exchange that the status of an AML officer has changed.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param officer_pub the public signing key of the officer
+ * @param officer_name name of the officer
+ * @param change_date when to affect the status change
+ * @param is_active true to enable the officer
+ * @param read_only true to only allow read-only access
+ * @param master_sig signature affirming the change
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *
+TALER_EXCHANGE_management_update_aml_officer (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const char *officer_name,
+  struct GNUNET_TIME_Timestamp change_date,
+  bool is_active,
+  bool read_only,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementUpdateAmlOfficerCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_update_aml_officer() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_update_aml_officer_cancel (
+  struct TALER_EXCHANGE_ManagementUpdateAmlOfficer *rh);
+
+
+/**
+ * Summary data about an AML decision.
+ */
+struct TALER_EXCHANGE_AmlDecisionSummary
+{
+  /**
+   * What is the current monthly threshold.
+   */
+  struct TALER_Amount threshold;
+
+  /**
+   * Account the decision was made for.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * RowID of this decision.
+   */
+  uint64_t rowid;
+
+  /**
+   * Current decision state.
+   */
+  enum TALER_AmlDecisionState current_state;
+};
+
+
+/**
+ * Information about AML decisions returned by the exchange.
+ */
+struct TALER_EXCHANGE_AmlDecisionsResponse
+{
+  /**
+   * HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP response code.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success (#MHD_HTTP_OK).
+     */
+    struct
+    {
+
+      /**
+       * Array of AML decision summaries returned by the exchange.
+       */
+      const struct TALER_EXCHANGE_AmlDecisionSummary *decisions;
+
+      /**
+       * Length of the @e decisions array.
+       */
+      unsigned int decisions_length;
+
+    } ok;
+
+  } details;
+};
+
+
+/**
+ * Function called with summary information about
+ * AML decisions.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_LookupAmlDecisionsCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AmlDecisionsResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /aml/$OFFICER_PUB/decisions/$STATUS request.
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions;
+
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param exchange_url HTTP base URL for the exchange
+ * @param start row number starting point (exclusive rowid)
+ * @param delta number of records to return, negative for descending, positive 
for ascending from start
+ * @param state type of AML decisions to return
+ * @param officer_priv private key of the deciding AML officer
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_LookupAmlDecisions *
+TALER_EXCHANGE_lookup_aml_decisions (
+  struct GNUNET_CURL_Context *ctx,
+  const char *exchange_url,
+  uint64_t start,
+  int delta,
+  enum TALER_AmlDecisionState state,
+  const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+  TALER_EXCHANGE_LookupAmlDecisionsCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_lookup_aml_decisions() operation.
+ *
+ * @param lh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_lookup_aml_decisions_cancel (
+  struct TALER_EXCHANGE_LookupAmlDecisions *lh);
+
+
+/**
+ * Detailed data about an AML decision.
+ */
+struct TALER_EXCHANGE_AmlDecisionDetail
+{
+  /**
+   * When was the decision made.
+   */
+  struct GNUNET_TIME_Timestamp decision_time;
+
+  /**
+   * New threshold set by this decision.
+   */
+  struct TALER_Amount new_threshold;
+
+  /**
+   * Who made the decision?
+   */
+  struct TALER_AmlOfficerPublicKeyP decider_pub;
+
+  /**
+   * Justification given for the decision.
+   */
+  const char *justification;
+
+  /**
+   * New decision state.
+   */
+  enum TALER_AmlDecisionState new_state;
+};
+
+
+/**
+ * Detailed data collected during a KYC process for the account.
+ */
+struct TALER_EXCHANGE_KycHistoryDetail
+{
+  /**
+   * Configuration section name of the KYC provider that contributed the data.
+   */
+  const char *provider_section;
+
+  /**
+   * The collected KYC data.
+   */
+  const json_t *attributes;
+
+  /**
+   * When was the data collection made.
+   */
+  struct GNUNET_TIME_Timestamp collection_time;
+
+};
+
+
+/**
+ * Information about AML decision details returned by the exchange.
+ */
+struct TALER_EXCHANGE_AmlDecisionResponse
+{
+  /**
+   * HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP response code.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success (#MHD_HTTP_OK).
+     */
+    struct
+    {
+
+      /**
+       * Array of AML decision details returned by the exchange.
+       */
+      const struct TALER_EXCHANGE_AmlDecisionDetail *aml_history;
+
+      /**
+       * Length of the @e aml_history array.
+       */
+      unsigned int aml_history_length;
+
+      /**
+       * Array of KYC data collections returned by the exchange.
+       */
+      const struct TALER_EXCHANGE_KycHistoryDetail *kyc_attributes;
+
+      /**
+       * Length of the @e kyc_attributes array.
+       */
+      unsigned int kyc_attributes_length;
+
+    } ok;
+
+  } details;
+};
+
+
+/**
+ * Function called with summary information about
+ * AML decisions.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_LookupAmlDecisionCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AmlDecisionResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /aml/$OFFICER_PUB/decision/$H_PAYTO request.
+ */
+struct TALER_EXCHANGE_LookupAmlDecision;
+
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param exchange_url HTTP base URL for the exchange
+ * @param h_payto which account to return the decision history for
+ * @param officer_priv private key of the deciding AML officer
+ * @param history true to return the full history, otherwise only the last 
decision
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_LookupAmlDecision *
+TALER_EXCHANGE_lookup_aml_decision (
+  struct GNUNET_CURL_Context *ctx,
+  const char *exchange_url,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+  bool history,
+  TALER_EXCHANGE_LookupAmlDecisionCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_lookup_aml_decision() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_lookup_aml_decision_cancel (
+  struct TALER_EXCHANGE_LookupAmlDecision *rh);
+
+
+/**
+ * @brief Handle for a POST /aml-decision/$OFFICER_PUB request.
+ */
+struct TALER_EXCHANGE_AddAmlDecision;
+
+
+/**
+ * Response when making an AML decision.
+ */
+struct TALER_EXCHANGE_AddAmlDecisionResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about storing an
+ * an AML decision.
+ *
+ * @param cls closure
+ * @param adr response data
+ */
+typedef void
+(*TALER_EXCHANGE_AddAmlDecisionCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AddAmlDecisionResponse *adr);
+
+/**
+ * Inform the exchange that an AML decision has been taken.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param justification human-readable justification
+ * @param decision_time when was the decision made
+ * @param new_threshold at what monthly amount threshold
+ *                      should a revision be triggered
+ * @param h_payto payto URI hash of the account the
+ *                      decision is about
+ * @param new_state updated AML state
+ * @param kyc_requirements JSON array of KYC requirements being imposed, NULL 
for none
+ * @param officer_priv private key of the deciding AML officer
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AddAmlDecision *
+TALER_EXCHANGE_add_aml_decision (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const char *justification,
+  struct GNUNET_TIME_Timestamp decision_time,
+  const struct TALER_Amount *new_threshold,
+  const struct TALER_PaytoHashP *h_payto,
+  enum TALER_AmlDecisionState new_state,
+  const json_t *kyc_requirements,
+  const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
+  TALER_EXCHANGE_AddAmlDecisionCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_add_aml_decision() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_add_aml_decision_cancel (
+  struct TALER_EXCHANGE_AddAmlDecision *rh);
+
+
+/**
+ * Response when adding a partner exchange.
+ */
+struct TALER_EXCHANGE_ManagementAddPartnerResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the change to
+ * an AML officer status.
+ *
+ * @param cls closure
+ * @param apr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAddPartnerCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementAddPartnerResponse *apr);
+
+
+/**
+ * @brief Handle for a POST /management/partners/$PARTNER_PUB request.
+ */
+struct TALER_EXCHANGE_ManagementAddPartner;
+
+
+/**
+ * Inform the exchange that the status of a partnering
+ * exchange was defined.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param partner_pub the offline signing key of the partner
+ * @param start_date validity period start
+ * @param end_date validity period end
+ * @param wad_frequency how often will we do wad transfers to this partner
+ * @param wad_fee what is the wad fee to this partner
+ * @param partner_base_url what is the base URL of the @a partner_pub exchange
+ * @param master_sig the signature the signature
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAddPartner *
+TALER_EXCHANGE_management_add_partner (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_MasterPublicKeyP *partner_pub,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  struct GNUNET_TIME_Relative wad_frequency,
+  const struct TALER_Amount *wad_fee,
+  const char *partner_base_url,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementAddPartnerCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_add_partner() operation.
+ *
+ * @param rh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_add_partner_cancel (
+  struct TALER_EXCHANGE_ManagementAddPartner *rh);
+
+
+/**
+ * Response when enabling an auditor.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the auditor setup operation result.
+ *
+ * @param cls closure
+ * @param aer response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAuditorEnableCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementAuditorEnableResponse *aer);
+
+
+/**
+ * @brief Handle for a POST /management/auditors request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle;
+
+
+/**
+ * Inform the exchange that an auditor should be enable or enabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param auditor_pub the public signing key of the auditor
+ * @param auditor_url base URL of the auditor
+ * @param auditor_name human readable name for the auditor
+ * @param validity_start when was this decided?
+ * @param master_sig signature affirming the auditor addition
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAuditorEnableHandle *
+TALER_EXCHANGE_management_enable_auditor (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  const char *auditor_name,
+  struct GNUNET_TIME_Timestamp validity_start,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementAuditorEnableCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_auditor() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_enable_auditor_cancel (
+  struct TALER_EXCHANGE_ManagementAuditorEnableHandle *ah);
+
+/**
+ * Response when disabling an auditor.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the auditor disable operation result.
+ *
+ * @param cls closure
+ * @param adr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementAuditorDisableCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementAuditorDisableResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /management/auditors/$AUDITOR_PUB/disable request.
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle;
+
+
+/**
+ * Inform the exchange that an auditor should be disabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param auditor_pub the public signing key of the auditor
+ * @param validity_end when was this decided?
+ * @param master_sig signature affirming the auditor addition
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementAuditorDisableHandle *
+TALER_EXCHANGE_management_disable_auditor (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  struct GNUNET_TIME_Timestamp validity_end,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementAuditorDisableCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_disable_auditor() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_disable_auditor_cancel (
+  struct TALER_EXCHANGE_ManagementAuditorDisableHandle *ah);
+
+
+/**
+ * Response from an exchange account/enable operation.
+ */
+struct TALER_EXCHANGE_ManagementWireEnableResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the wire enable operation result.
+ *
+ * @param cls closure
+ * @param wer HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementWireEnableCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementWireEnableResponse *wer);
+
+
+/**
+ * @brief Handle for a POST /management/wire request.
+ */
+struct TALER_EXCHANGE_ManagementWireEnableHandle;
+
+
+/**
+ * Inform the exchange that a wire account should be enabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param payto_uri RFC 8905 URI of the exchange's bank account
+ * @param conversion_url URL of the conversion service, or NULL if none
+ * @param debit_restrictions JSON encoding of debit restrictions on the 
account; see AccountRestriction in the spec
+ * @param credit_restrictions JSON encoding of credit restrictions on the 
account; see AccountRestriction in the spec
+ * @param validity_start when was this decided?
+ * @param master_sig1 signature affirming the wire addition
+ *        of purpose #TALER_SIGNATURE_MASTER_ADD_WIRE
+ * @param master_sig2 signature affirming the validity of the account for 
clients;
+ *        of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementWireEnableHandle *
+TALER_EXCHANGE_management_enable_wire (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  struct GNUNET_TIME_Timestamp validity_start,
+  const struct TALER_MasterSignatureP *master_sig1,
+  const struct TALER_MasterSignatureP *master_sig2,
+  TALER_EXCHANGE_ManagementWireEnableCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param wh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_enable_wire_cancel (
+  struct TALER_EXCHANGE_ManagementWireEnableHandle *wh);
+
+
+/**
+ * Response from an exchange account/disable operation.
+ */
+struct TALER_EXCHANGE_ManagementWireDisableResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the wire disable operation result.
+ *
+ * @param cls closure
+ * @param wdr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementWireDisableCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementWireDisableResponse *wdr);
+
+
+/**
+ * @brief Handle for a POST /management/wire/disable request.
+ */
+struct TALER_EXCHANGE_ManagementWireDisableHandle;
+
+
+/**
+ * Inform the exchange that a wire account should be disabled.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param payto_uri RFC 8905 URI of the exchange's bank account
+ * @param validity_end when was this decided?
+ * @param master_sig signature affirming the wire addition
+ *        of purpose #TALER_SIGNATURE_MASTER_DEL_WIRE
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementWireDisableHandle *
+TALER_EXCHANGE_management_disable_wire (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp validity_end,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementWireDisableCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_disable_wire() operation.
+ *
+ * @param wh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_disable_wire_cancel (
+  struct TALER_EXCHANGE_ManagementWireDisableHandle *wh);
+
+
+/**
+ * Response when setting wire fees.
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+/**
+ * Function called with information about the wire enable operation result.
+ *
+ * @param cls closure
+ * @param wfr response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementSetWireFeeCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementSetWireFeeResponse *wfr);
+
+
+/**
+ * @brief Handle for a POST /management/wire-fees request.
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle;
+
+
+/**
+ * Inform the exchange about future wire fees.
+ *
+ * @param ctx the context
+ * @param exchange_base_url HTTP base URL for the exchange
+ * @param wire_method for which wire method are fees provided
+ * @param validity_start start date for the provided wire fees
+ * @param validity_end end date for the provided wire fees
+ * @param fees the wire fees for this time period
+ * @param master_sig signature affirming the wire fees;
+ *        of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementSetWireFeeHandle *
+TALER_EXCHANGE_management_set_wire_fees (
+  struct GNUNET_CURL_Context *ctx,
+  const char *exchange_base_url,
+  const char *wire_method,
+  struct GNUNET_TIME_Timestamp validity_start,
+  struct GNUNET_TIME_Timestamp validity_end,
+  const struct TALER_WireFeeSet *fees,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementSetWireFeeCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param swfh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_set_wire_fees_cancel (
+  struct TALER_EXCHANGE_ManagementSetWireFeeHandle *swfh);
+
+
+/**
+ * Response when setting global fees.
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the global fee setting operation 
result.
+ *
+ * @param cls closure
+ * @param gfr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ManagementSetGlobalFeeCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementSetGlobalFeeResponse *gfr);
+
+
+/**
+ * @brief Handle for a POST /management/global-fees request.
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle;
+
+
+/**
+ * Inform the exchange about global fees.
+ *
+ * @param ctx the context
+ * @param exchange_base_url HTTP base URL for the exchange
+ * @param validity_start start date for the provided wire fees
+ * @param validity_end end date for the provided wire fees
+ * @param fees the wire fees for this time period
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param master_sig signature affirming the wire fees;
+ *        of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *
+TALER_EXCHANGE_management_set_global_fees (
+  struct GNUNET_CURL_Context *ctx,
+  const char *exchange_base_url,
+  struct GNUNET_TIME_Timestamp validity_start,
+  struct GNUNET_TIME_Timestamp validity_end,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  const struct TALER_MasterSignatureP *master_sig,
+  TALER_EXCHANGE_ManagementSetGlobalFeeCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_management_enable_wire() operation.
+ *
+ * @param sgfh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_management_set_global_fees_cancel (
+  struct TALER_EXCHANGE_ManagementSetGlobalFeeHandle *sgfh);
+
+
+/**
+ * Response when adding denomination signature by auditor.
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationResponse
+{
+  /**
+   * HTTP response data.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the POST
+ * /auditor/$AUDITOR_PUB/$H_DENOM_PUB operation result.
+ *
+ * @param cls closure
+ * @param adr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_AuditorAddDenominationCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AuditorAddDenominationResponse *adr);
+
+
+/**
+ * @brief Handle for a POST /auditor/$AUDITOR_PUB/$H_DENOM_PUB request.
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationHandle;
+
+
+/**
+ * Provide auditor signatures for a denomination to the exchange.
+ *
+ * @param ctx the context
+ * @param url HTTP base URL for the exchange
+ * @param h_denom_pub hash of the public key of the denomination
+ * @param auditor_pub public key of the auditor
+ * @param auditor_sig signature of the auditor, of
+ *         purpose #TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AuditorAddDenominationHandle *
+TALER_EXCHANGE_add_auditor_denomination (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const struct TALER_AuditorSignatureP *auditor_sig,
+  TALER_EXCHANGE_AuditorAddDenominationCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_add_auditor_denomination() operation.
+ *
+ * @param ah handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_add_auditor_denomination_cancel (
+  struct TALER_EXCHANGE_AuditorAddDenominationHandle *ah);
+
+
+/* ********************* W2W API ****************** */
+
+
+/**
+ * Response generated for a contract get request.
+ */
+struct TALER_EXCHANGE_ContractGetResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP status code.
+   */
+  union
+  {
+    /**
+     * Information returned on #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * Public key of the purse.
+       */
+      struct TALER_PurseContractPublicKeyP purse_pub;
+
+      /**
+       * Encrypted contract.
+       */
+      const void *econtract;
+
+      /**
+       * Number of bytes in @e econtract.
+       */
+      size_t econtract_size;
+
+    } ok;
+
+  } details;
+
+};
+
+/**
+ * Function called with information about the a purse.
+ *
+ * @param cls closure
+ * @param cgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ContractGetCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ContractGetResponse *cgr);
+
+
+/**
+ * @brief Handle for a GET /contracts/$CPUB request.
+ */
+struct TALER_EXCHANGE_ContractsGetHandle;
+
+
+/**
+ * Request information about a contract from the exchange.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param contract_priv private key of the contract
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_ContractsGetHandle *
+TALER_EXCHANGE_contract_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  TALER_EXCHANGE_ContractGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_contract_get() operation.
+ *
+ * @param cgh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_contract_get_cancel (
+  struct TALER_EXCHANGE_ContractsGetHandle *cgh);
+
+
+/**
+ * Response generated for a purse get request.
+ */
+struct TALER_EXCHANGE_PurseGetResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Response on #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * Time when the purse was merged (or zero if it
+       * was not merged).
+       */
+      struct GNUNET_TIME_Timestamp merge_timestamp;
+
+      /**
+       * Time when the full amount was deposited into
+       * the purse (or zero if a sufficient amount
+       * was not yet deposited).
+       */
+      struct GNUNET_TIME_Timestamp deposit_timestamp;
+
+      /**
+       * Reserve balance (how much was deposited in
+       * total into the reserve, minus deposit fees).
+       */
+      struct TALER_Amount balance;
+
+      /**
+       * Time when the purse will expire.
+       */
+      struct GNUNET_TIME_Timestamp purse_expiration;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Function called with information about the a purse.
+ *
+ * @param cls closure
+ * @param pgr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseGetCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_PurseGetResponse *pgr);
+
+
+/**
+ * @brief Handle for a GET /purses/$PPUB request.
+ */
+struct TALER_EXCHANGE_PurseGetHandle;
+
+
+/**
+ * Request information about a purse from the exchange.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_pub public key of the purse
+ * @param timeout how long to wait for a change to happen
+ * @param wait_for_merge true to wait for a merge event, otherwise wait for a 
deposit event
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseGetHandle *
+TALER_EXCHANGE_purse_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Relative timeout,
+  bool wait_for_merge,
+  TALER_EXCHANGE_PurseGetCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_get() operation.
+ *
+ * @param pgh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_get_cancel (
+  struct TALER_EXCHANGE_PurseGetHandle *pgh);
+
+
+/**
+ * Response generated for a purse creation request.
+ */
+struct TALER_EXCHANGE_PurseCreateDepositResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP status.
+   */
+  union
+  {
+
+    /**
+     * Detailed returned on #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Signing key used by the exchange to sign the
+       * purse create with deposit confirmation.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * Signature from the exchange on the
+       * purse create with deposit confirmation.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+
+    } ok;
+
+  } details;
+
+};
+
+/**
+ * Function called with information about the creation
+ * of a new purse.
+ *
+ * @param cls closure
+ * @param pcr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseCreateDepositCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_PurseCreateDepositResponse *pcr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/create request.
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle;
+
+
+/**
+ * Information about a coin to be deposited into a purse or reserve.
+ */
+struct TALER_EXCHANGE_PurseDeposit
+{
+  /**
+   * Age commitment data, might be NULL.
+   */
+  const struct TALER_AgeCommitmentProof *age_commitment_proof;
+
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Signature proving the validity of the coin.
+   */
+  struct TALER_DenominationSignature denom_sig;
+
+  /**
+   * Hash of the denomination's public key.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Amount of the coin to transfer into the purse.
+   */
+  struct TALER_Amount amount;
+
+};
+
+
+/**
+ * Inform the exchange that a purse should be created
+ * and coins deposited into it.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_priv private key of the purse
+ * @param merge_priv the merge credential
+ * @param contract_priv key needed to obtain and decrypt the contract
+ * @param contract_terms contract the purse is about
+ * @param num_deposits length of the @a deposits array
+ * @param deposits array of deposits to make into the purse
+ * @param upload_contract true to upload the contract; must
+ *        be FALSE for repeated calls to this API for the
+ *        same purse (i.e. when adding more deposits).
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseCreateDepositHandle *
+TALER_EXCHANGE_purse_create_with_deposit (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const json_t *contract_terms,
+  unsigned int num_deposits,
+  const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+  bool upload_contract,
+  TALER_EXCHANGE_PurseCreateDepositCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_create_with_deposit() operation.
+ *
+ * @param pch handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_create_with_deposit_cancel (
+  struct TALER_EXCHANGE_PurseCreateDepositHandle *pch);
+
+
+/**
+ * Response generated for a purse deletion request.
+ */
+struct TALER_EXCHANGE_PurseDeleteResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+};
+
+
+/**
+ * Function called with information about the deletion
+ * of a purse.
+ *
+ * @param cls closure
+ * @param pdr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseDeleteCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_PurseDeleteResponse *pdr);
+
+
+/**
+ * @brief Handle for a DELETE /purses/$PID request.
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle;
+
+
+/**
+ * Asks the exchange to delete a purse. Will only succeed if
+ * the purse was not yet merged and did not yet time out.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param purse_priv private key of the purse
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseDeleteHandle *
+TALER_EXCHANGE_purse_delete (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  TALER_EXCHANGE_PurseDeleteCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_delete() operation.
+ *
+ * @param pdh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_delete_cancel (
+  struct TALER_EXCHANGE_PurseDeleteHandle *pdh);
+
+
+/**
+ * Response generated for an account merge request.
+ */
+struct TALER_EXCHANGE_AccountMergeResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Reserve signature affirming the merge.
+   */
+  const struct TALER_ReserveSignatureP *reserve_sig;
+
+  /**
+   * Details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Detailed returned on #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Signature by the exchange affirming the merge.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * Online signing key used by the exchange.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * Timestamp of the exchange for @e exchange_sig.
+       */
+      struct GNUNET_TIME_Timestamp etime;
+
+    } ok;
+
+    /**
+     * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+      /**
+       * Requirement row target that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t requirement_row;
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+/**
+ * Function called with information about an account merge
+ * operation.
+ *
+ * @param cls closure
+ * @param amr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_AccountMergeCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_AccountMergeResponse *amr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/merge request.
+ */
+struct TALER_EXCHANGE_AccountMergeHandle;
+
+
+/**
+ * Inform the exchange that a purse should be merged
+ * with a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_exchange_url base URL of the exchange with the reserve
+ * @param reserve_priv private key of the reserve to merge into
+ * @param purse_pub public key of the purse to merge
+ * @param merge_priv private key granting us the right to merge
+ * @param h_contract_terms hash of the purses' contract
+ * @param min_age minimum age of deposits into the purse
+ * @param purse_value_after_fees amount that should be in the purse
+ * @param purse_expiration when will the purse expire
+ * @param merge_timestamp when is the merge happening (current time)
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_AccountMergeHandle *
+TALER_EXCHANGE_account_merge (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const char *reserve_exchange_url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint8_t min_age,
+  const struct TALER_Amount *purse_value_after_fees,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  TALER_EXCHANGE_AccountMergeCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_account_merge() operation.
+ *
+ * @param amh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_account_merge_cancel (
+  struct TALER_EXCHANGE_AccountMergeHandle *amh);
+
+
+/**
+ * Response generated for a purse creation request.
+ */
+struct TALER_EXCHANGE_PurseCreateMergeResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Reserve signature generated for the request
+   * (client-side).
+   */
+  const struct TALER_ReserveSignatureP *reserve_sig;
+
+  /**
+   * Details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Details returned on #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+    } ok;
+
+    /**
+   * Details if the status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+   */
+    struct
+    {
+      /**
+       * Requirement row that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t requirement_row;
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+/**
+ * Function called with information about the creation
+ * of a new purse.
+ *
+ * @param cls closure
+ * @param pcr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseCreateMergeCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_PurseCreateMergeResponse *pcr);
+
+
+/**
+ * @brief Handle for a POST /reserves/$RID/purse request.
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle;
+
+
+/**
+ * Inform the exchange that a purse should be created
+ * and merged with a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve
+ * @param purse_priv private key of the purse
+ * @param merge_priv private key of the merge capability
+ * @param contract_priv private key to get the contract
+ * @param contract_terms contract the purse is about
+ * @param upload_contract true to upload the contract
+ * @param pay_for_purse true to pay for purse creation
+ * @param merge_timestamp when should the merge happen (use current time)
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseCreateMergeHandle *
+TALER_EXCHANGE_purse_create_with_merge (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const json_t *contract_terms,
+  bool upload_contract,
+  bool pay_for_purse,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  TALER_EXCHANGE_PurseCreateMergeCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_create_with_merge() operation.
+ *
+ * @param pcm handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_create_with_merge_cancel (
+  struct TALER_EXCHANGE_PurseCreateMergeHandle *pcm);
+
+
+/**
+ * Response generated for purse deposit request.
+ */
+struct TALER_EXCHANGE_PurseDepositResponse
+{
+  /**
+   * Full HTTP response.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on the HTTP status.
+   */
+  union
+  {
+    /**
+     * Detailed returned on #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * When does the purse expire.
+       */
+      struct GNUNET_TIME_Timestamp purse_expiration;
+
+      /**
+       * How much was actually deposited into the purse.
+       */
+      struct TALER_Amount total_deposited;
+
+      /**
+       * How much should be in the purse in total in the end.
+       */
+      struct TALER_Amount purse_value_after_fees;
+
+      /**
+       * Hash of the contract (needed to verify signature).
+       */
+      struct TALER_PrivateContractHashP h_contract_terms;
+
+    } ok;
+  } details;
+
+};
+
+/**
+ * Function called with information about a purse-deposit
+ * operation.
+ *
+ * @param cls closure
+ * @param pdr HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_PurseDepositCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_PurseDepositResponse *pdr);
+
+
+/**
+ * @brief Handle for a POST /purses/$PID/deposit request.
+ */
+struct TALER_EXCHANGE_PurseDepositHandle;
+
+
+/**
+ * Inform the exchange that a deposit should be made into
+ * a purse.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param purse_exchange_url base URL of the exchange hosting the purse
+ * @param purse_pub public key of the purse to merge
+ * @param min_age minimum age we need to prove for the purse
+ * @param num_deposits length of the @a deposits array
+ * @param deposits array of deposits to make into the purse
+ * @param cb function to call with the exchange's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_EXCHANGE_PurseDepositHandle *
+TALER_EXCHANGE_purse_deposit (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const char *purse_exchange_url,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  uint8_t min_age,
+  unsigned int num_deposits,
+  const struct TALER_EXCHANGE_PurseDeposit deposits[static num_deposits],
+  TALER_EXCHANGE_PurseDepositCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel #TALER_EXCHANGE_purse_deposit() operation.
+ *
+ * @param amh handle of the operation to cancel
+ */
+void
+TALER_EXCHANGE_purse_deposit_cancel (
+  struct TALER_EXCHANGE_PurseDepositHandle *amh);
+
+
+/* *********************  /reserves/$RID/open *********************** */
+
+
+/**
+ * @brief A /reserves/$RID/open Handle
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle;
+
+
+/**
+ * @brief Reserve open result details.
+ */
+struct TALER_EXCHANGE_ReserveOpenResult
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+      /**
+       * New expiration time
+       */
+      struct GNUNET_TIME_Timestamp expiration_time;
+
+      /**
+       * Actual cost of the open operation.
+       */
+      struct TALER_Amount open_cost;
+
+    } ok;
+
+
+    /**
+     * Information returned if the payment provided is insufficient, if
+     * @e hr.http_status is #MHD_HTTP_PAYMENT_REQUIRED
+     */
+    struct
+    {
+      /**
+       * Current expiration time of the reserve.
+       */
+      struct GNUNET_TIME_Timestamp expiration_time;
+
+      /**
+       * Actual cost of the open operation that should have been paid.
+       */
+      struct TALER_Amount open_cost;
+
+    } payment_required;
+
+
+    /**
+     * Information returned if KYC is required to proceed, set if
+     * @e hr.http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+      /**
+       * Requirement row that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t requirement_row;
+
+      /**
+       * Hash of the payto-URI of the account to KYC;
+       */
+      struct TALER_PaytoHashP h_payto;
+
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve open request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesOpenCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveOpenResult *ror);
+
+
+/**
+ * Submit a request to open a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param keys exchange keys
+ * @param reserve_priv private key of the reserve to open
+ * @param reserve_contribution amount to pay from the reserve's balance for 
the operation
+ * @param coin_payments_length length of the @a coin_payments array
+ * @param coin_payments array of coin payments to use for opening the reserve
+ * @param expiration_time desired new expiration time for the reserve
+ * @param min_purses minimum number of purses to allow being concurrently 
opened per reserve
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle *
+TALER_EXCHANGE_reserves_open (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_Amount *reserve_contribution,
+  unsigned int coin_payments_length,
+  const struct TALER_EXCHANGE_PurseDeposit coin_payments[
+    static coin_payments_length],
+  struct GNUNET_TIME_Timestamp expiration_time,
+  uint32_t min_purses,
+  TALER_EXCHANGE_ReservesOpenCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve status request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param[in] roh the reserve open request handle
+ */
+void
+TALER_EXCHANGE_reserves_open_cancel (
+  struct TALER_EXCHANGE_ReservesOpenHandle *roh);
+
+
+/* *********************  /reserves/$RID/attest *********************** */
+
+
+/**
+ * @brief A Get /reserves/$RID/attest Handle
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle;
+
+
+/**
+ * @brief Reserve GET attest result details.
+ */
+struct TALER_EXCHANGE_ReserveGetAttestResult
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+
+      /**
+       * Length of the @e attributes array.
+       */
+      unsigned int attributes_length;
+
+      /**
+       * Array of attributes available about the user.
+       */
+      const char **attributes;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve attest request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesGetAttestCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveGetAttestResult *ror);
+
+
+/**
+ * Submit a request to get the list of attestable attributes for a reserve.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param reserve_pub public key of the reserve to get available attributes for
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesGetAttestHandle *
+TALER_EXCHANGE_reserves_get_attestable (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  TALER_EXCHANGE_ReservesGetAttestCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a request to get attestable attributes.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rgah the reserve get attestable request handle
+ */
+void
+TALER_EXCHANGE_reserves_get_attestable_cancel (
+  struct TALER_EXCHANGE_ReservesGetAttestHandle *rgah);
+
+
+/**
+ * @brief A POST /reserves/$RID/attest Handle
+ */
+struct TALER_EXCHANGE_ReservesPostAttestHandle;
+
+
+/**
+ * @brief Reserve attest result details.
+ */
+struct TALER_EXCHANGE_ReservePostAttestResult
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+      /**
+       * Time when the exchange made the signature.
+       */
+      struct GNUNET_TIME_Timestamp exchange_time;
+
+      /**
+       * Expiration time of the attested attributes.
+       */
+      struct GNUNET_TIME_Timestamp expiration_time;
+
+      /**
+       * Signature by the exchange affirming the attributes.
+       */
+      struct TALER_ExchangeSignatureP exchange_sig;
+
+      /**
+       * Online signing key used by the exchange.
+       */
+      struct TALER_ExchangePublicKeyP exchange_pub;
+
+      /**
+       * Attributes being confirmed by the exchange.
+       */
+      const json_t *attributes;
+
+    } ok;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve attest request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesPostAttestCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReservePostAttestResult *ror);
+
+
+/**
+ * Submit a request to attest attributes about the owner of a reserve.
+ *
+ * @param ctx CURL context
+ * @param url exchange base URL
+ * @param reserve_priv private key of the reserve to attest
+ * @param attributes_length length of the @a attributes array
+ * @param attributes array of names of attributes to get attestations for
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesAttestHandle *
+TALER_EXCHANGE_reserves_attest (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  unsigned int attributes_length,
+  const char *attributes[const static attributes_length],
+  TALER_EXCHANGE_ReservesPostAttestCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve attestation request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rah the reserve attest request handle
+ */
+void
+TALER_EXCHANGE_reserves_attest_cancel (
+  struct TALER_EXCHANGE_ReservesAttestHandle *rah);
+
+
+/* *********************  /reserves/$RID/close *********************** */
+
+
+/**
+ * @brief A /reserves/$RID/close Handle
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle;
+
+
+/**
+ * @brief Reserve close result details.
+ */
+struct TALER_EXCHANGE_ReserveCloseResult
+{
+
+  /**
+   * High-level HTTP response details.
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Details depending on @e hr.http_status.
+   */
+  union
+  {
+
+    /**
+     * Information returned on success, if
+     * @e hr.http_status is #MHD_HTTP_OK
+     */
+    struct
+    {
+
+      /**
+       * Amount wired to the target account.
+       */
+      struct TALER_Amount wire_amount;
+    } ok;
+
+    /**
+     * Information returned if KYC is required to proceed, set if
+     * @e hr.http_status is #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS.
+     */
+    struct
+    {
+      /**
+       * Requirement row that the merchant should use
+       * to check for its KYC status.
+       */
+      uint64_t requirement_row;
+
+      /**
+       * Hash of the payto-URI of the account to KYC;
+       */
+      struct TALER_PaytoHashP h_payto;
+
+    } unavailable_for_legal_reasons;
+
+  } details;
+
+};
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * reserve close request to a exchange.
+ *
+ * @param cls closure
+ * @param ror HTTP response data
+ */
+typedef void
+(*TALER_EXCHANGE_ReservesCloseCallback) (
+  void *cls,
+  const struct TALER_EXCHANGE_ReserveCloseResult *ror);
+
+
+/**
+ * Submit a request to close a reserve.
+ *
+ * @param ctx curl context
+ * @param url exchange base URL
+ * @param reserve_priv private key of the reserve to close
+ * @param target_payto_uri where to send the payment, NULL to send to reserve 
origin
+ * @param cb the callback to call when a reply for this request is available
+ * @param cb_cls closure for the above callback
+ * @return a handle for this request; NULL if the inputs are invalid (i.e.
+ *         signatures fail to verify).  In this case, the callback is not 
called.
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle *
+TALER_EXCHANGE_reserves_close (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const char *target_payto_uri,
+  TALER_EXCHANGE_ReservesCloseCallback cb,
+  void *cb_cls);
+
+
+/**
+ * Cancel a reserve status request.  This function cannot be used
+ * on a request handle if a response is already served for it.
+ *
+ * @param rch the reserve request handle
+ */
+void
+TALER_EXCHANGE_reserves_close_cancel (
+  struct TALER_EXCHANGE_ReservesCloseHandle *rch);
+
+#endif  /* _TALER_EXCHANGE_SERVICE_H */
diff --git a/src/include/taler_exchangedb_lib.h 
b/src/include/taler_exchangedb_lib.h
new file mode 100644
index 0000000..17b01b0
--- /dev/null
+++ b/src/include/taler_exchangedb_lib.h
@@ -0,0 +1,199 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 include/taler_exchangedb_lib.h
+ * @brief IO operations for the exchange's private keys
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGEDB_LIB_H
+#define TALER_EXCHANGEDB_LIB_H
+
+#include "taler_signatures.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_bank_service.h"
+
+
+/**
+ * Initialize the plugin.
+ *
+ * @param cfg configuration to use
+ * @return NULL on failure
+ */
+struct TALER_EXCHANGEDB_Plugin *
+TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Shutdown the plugin.
+ *
+ * @param plugin plugin to unload
+ */
+void
+TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin);
+
+/**
+ * Information about an account from the configuration.
+ */
+struct TALER_EXCHANGEDB_AccountInfo
+{
+  /**
+   * Authentication data. Only parsed if
+   * #TALER_EXCHANGEDB_ALO_AUTHDATA was set.
+   */
+  const struct TALER_BANK_AuthenticationData *auth;
+
+  /**
+   * Section in the configuration file that specifies the
+   * account. Must start with "exchange-account-".
+   */
+  const char *section_name;
+
+  /**
+   * Name of the wire method used by this account.
+   */
+  const char *method;
+
+  /**
+   * true if this account is enabed to be debited
+   * by the taler-exchange-aggregator.
+   */
+  bool debit_enabled;
+
+  /**
+   * true if this account is enabed to be credited by wallets
+   * and needs to be watched by the taler-exchange-wirewatch.
+   * Also, the account will only be included in /wire if credit
+   * is enabled.
+   */
+  bool credit_enabled;
+};
+
+
+/**
+ * Calculate the total value of all transactions performed.
+ * Stores @a off plus the cost of all transactions in @a tl
+ * in @a ret.
+ *
+ * @param tl transaction list to process
+ * @param off offset to use as the starting value
+ * @param[out] ret where the resulting total is to be stored
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGEDB_calculate_transaction_list_totals (
+  struct TALER_EXCHANGEDB_TransactionList *tl,
+  const struct TALER_Amount *off,
+  struct TALER_Amount *ret);
+
+
+/**
+ * Function called with information about a wire account.
+ *
+ * @param cls closure
+ * @param ai account information
+ */
+typedef void
+(*TALER_EXCHANGEDB_AccountCallback)(
+  void *cls,
+  const struct TALER_EXCHANGEDB_AccountInfo *ai);
+
+
+/**
+ * Return information about all accounts that
+ * were loaded by #TALER_EXCHANGEDB_load_accounts().
+ *
+ * @param cb callback to invoke
+ * @param cb_cls closure for @a cb
+ */
+void
+TALER_EXCHANGEDB_find_accounts (TALER_EXCHANGEDB_AccountCallback cb,
+                                void *cb_cls);
+
+
+/**
+ * Find the wire plugin for the given payto:// URL.
+ * Only useful after the accounts have been loaded
+ * using #TALER_EXCHANGEDB_load_accounts().
+ *
+ * @param method wire method we need an account for
+ * @return NULL on error
+ */
+const struct TALER_EXCHANGEDB_AccountInfo *
+TALER_EXCHANGEDB_find_account_by_method (const char *method);
+
+
+/**
+ * Find the wire plugin for the given payto:// URL
+ * Only useful after the accounts have been loaded
+ * using #TALER_EXCHANGEDB_load_accounts().
+ *
+ * @param url wire address we need an account for
+ * @return NULL on error
+ */
+const struct TALER_EXCHANGEDB_AccountInfo *
+TALER_EXCHANGEDB_find_account_by_payto_uri (const char *url);
+
+
+/**
+ * Options for #TALER_EXCHANGEDB_load_accounts()
+ */
+enum TALER_EXCHANGEDB_AccountLoaderOptions
+{
+  TALER_EXCHANGEDB_ALO_NONE = 0,
+
+  /**
+   * Load accounts enabled for DEBITs.
+   */
+  TALER_EXCHANGEDB_ALO_DEBIT = 1,
+
+  /**
+   * Load accounts enabled for CREDITs.
+   */
+  TALER_EXCHANGEDB_ALO_CREDIT = 2,
+
+  /**
+   * Load authentication data from the
+   * "taler-accountcredentials-" section
+   * to access the account at the bank.
+   */
+  TALER_EXCHANGEDB_ALO_AUTHDATA = 4
+};
+
+
+/**
+ * Load account information opf the exchange from
+ * @a cfg.
+ *
+ * @param cfg configuration to load from
+ * @param options loader options
+ * @return #GNUNET_OK on success, #GNUNET_NO if no accounts are configured
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGEDB_load_accounts (
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  enum TALER_EXCHANGEDB_AccountLoaderOptions options);
+
+
+/**
+ * Free resources allocated by
+ * #TALER_EXCHANGEDB_load_accounts().
+ */
+void
+TALER_EXCHANGEDB_unload_accounts (void);
+
+#endif
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
new file mode 100644
index 0000000..8be26a7
--- /dev/null
+++ b/src/include/taler_exchangedb_plugin.h
@@ -0,0 +1,7133 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/taler_exchangedb_plugin.h
+ * @brief Low-level (statement-level) database access for the exchange
+ * @author Florian Dold
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGEDB_PLUGIN_H
+#define TALER_EXCHANGEDB_PLUGIN_H
+#include <jansson.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_db_lib.h>
+#include "taler_json_lib.h"
+#include "taler_signatures.h"
+#include "taler_extensions_policy.h"
+
+
+/**
+ * Per-coin information returned when doing a batch insert.
+ */
+struct TALER_EXCHANGEDB_CoinInfo
+{
+  /**
+   * Row of the coin in the known_coins table.
+   */
+  uint64_t known_coin_id;
+
+  /**
+   * Hash of the denomination, relevant on @e denom_conflict.
+   */
+  struct TALER_DenominationHashP denom_hash;
+
+  /**
+   * Hash of the age commitment, relevant on @e age_conflict.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * True if the coin was known previously.
+   */
+  bool existed;
+
+  /**
+   * True if the known coin has a different denomination;
+   * application will find denomination of the already
+   * known coin in @e denom_hash.
+   */
+  bool denom_conflict;
+
+  /**
+   * True if the known coin has a different age restriction;
+   * application will find age commitment of the already
+   * known coin in @e h_age_commitment.
+   */
+  bool age_conflict;
+};
+
+
+/**
+ * Information about a denomination key.
+ */
+struct TALER_EXCHANGEDB_DenominationKeyInformation
+{
+
+  /**
+   * Signature over this struct to affirm the validity of the key.
+   */
+  struct TALER_MasterSignatureP signature;
+
+  /**
+   * Start time of the validity period for this key.
+   */
+  struct GNUNET_TIME_Timestamp start;
+
+  /**
+   * The exchange will sign fresh coins between @e start and this time.
+   * @e expire_withdraw will be somewhat larger than @e start to
+   * ensure a sufficiently large anonymity set, while also allowing
+   * the Exchange to limit the financial damage in case of a key being
+   * compromised.  Thus, exchanges with low volume are expected to have a
+   * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+   * with high transaction volume.  The period may also differ between
+   * types of coins.  A exchange may also have a few denomination keys
+   * with the same value with overlapping validity periods, to address
+   * issues such as clock skew.
+   */
+  struct GNUNET_TIME_Timestamp expire_withdraw;
+
+  /**
+   * Coins signed with the denomination key must be spent or refreshed
+   * between @e start and this expiration time.  After this time, the
+   * exchange will refuse transactions involving this key as it will
+   * "drop" the table with double-spending information (shortly after)
+   * this time.  Note that wallets should refresh coins significantly
+   * before this time to be on the safe side.  @e expire_deposit must be
+   * significantly larger than @e expire_withdraw (by months or even
+   * years).
+   */
+  struct GNUNET_TIME_Timestamp expire_deposit;
+
+  /**
+   * When do signatures with this denomination key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_deposit (by a year or more).
+   */
+  struct GNUNET_TIME_Timestamp expire_legal;
+
+  /**
+   * The value of the coins signed with this denomination key.
+   */
+  struct TALER_Amount value;
+
+  /**
+   * Fees for the coin.
+   */
+  struct TALER_DenomFeeSet fees;
+
+  /**
+   * Hash code of the denomination public key. (Used to avoid having
+   * the variable-size RSA key in this struct.)
+   */
+  struct TALER_DenominationHashP denom_hash;
+
+  /**
+   * If denomination was setup for age restriction, non-zero age mask.
+   * Note that the mask is not part of the signature.
+   */
+  struct TALER_AgeMask age_mask;
+};
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Events signalling that a coin deposit status
+ * changed.
+ */
+struct TALER_CoinDepositEventP
+{
+  /**
+   * Of type #TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED.
+   */
+  struct GNUNET_DB_EventHeaderP header;
+
+  /**
+   * Public key of the merchant.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+};
+
+/**
+ * Events signalling a reserve got funding.
+ */
+struct TALER_ReserveEventP
+{
+  /**
+   * Of type #TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING.
+   */
+  struct GNUNET_DB_EventHeaderP header;
+
+  /**
+   * Public key of the reserve the event is about.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+};
+
+
+/**
+ * Signature of events signalling a purse changed its status.
+ */
+struct TALER_PurseEventP
+{
+  /**
+   * Of type #TALER_DBEVENT_EXCHANGE_PURSE_MERGED or
+   * #TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED.
+   */
+  struct GNUNET_DB_EventHeaderP header;
+
+  /**
+   * Public key of the purse the event is about.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+};
+
+
+/**
+ * Signature of events signalling a KYC process was completed.
+ */
+struct TALER_KycCompletedEventP
+{
+  /**
+   * Of type #TALER_DBEVENT_EXCHANGE_KYC_COMPLETED.
+   */
+  struct GNUNET_DB_EventHeaderP header;
+
+  /**
+   * Public key of the reserve the event is about.
+   */
+  struct TALER_PaytoHashP h_payto;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+/**
+ * Meta data about an exchange online signing key.
+ */
+struct TALER_EXCHANGEDB_SignkeyMetaData
+{
+  /**
+   * Start time of the validity period for this key.
+   */
+  struct GNUNET_TIME_Timestamp start;
+
+  /**
+   * The exchange will sign messages with this key between @e start and this 
time.
+   */
+  struct GNUNET_TIME_Timestamp expire_sign;
+
+  /**
+   * When do signatures with this sign key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_sign (by a year or more).
+   */
+  struct GNUNET_TIME_Timestamp expire_legal;
+
+};
+
+
+/**
+ * Enumeration of all of the tables replicated by exchange-auditor
+ * database replication.
+ */
+enum TALER_EXCHANGEDB_ReplicatedTable
+{
+  /* From exchange-0002.sql: */
+  TALER_EXCHANGEDB_RT_DENOMINATIONS,
+  TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS,
+  TALER_EXCHANGEDB_RT_WIRE_TARGETS,
+  TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES,
+  TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS,
+  TALER_EXCHANGEDB_RT_RESERVES,
+  TALER_EXCHANGEDB_RT_RESERVES_IN,
+  TALER_EXCHANGEDB_RT_RESERVES_CLOSE,
+  TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS,
+  TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS,
+  TALER_EXCHANGEDB_RT_RESERVES_OUT,
+  TALER_EXCHANGEDB_RT_AUDITORS,
+  TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS,
+  TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS,
+  TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS,
+  TALER_EXCHANGEDB_RT_KNOWN_COINS,
+  TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS,
+  TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS,
+  TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS,
+  TALER_EXCHANGEDB_RT_BATCH_DEPOSITS,
+  TALER_EXCHANGEDB_RT_COIN_DEPOSITS,
+  TALER_EXCHANGEDB_RT_REFUNDS,
+  TALER_EXCHANGEDB_RT_WIRE_OUT,
+  TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING,
+  TALER_EXCHANGEDB_RT_WIRE_FEE,
+  TALER_EXCHANGEDB_RT_GLOBAL_FEE,
+  TALER_EXCHANGEDB_RT_RECOUP,
+  TALER_EXCHANGEDB_RT_RECOUP_REFRESH,
+  TALER_EXCHANGEDB_RT_EXTENSIONS,
+  TALER_EXCHANGEDB_RT_POLICY_DETAILS,
+  TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS,
+  TALER_EXCHANGEDB_RT_PURSE_REQUESTS,
+  TALER_EXCHANGEDB_RT_PURSE_DECISION,
+  TALER_EXCHANGEDB_RT_PURSE_MERGES,
+  TALER_EXCHANGEDB_RT_PURSE_DEPOSITS,
+  TALER_EXCHANGEDB_RT_ACCOUNT_MERGES,
+  TALER_EXCHANGEDB_RT_HISTORY_REQUESTS,
+  TALER_EXCHANGEDB_RT_CLOSE_REQUESTS,
+  TALER_EXCHANGEDB_RT_WADS_OUT,
+  TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES,
+  TALER_EXCHANGEDB_RT_WADS_IN,
+  TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES,
+  TALER_EXCHANGEDB_RT_PROFIT_DRAINS,
+  /* From exchange-0003.sql: */
+  TALER_EXCHANGEDB_RT_AML_STAFF,
+  TALER_EXCHANGEDB_RT_AML_HISTORY,
+  TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES,
+  TALER_EXCHANGEDB_RT_PURSE_DELETION,
+  TALER_EXCHANGEDB_RT_AGE_WITHDRAW,
+};
+
+
+/**
+ * Record of a single entry in a replicated table.
+ */
+struct TALER_EXCHANGEDB_TableData
+{
+  /**
+   * Data of which table is returned here?
+   */
+  enum TALER_EXCHANGEDB_ReplicatedTable table;
+
+  /**
+   * Serial number of the record.
+   */
+  uint64_t serial;
+
+  /**
+   * Table-specific details.
+   */
+  union
+  {
+
+    /**
+     * Details from the 'denominations' table.
+     */
+    struct
+    {
+      uint32_t denom_type;
+      uint32_t age_mask;
+      struct TALER_DenominationPublicKey denom_pub;
+      struct TALER_MasterSignatureP master_sig;
+      struct GNUNET_TIME_Timestamp valid_from;
+      struct GNUNET_TIME_Timestamp expire_withdraw;
+      struct GNUNET_TIME_Timestamp expire_deposit;
+      struct GNUNET_TIME_Timestamp expire_legal;
+      struct TALER_Amount coin;
+      struct TALER_DenomFeeSet fees;
+    } denominations;
+
+    struct
+    {
+      struct TALER_MasterSignatureP master_sig;
+      uint64_t denominations_serial;
+    } denomination_revocations;
+
+    struct
+    {
+      char *payto_uri;
+    } wire_targets;
+
+    struct
+    {
+      struct TALER_PaytoHashP h_payto;
+      struct GNUNET_TIME_Timestamp expiration_time;
+      char *provider_section;
+      char *provider_user_id;
+      char *provider_legitimization_id;
+    } legitimization_processes;
+
+    struct
+    {
+      struct TALER_PaytoHashP h_payto;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      bool no_reserve_pub;
+      char *required_checks;
+    } legitimization_requirements;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_TIME_Timestamp expiration_date;
+      struct GNUNET_TIME_Timestamp gc_date;
+    } reserves;
+
+    struct
+    {
+      uint64_t wire_reference;
+      struct TALER_Amount credit;
+      struct TALER_PaytoHashP sender_account_h_payto;
+      char *exchange_account_section;
+      struct GNUNET_TIME_Timestamp execution_date;
+      struct TALER_ReservePublicKeyP reserve_pub;
+    } reserves_in;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_TIME_Timestamp request_timestamp;
+      struct GNUNET_TIME_Timestamp expiration_date;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_Amount reserve_payment;
+      uint32_t requested_purse_limit;
+    } reserves_open_requests;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_CoinSpendSignatureP coin_sig;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_Amount contribution;
+    } reserves_open_deposits;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_TIME_Timestamp execution_date;
+      struct TALER_WireTransferIdentifierRawP wtid;
+      struct TALER_PaytoHashP sender_account_h_payto;
+      struct TALER_Amount amount;
+      struct TALER_Amount closing_fee;
+    } reserves_close;
+
+    struct
+    {
+      struct TALER_BlindedCoinHashP h_blind_ev;
+      uint64_t denominations_serial;
+      struct TALER_BlindedDenominationSignature denom_sig;
+      uint64_t reserve_uuid;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct GNUNET_TIME_Timestamp execution_date;
+      struct TALER_Amount amount_with_fee;
+    } reserves_out;
+
+    struct
+    {
+      struct TALER_AuditorPublicKeyP auditor_pub;
+      char *auditor_url;
+      char *auditor_name;
+      bool is_active;
+      struct GNUNET_TIME_Timestamp last_change;
+    } auditors;
+
+    struct
+    {
+      uint64_t auditor_uuid;
+      uint64_t denominations_serial;
+      struct TALER_AuditorSignatureP auditor_sig;
+    } auditor_denom_sigs;
+
+    struct
+    {
+      struct TALER_ExchangePublicKeyP exchange_pub;
+      struct TALER_MasterSignatureP master_sig;
+      struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+    } exchange_sign_keys;
+
+    struct
+    {
+      uint64_t esk_serial;
+      struct TALER_MasterSignatureP master_sig;
+    } signkey_revocations;
+
+    struct
+    {
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_AgeCommitmentHash age_hash;
+      uint64_t denominations_serial;
+      struct TALER_DenominationSignature denom_sig;
+    } known_coins;
+
+    struct
+    {
+      struct TALER_RefreshCommitmentP rc;
+      struct TALER_CoinSpendPublicKeyP old_coin_pub;
+      struct TALER_CoinSpendSignatureP old_coin_sig;
+      struct TALER_Amount amount_with_fee;
+      uint32_t noreveal_index;
+    } refresh_commitments;
+
+    struct
+    {
+      uint64_t melt_serial_id;
+      uint32_t freshcoin_index;
+      struct TALER_CoinSpendSignatureP link_sig;
+      uint64_t denominations_serial;
+      void *coin_ev;
+      size_t coin_ev_size;
+      struct TALER_ExchangeWithdrawValues ewv;
+      // h_coin_ev omitted, to be recomputed!
+      struct TALER_BlindedDenominationSignature ev_sig;
+    } refresh_revealed_coins;
+
+    struct
+    {
+      uint64_t melt_serial_id;
+      struct TALER_TransferPublicKeyP tp;
+      struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA - 1];
+    } refresh_transfer_keys;
+
+    struct
+    {
+      uint64_t shard;
+      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_PrivateContractHashP h_contract_terms;
+      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;
+      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 batch_deposit_serial_id;
+      struct TALER_MerchantSignatureP merchant_sig;
+      uint64_t rtransaction_id;
+      struct TALER_Amount amount_with_fee;
+    } refunds;
+
+    struct
+    {
+      struct GNUNET_TIME_Timestamp execution_date;
+      struct TALER_WireTransferIdentifierRawP wtid_raw;
+      struct TALER_PaytoHashP wire_target_h_payto;
+      char *exchange_account_section;
+      struct TALER_Amount amount;
+    } wire_out;
+
+    struct
+    {
+      uint64_t batch_deposit_serial_id;
+      struct TALER_WireTransferIdentifierRawP wtid_raw;
+    } aggregation_tracking;
+
+    struct
+    {
+      char *wire_method;
+      struct GNUNET_TIME_Timestamp start_date;
+      struct GNUNET_TIME_Timestamp end_date;
+      struct TALER_WireFeeSet fees;
+      struct TALER_MasterSignatureP master_sig;
+    } wire_fee;
+
+    struct
+    {
+      struct GNUNET_TIME_Timestamp start_date;
+      struct GNUNET_TIME_Timestamp end_date;
+      struct TALER_GlobalFeeSet fees;
+      struct GNUNET_TIME_Relative purse_timeout;
+      struct GNUNET_TIME_Relative history_expiration;
+      uint32_t purse_account_limit;
+      struct TALER_MasterSignatureP master_sig;
+    } global_fee;
+
+    struct
+    {
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_CoinSpendSignatureP coin_sig;
+      union TALER_DenominationBlindingKeyP coin_blind;
+      struct TALER_Amount amount;
+      struct GNUNET_TIME_Timestamp timestamp;
+      uint64_t reserve_out_serial_id;
+    } recoup;
+
+    struct
+    {
+      uint64_t known_coin_id;
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_CoinSpendSignatureP coin_sig;
+      union TALER_DenominationBlindingKeyP coin_blind;
+      struct TALER_Amount amount;
+      struct GNUNET_TIME_Timestamp timestamp;
+      uint64_t rrc_serial;
+    } recoup_refresh;
+
+    struct
+    {
+      char *name;
+      char *manifest;
+    } extensions;
+
+    struct
+    {
+      struct GNUNET_HashCode hash_code;
+      json_t *policy_json;
+      bool no_policy_json;
+      struct GNUNET_TIME_Timestamp deadline;
+      struct TALER_Amount commitment;
+      struct TALER_Amount accumulated_total;
+      struct TALER_Amount fee;
+      struct TALER_Amount transferable;
+      uint16_t fulfillment_state; /* will also be recomputed */
+      uint64_t fulfillment_id;
+      bool no_fulfillment_id;
+    } policy_details;
+
+    struct
+    {
+      struct GNUNET_TIME_Timestamp fulfillment_timestamp;
+      char *fulfillment_proof;
+      struct GNUNET_HashCode h_fulfillment_proof;
+      struct GNUNET_HashCode *policy_hash_codes;
+      size_t policy_hash_codes_count;
+    } policy_fulfillments;
+
+    struct
+    {
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PurseMergePublicKeyP merge_pub;
+      struct GNUNET_TIME_Timestamp purse_creation;
+      struct GNUNET_TIME_Timestamp purse_expiration;
+      struct TALER_PrivateContractHashP h_contract_terms;
+      uint32_t age_limit;
+      uint32_t flags;
+      struct TALER_Amount amount_with_fee;
+      struct TALER_Amount purse_fee;
+      struct TALER_PurseContractSignatureP purse_sig;
+    } purse_requests;
+
+    struct
+    {
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct GNUNET_TIME_Timestamp action_timestamp;
+      bool refunded;
+    } purse_decision;
+
+    struct
+    {
+      uint64_t partner_serial_id;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PurseMergeSignatureP merge_sig;
+      struct GNUNET_TIME_Timestamp merge_timestamp;
+    } purse_merges;
+
+    struct
+    {
+      uint64_t partner_serial_id;
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct TALER_Amount amount_with_fee;
+      struct TALER_CoinSpendSignatureP coin_sig;
+    } purse_deposits;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PaytoHashP wallet_h_payto;
+    } account_merges;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct GNUNET_TIME_Timestamp request_timestamp;
+      struct TALER_Amount history_fee;
+    } history_requests;
+
+    struct
+    {
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct GNUNET_TIME_Timestamp close_timestamp;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_Amount close;
+      struct TALER_Amount close_fee;
+      char *payto_uri;
+    } close_requests;
+
+    struct
+    {
+      struct TALER_WadIdentifierP wad_id;
+      uint64_t partner_serial_id;
+      struct TALER_Amount amount;
+      struct GNUNET_TIME_Timestamp execution_time;
+    } wads_out;
+
+    struct
+    {
+      uint64_t wad_out_serial_id;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PrivateContractHashP h_contract;
+      struct GNUNET_TIME_Timestamp purse_expiration;
+      struct GNUNET_TIME_Timestamp merge_timestamp;
+      struct TALER_Amount amount_with_fee;
+      struct TALER_Amount wad_fee;
+      struct TALER_Amount deposit_fees;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_PurseContractSignatureP purse_sig;
+    } wads_out_entries;
+
+    struct
+    {
+      struct TALER_WadIdentifierP wad_id;
+      char *origin_exchange_url;
+      struct TALER_Amount amount;
+      struct GNUNET_TIME_Timestamp arrival_time;
+    } wads_in;
+
+    struct
+    {
+      uint64_t wad_in_serial_id;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PrivateContractHashP h_contract;
+      struct GNUNET_TIME_Timestamp purse_expiration;
+      struct GNUNET_TIME_Timestamp merge_timestamp;
+      struct TALER_Amount amount_with_fee;
+      struct TALER_Amount wad_fee;
+      struct TALER_Amount deposit_fees;
+      struct TALER_ReserveSignatureP reserve_sig;
+      struct TALER_PurseContractSignatureP purse_sig;
+    } wads_in_entries;
+
+    struct
+    {
+      struct TALER_WireTransferIdentifierRawP wtid;
+      char *account_section;
+      char *payto_uri;
+      struct GNUNET_TIME_Timestamp trigger_date;
+      struct TALER_Amount amount;
+      struct TALER_MasterSignatureP master_sig;
+    } profit_drains;
+
+    struct
+    {
+      struct TALER_AmlOfficerPublicKeyP decider_pub;
+      struct TALER_MasterSignatureP master_sig;
+      char *decider_name;
+      bool is_active;
+      bool read_only;
+      struct GNUNET_TIME_Timestamp last_change;
+    } aml_staff;
+
+    struct
+    {
+      struct TALER_PaytoHashP h_payto;
+      struct TALER_Amount new_threshold;
+      enum TALER_AmlDecisionState new_status;
+      struct GNUNET_TIME_Timestamp decision_time;
+      char *justification;
+      char *kyc_requirements; /* NULL allowed! */
+      uint64_t kyc_req_row;
+      struct TALER_AmlOfficerPublicKeyP decider_pub;
+      struct TALER_AmlOfficerSignatureP decider_sig;
+    } aml_history;
+
+    struct
+    {
+      struct TALER_PaytoHashP h_payto;
+      struct GNUNET_ShortHashCode kyc_prox;
+      char *provider;
+      struct GNUNET_TIME_Timestamp collection_time;
+      struct GNUNET_TIME_Timestamp expiration_time;
+      void *encrypted_attributes;
+      size_t encrypted_attributes_size;
+    } kyc_attributes;
+
+    struct
+    {
+      struct TALER_PurseContractPublicKeyP purse_pub;
+      struct TALER_PurseContractSignatureP purse_sig;
+    } purse_deletion;
+
+    struct
+    {
+      struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+      struct TALER_Amount amount_with_fee;
+      uint16_t max_age;
+      uint32_t noreveal_index;
+      struct TALER_ReservePublicKeyP reserve_pub;
+      struct TALER_ReserveSignatureP reserve_sig;
+      uint64_t num_coins;
+      uint64_t *denominations_serials;
+      void *h_blind_evs;
+      struct TALER_BlindedDenominationSignature denom_sigs;
+    } age_withdraw;
+
+  } details;
+
+};
+
+
+/**
+ * Function called on data to replicate in the auditor's database.
+ *
+ * @param cls closure
+ * @param td record from an exchange table
+ * @return #GNUNET_OK to continue to iterate,
+ *         #GNUNET_SYSERR to fail with an error
+ */
+typedef int
+(*TALER_EXCHANGEDB_ReplicationCallback)(
+  void *cls,
+  const struct TALER_EXCHANGEDB_TableData *td);
+
+
+/**
+ * @brief All information about a denomination key (which is used to
+ * sign coins into existence).
+ */
+struct TALER_EXCHANGEDB_DenominationKey
+{
+  /**
+   * The private key of the denomination.  Will be NULL if the private
+   * key is not available (this is the case after the key has expired
+   * for signing coins, but is still valid for depositing coins).
+   */
+  struct TALER_DenominationPrivateKey denom_priv;
+
+  /**
+   * Decoded denomination public key (the hash of it is in
+   * @e issue, but we sometimes need the full public key as well).
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Signed public information about a denomination key.
+   */
+  struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+};
+
+
+/**
+ * @brief Information we keep on bank transfer(s) that established a reserve.
+ */
+struct TALER_EXCHANGEDB_BankTransfer
+{
+
+  /**
+   * Public key of the reserve that was filled.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Amount that was transferred to the exchange.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * When did the exchange receive the incoming transaction?
+   * (This is the execution date of the exchange's database,
+   * the execution date of the bank should be in @e wire).
+   */
+  struct GNUNET_TIME_Timestamp execution_date;
+
+  /**
+   * Detailed wire information about the sending account
+   * in "payto://" format.
+   */
+  char *sender_account_details;
+
+  /**
+   * Data uniquely identifying the wire transfer (wire transfer-type specific)
+   */
+  uint64_t wire_reference;
+
+};
+
+
+/**
+ * @brief Information we keep on bank transfer(s) that
+ * closed a reserve.
+ */
+struct TALER_EXCHANGEDB_ClosingTransfer
+{
+
+  /**
+   * Public key of the reserve that was depleted.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Amount that was transferred from the exchange.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Amount that was charged by the exchange.
+   */
+  struct TALER_Amount closing_fee;
+
+  /**
+   * When did the exchange execute the transaction?
+   */
+  struct GNUNET_TIME_Timestamp execution_date;
+
+  /**
+   * Detailed wire information about the receiving account
+   * in payto://-format.
+   */
+  char *receiver_account_details;
+
+  /**
+   * Detailed wire transfer information that uniquely identifies the
+   * wire transfer.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+};
+
+
+/**
+ * @brief A summary of a Reserve
+ */
+struct TALER_EXCHANGEDB_Reserve
+{
+  /**
+   * The reserve's public key.  This uniquely identifies the reserve
+   */
+  struct TALER_ReservePublicKeyP pub;
+
+  /**
+   * The balance amount existing in the reserve
+   */
+  struct TALER_Amount balance;
+
+  /**
+   * The expiration date of this reserve; funds will be wired back
+   * at this time.
+   */
+  struct GNUNET_TIME_Timestamp expiry;
+
+  /**
+   * The legal expiration date of this reserve; we will forget about
+   * it at this time.
+   */
+  struct GNUNET_TIME_Timestamp gc;
+};
+
+
+/**
+ * Meta data about a denomination public key.
+ */
+struct TALER_EXCHANGEDB_DenominationKeyMetaData
+{
+  /**
+   * Serial of the denomination key as in the DB.
+   * Can be used in calls to stored procedures in order to spare
+   * additional lookups.
+   */
+  uint64_t serial;
+
+  /**
+   * Start time of the validity period for this key.
+   */
+  struct GNUNET_TIME_Timestamp start;
+
+  /**
+   * The exchange will sign fresh coins between @e start and this time.
+   * @e expire_withdraw will be somewhat larger than @e start to
+   * ensure a sufficiently large anonymity set, while also allowing
+   * the Exchange to limit the financial damage in case of a key being
+   * compromised.  Thus, exchanges with low volume are expected to have a
+   * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+   * with high transaction volume.  The period may also differ between
+   * types of coins.  A exchange may also have a few denomination keys
+   * with the same value with overlapping validity periods, to address
+   * issues such as clock skew.
+   */
+  struct GNUNET_TIME_Timestamp expire_withdraw;
+
+  /**
+   * Coins signed with the denomination key must be spent or refreshed
+   * between @e start and this expiration time.  After this time, the
+   * exchange will refuse transactions involving this key as it will
+   * "drop" the table with double-spending information (shortly after)
+   * this time.  Note that wallets should refresh coins significantly
+   * before this time to be on the safe side.  @e expire_deposit must be
+   * significantly larger than @e expire_withdraw (by months or even
+   * years).
+   */
+  struct GNUNET_TIME_Timestamp expire_deposit;
+
+  /**
+   * When do signatures with this denomination key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_deposit (by a year or more).
+   */
+  struct GNUNET_TIME_Timestamp expire_legal;
+
+  /**
+   * The value of the coins signed with this denomination key.
+   */
+  struct TALER_Amount value;
+
+  /**
+   * The fees the exchange charges for operations with
+   * coins of this denomination.
+   */
+  struct TALER_DenomFeeSet fees;
+
+  /**
+   * Age restriction for the denomination. (can be zero). If not zero, the bits
+   * set in the mask mark the edges at the beginning of a next age group.  F.e.
+   * for the age groups
+   *     0-7, 8-9, 10-11, 12-14, 14-15, 16-17, 18-21, 21-*
+   * the following bits are set:
+   *
+   *   31     24        16        8         0
+   *   |      |         |         |         |
+   *   oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+   *
+   * A value of 0 means that the denomination does not support the extension 
for
+   * age-restriction.
+   */
+  struct TALER_AgeMask age_mask;
+};
+
+
+/**
+ * Signature of a function called with information about the exchange's
+ * denomination keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param denom_pub public key of the denomination
+ * @param h_denom_pub hash of @a denom_pub
+ * @param meta meta data information about the denomination type (value, 
expirations, fees)
+ * @param master_sig master signature affirming the validity of this 
denomination
+ * @param recoup_possible true if the key was revoked and clients can 
currently recoup
+ *        coins of this denomination
+ */
+typedef void
+(*TALER_EXCHANGEDB_DenominationsCallback)(
+  void *cls,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig,
+  bool recoup_possible);
+
+
+/**
+ * Signature of a function called with information about the exchange's
+ * online signing keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param exchange_pub public key of the exchange
+ * @param meta meta data information about the signing type (expirations)
+ * @param master_sig master signature affirming the validity of this 
denomination
+ */
+typedef void
+(*TALER_EXCHANGEDB_ActiveSignkeysCallback)(
+  void *cls,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Function called on all KYC process names that the given
+ * account has already passed.
+ *
+ * @param cls closure
+ * @param kyc_provider_section_name configuration section
+ *        of the respective KYC process
+ */
+typedef void
+(*TALER_EXCHANGEDB_SatisfiedProviderCallback)(
+  void *cls,
+  const char *kyc_provider_section_name);
+
+
+/**
+ * Function called on all legitimization operations
+ * we have performed for the given account so far
+ * (and that have not yet expired).
+ *
+ * @param cls closure
+ * @param kyc_provider_section_name configuration section
+ *        of the respective KYC process
+ * @param provider_user_id UID at a provider (can be NULL)
+ * @param legi_id legitimization process ID (can be NULL)
+ */
+typedef void
+(*TALER_EXCHANGEDB_LegitimizationProcessCallback)(
+  void *cls,
+  const char *kyc_provider_section_name,
+  const char *provider_user_id,
+  const char *legi_id);
+
+
+/**
+ * Function called with information about the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of the auditor
+ * @param auditor_url URL of the REST API of the auditor
+ * @param auditor_name human readable official name of the auditor
+ */
+typedef void
+(*TALER_EXCHANGEDB_AuditorsCallback)(
+  void *cls,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  const char *auditor_name);
+
+
+/**
+ * Function called with information about the denominations
+ * audited by the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of an auditor
+ * @param h_denom_pub hash of a denomination key audited by this auditor
+ * @param auditor_sig signature from the auditor affirming this
+ */
+typedef void
+(*TALER_EXCHANGEDB_AuditorDenominationsCallback)(
+  void *cls,
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AuditorSignatureP *auditor_sig);
+
+
+/**
+ * @brief Information we keep for a withdrawn coin to reproduce
+ * the /withdraw operation if needed, and to have proof
+ * that a reserve was drained by this amount.
+ */
+struct TALER_EXCHANGEDB_CollectableBlindcoin
+{
+
+  /**
+   * Our (blinded) signature over the (blinded) coin.
+   */
+  struct TALER_BlindedDenominationSignature sig;
+
+  /**
+   * Hash of the denomination key (which coin was generated).
+   */
+  struct TALER_DenominationHashP denom_pub_hash;
+
+  /**
+   * Value of the coin being exchangeed (matching the denomination key)
+   * plus the transaction fee.  We include this in what is being
+   * signed so that we can verify a reserve's remaining total balance
+   * without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Withdrawal fee charged by the exchange.  This must match the Exchange's
+   * denomination key's withdrawal fee.  If the client puts in an
+   * invalid withdrawal fee (too high or too low) that does not match
+   * the Exchange's denomination key, the withdraw operation is invalid
+   * and will be rejected by the exchange.  The @e amount_with_fee minus
+   * the @e withdraw_fee is must match the value of the generated
+   * coin.  We include this in what is being signed so that we can
+   * verify a exchange's accounting without needing to access the
+   * respective denomination key information each time.
+   */
+  struct TALER_Amount withdraw_fee;
+
+  /**
+   * Public key of the reserve that was drained.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Hash over the blinded message, needed to verify
+   * the @e reserve_sig.
+   */
+  struct TALER_BlindedCoinHashP h_coin_envelope;
+
+  /**
+   * Signature confirming the withdrawal, matching @e reserve_pub,
+   * @e denom_pub and @e h_coin_envelope.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+};
+
+
+/**
+ * @brief Information we keep for an age-withdraw request
+ * to reproduce the /age-withdraw operation if needed, and to have proof
+ * that a reserve was drained by this amount.
+ */
+struct TALER_EXCHANGEDB_AgeWithdraw
+{
+  /**
+   * Total amount (with fee) committed to withdraw
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Maximum age (in years) that the coins are restricted to.
+   */
+  uint16_t max_age;
+
+  /**
+   * The hash of the commitment of all n*kappa coins
+   */
+  struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+  /**
+   * Index (smaller #TALER_CNC_KAPPA) which the exchange has chosen to not have
+   * revealed during cut and choose.  This value applies to all n coins in the
+   * commitment.
+   */
+  uint16_t noreveal_index;
+
+  /**
+   * Public key of the reserve that was drained.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Signature confirming the age withdrawal commitment, matching @e
+   * reserve_pub, @e max_age and @e h_commitment and @e amount_with_fee.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * Number of coins to be withdrawn.
+   */
+  size_t num_coins;
+
+  /**
+   * Array of @a num_coins blinded coins.  These are the chosen coins
+   * (according to @a noreveal_index) from the request, which contained
+   * kappa*num_coins blinded coins.
+   */
+  struct TALER_BlindedCoinHashP *h_coin_evs;
+
+  /**
+   * Array of @a num_coins denomination signatures of the blinded coins @a
+   * h_coin_evs.
+   */
+  struct TALER_BlindedDenominationSignature *denom_sigs;
+
+  /**
+   * Array of @a num_coins serial id's of the denominations, corresponding to
+   * the coins in @a h_coin_evs.
+   */
+  uint64_t *denom_serials;
+
+  /**
+   * [out]-Array of @a num_coins hashes of the public keys of the denominations
+   * identified by @e denom_serials.  This field is set when calling
+   * get_age_withdraw
+   */
+  struct TALER_DenominationHashP *denom_pub_hashes;
+};
+
+
+/**
+ * Information the exchange records about a recoup request
+ * in a reserve history.
+ */
+struct TALER_EXCHANGEDB_Recoup
+{
+
+  /**
+   * Information about the coin that was paid back.
+   */
+  struct TALER_CoinPublicInfo coin;
+
+  /**
+   * Blinding factor supplied to prove to the exchange that
+   * the coin came from this reserve.
+   */
+  union TALER_DenominationBlindingKeyP coin_blind;
+
+  /**
+   * Signature of the coin of type
+   * #TALER_SIGNATURE_WALLET_COIN_RECOUP.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Public key of the reserve the coin was paid back into.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * How much was the coin still worth at this time?
+   */
+  struct TALER_Amount value;
+
+  /**
+   * When did the recoup operation happen?
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+};
+
+
+/**
+ * Public key to which a nonce is locked.
+ */
+union TALER_EXCHANGEDB_NonceLockTargetP
+{
+  /**
+   * Nonce is locked to this coin key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin;
+
+  /**
+   * Nonce is locked to this reserve key.
+   */
+  struct TALER_ReservePublicKeyP reserve;
+};
+
+
+/**
+ * Information the exchange records about a recoup request
+ * in a coin history.
+ */
+struct TALER_EXCHANGEDB_RecoupListEntry
+{
+
+  /**
+   * Blinding factor supplied to prove to the exchange that
+   * the coin came from this reserve.
+   */
+  union TALER_DenominationBlindingKeyP coin_blind;
+
+  /**
+   * Signature of the coin of type
+   * #TALER_SIGNATURE_WALLET_COIN_RECOUP.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Hash of the public denomination key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Public key of the reserve the coin was paid back into.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * How much was the coin still worth at this time?
+   */
+  struct TALER_Amount value;
+
+  /**
+   * When did the /recoup operation happen?
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+};
+
+
+/**
+ * Information the exchange records about a recoup-refresh request in
+ * a coin transaction history.
+ */
+struct TALER_EXCHANGEDB_RecoupRefreshListEntry
+{
+
+  /**
+   * Information about the coin that was paid back
+   * (NOT the coin we are considering the history of!)
+   */
+  struct TALER_CoinPublicInfo coin;
+
+  /**
+   * Blinding factor supplied to prove to the exchange that
+   * the coin came from this @e old_coin_pub.
+   */
+  union TALER_DenominationBlindingKeyP coin_blind;
+
+  /**
+   * Signature of the coin of type
+   * #TALER_SIGNATURE_WALLET_COIN_RECOUP.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Public key of the old coin that the refreshed coin was paid back to.
+   */
+  struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+  /**
+   * How much was the coin still worth at this time?
+   */
+  struct TALER_Amount value;
+
+  /**
+   * When did the recoup operation happen?
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+};
+
+
+/**
+ * Details about a purse merge operation.
+ */
+struct TALER_EXCHANGEDB_PurseMerge
+{
+
+  /**
+   * Public key of the reserve the coin was merged into.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Amount in the purse, with fees.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Fee paid for the purse.
+   */
+  struct TALER_Amount purse_fee;
+
+  /**
+   * Hash over the contract.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Merge capability key.
+   */
+  struct TALER_PurseMergePublicKeyP merge_pub;
+
+  /**
+   * Purse public key.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Signature by the reserve approving the merge.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * When was the merge made.
+   */
+  struct GNUNET_TIME_Timestamp merge_timestamp;
+
+  /**
+   * When was the purse set to expire.
+   */
+  struct GNUNET_TIME_Timestamp purse_expiration;
+
+  /**
+   * Minimum age required for depositing into the purse.
+   */
+  uint32_t min_age;
+
+  /**
+   * Flags of the purse.
+   */
+  enum TALER_WalletAccountMergeFlags flags;
+
+  /**
+   * true if the purse was actually successfully merged,
+   * false if the @e purse_fee was charged but the
+   * @e amount was not credited to the reserve.
+   */
+  bool merged;
+};
+
+
+/**
+ * Details about a (paid for) reserve history request.
+ */
+struct TALER_EXCHANGEDB_HistoryRequest
+{
+  /**
+   * Public key of the reserve the history request was for.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Fee paid for the request.
+   */
+  struct TALER_Amount history_fee;
+
+  /**
+   * When was the request made.
+   */
+  struct GNUNET_TIME_Timestamp request_timestamp;
+
+  /**
+   * Signature by the reserve approving the history request.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+};
+
+
+/**
+ * Details about a (paid for) reserve open request.
+ */
+struct TALER_EXCHANGEDB_OpenRequest
+{
+  /**
+   * Public key of the reserve the open request was for.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Fee paid for the request from the reserve.
+   */
+  struct TALER_Amount open_fee;
+
+  /**
+   * When was the request made.
+   */
+  struct GNUNET_TIME_Timestamp request_timestamp;
+
+  /**
+   * How long was the reserve supposed to be open.
+   */
+  struct GNUNET_TIME_Timestamp reserve_expiration;
+
+  /**
+   * Signature by the reserve approving the open request,
+   * with purpose #TALER_SIGNATURE_WALLET_RESERVE_OPEN.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * How many open purses should be included with the
+   * open reserve?
+   */
+  uint32_t purse_limit;
+
+};
+
+
+/**
+ * Details about an (explicit) reserve close request.
+ */
+struct TALER_EXCHANGEDB_CloseRequest
+{
+  /**
+   * Public key of the reserve the history request was for.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * When was the request made.
+   */
+  struct GNUNET_TIME_Timestamp request_timestamp;
+
+  /**
+   * Hash of the payto://-URI of the target account
+   * for the closure, or all zeros for the reserve
+   * origin account.
+   */
+  struct TALER_PaytoHashP target_account_h_payto;
+
+  /**
+   * Signature by the reserve approving the history request.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+};
+
+
+/**
+ * @brief Types of operations on a reserve.
+ */
+enum TALER_EXCHANGEDB_ReserveOperation
+{
+  /**
+   * Money was deposited into the reserve via a bank transfer.
+   * This is how customers establish a reserve at the exchange.
+   */
+  TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE = 0,
+
+  /**
+   * A Coin was withdrawn from the reserve using /withdraw.
+   */
+  TALER_EXCHANGEDB_RO_WITHDRAW_COIN = 1,
+
+  /**
+   * A coin was returned to the reserve using /recoup.
+   */
+  TALER_EXCHANGEDB_RO_RECOUP_COIN = 2,
+
+  /**
+   * The exchange send inactive funds back from the reserve to the
+   * customer's bank account.  This happens when the exchange
+   * closes a reserve with a non-zero amount left in it.
+   */
+  TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK = 3,
+
+  /**
+   * Event where a purse was merged into a reserve.
+   */
+  TALER_EXCHANGEDB_RO_PURSE_MERGE = 4,
+
+  /**
+   * Event where a wallet paid for a full reserve history.
+   */
+  TALER_EXCHANGEDB_RO_HISTORY_REQUEST = 5,
+
+  /**
+   * Event where a wallet paid to open a reserve for longer.
+   */
+  TALER_EXCHANGEDB_RO_OPEN_REQUEST = 6,
+
+  /**
+   * Event where a wallet requested a reserve to be closed.
+   */
+  TALER_EXCHANGEDB_RO_CLOSE_REQUEST = 7
+};
+
+
+/**
+ * @brief Reserve history as a linked list.  Lists all of the transactions
+ * associated with this reserve (such as the bank transfers that
+ * established the reserve and all /withdraw operations we have done
+ * since).
+ */
+struct TALER_EXCHANGEDB_ReserveHistory
+{
+
+  /**
+   * Next entry in the reserve history.
+   */
+  struct TALER_EXCHANGEDB_ReserveHistory *next;
+
+  /**
+   * Type of the event, determines @e details.
+   */
+  enum TALER_EXCHANGEDB_ReserveOperation type;
+
+  /**
+   * Details of the operation, depending on @e type.
+   */
+  union
+  {
+
+    /**
+     * Details about a bank transfer to the exchange (reserve
+     * was established).
+     */
+    struct TALER_EXCHANGEDB_BankTransfer *bank;
+
+    /**
+     * Details about a /withdraw operation.
+     */
+    struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
+
+    /**
+     * Details about a /recoup operation.
+     */
+    struct TALER_EXCHANGEDB_Recoup *recoup;
+
+    /**
+     * Details about a bank transfer from the exchange (reserve
+     * was closed).
+     */
+    struct TALER_EXCHANGEDB_ClosingTransfer *closing;
+
+    /**
+     * Details about a purse merge operation.
+     */
+    struct TALER_EXCHANGEDB_PurseMerge *merge;
+
+    /**
+     * Details about a (paid for) reserve history request.
+     */
+    struct TALER_EXCHANGEDB_HistoryRequest *history;
+
+    /**
+     * Details about a (paid for) open reserve request.
+     */
+    struct TALER_EXCHANGEDB_OpenRequest *open_request;
+
+    /**
+     * Details about an (explicit) reserve close request.
+     */
+    struct TALER_EXCHANGEDB_CloseRequest *close_request;
+
+  } details;
+
+};
+
+
+/**
+ * @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
+{
+
+  /**
+   * 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;
+
+  /**
+   * Hash over additional inputs by the wallet.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+  /**
+   * Unsalted hash over @e receiver_wire_account.
+   */
+  struct TALER_PaytoHashP wire_target_h_payto;
+
+  /**
+   * Salt used by the merchant to compute "h_wire".
+   */
+  struct TALER_WireSaltP wire_salt;
+
+  /**
+   * 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;
+
+  /**
+   * Row ID of the policy details; 0 if no policy applies.
+   */
+  uint64_t policy_details_serial_id;
+
+  /**
+   * Information about the receiver for executing the transaction.  URI in
+   * payto://-format.
+   */
+  const char *receiver_wire_account;
+
+  /**
+   * Array about the coins that are being deposited.
+   */
+  const struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
+
+  /**
+   * Length of the @e cdis array.
+   */
+  unsigned int num_cdis;
+
+  /**
+   * False if @e wallet_data_hash was provided
+   */
+  bool no_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
+ * transaction ID must be unique.  While a coin can (theoretically) be
+ * deposited at the same merchant twice (with partial spending), the
+ * merchant must either use a different public key or a different
+ * transaction ID for the two transactions.  The same coin must not
+ * be used twice at the same merchant for the same transaction
+ * (as determined by transaction ID).
+ */
+struct TALER_EXCHANGEDB_Deposit
+{
+  /**
+   * 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;
+
+  /**
+   * 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;
+
+  /**
+   * Hash over inputs from the wallet to customize the contract.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+  /**
+   * 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;
+
+  /**
+   * 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.
+   */
+  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;
+
+  /**
+   * 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;
+
+  /**
+   * Depositing fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Information about the receiver for executing the transaction.  URI in
+   * payto://-format.
+   */
+  char *receiver_wire_account;
+
+  /**
+   * True if @e policy_json was provided
+   */
+  bool has_policy;
+
+  /**
+   * True if @e wallet_data_hash is not in use.
+   */
+  bool no_wallet_data_hash;
+
+};
+
+
+/**
+ * @brief Specification for a deposit operation in the
+ * `struct TALER_EXCHANGEDB_TransactionList`.
+ */
+struct TALER_EXCHANGEDB_DepositListEntry
+{
+
+  /**
+   * 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;
+
+  /**
+   * 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 proposa data between merchant and customer
+   * (remains unknown to the Exchange).
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Hash over inputs from the wallet to customize the contract.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+  /**
+   * Hash of the public denomination key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Age commitment hash, if applicable to the denomination.  Should be all
+   * zeroes if age commitment is not applicable to the denonimation.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Salt used to compute h_wire from the @e receiver_wire_account.
+   */
+  struct TALER_WireSaltP wire_salt;
+
+  /**
+   * 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;
+
+  /**
+   * 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;
+
+  /**
+   * Depositing fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * 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.
+   */
+  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;
+
+  /**
+   * Detailed information about the receiver for executing the transaction.
+   * URL in payto://-format.
+   */
+  char *receiver_wire_account;
+
+  /**
+   * true, if age commitment is not applicable
+   */
+  bool no_age_commitment;
+
+  /**
+   * true, if wallet data hash is not present
+   */
+  bool no_wallet_data_hash;
+
+  /**
+   * True if a policy was provided with the deposit request
+   */
+  bool has_policy;
+
+  /**
+   * Has the deposit been wired?
+   */
+  bool done;
+
+};
+
+
+/**
+ * @brief Specification for a refund operation in a coin's transaction list.
+ */
+struct TALER_EXCHANGEDB_RefundListEntry
+{
+
+  /**
+   * Public key of the merchant.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Signature from the merchant affirming the refund.
+   */
+  struct TALER_MerchantSignatureP merchant_sig;
+
+  /**
+   * Hash over the proposal data between merchant and customer
+   * (remains unknown to the Exchange).
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Merchant-generated REFUND transaction ID to detect duplicate
+   * refunds.
+   */
+  uint64_t rtransaction_id;
+
+  /**
+   * Fraction of the original deposit's value to be refunded, including
+   * refund fee (if any).  The coin is identified by @e coin_pub.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Refund fee to be covered by the customer.
+   */
+  struct TALER_Amount refund_fee;
+
+};
+
+
+/**
+ * @brief Specification for a refund operation.  The combination of
+ * the coin's public key, the merchant's public key and the
+ * transaction ID must be unique.  While a coin can (theoretically) be
+ * deposited at the same merchant twice (with partial spending), the
+ * merchant must either use a different public key or a different
+ * transaction ID for the two transactions.  The same goes for
+ * refunds, hence we also have a "rtransaction" ID which is disjoint
+ * from the transaction ID.  The same coin must not be used twice at
+ * the same merchant for the same transaction or rtransaction ID.
+ */
+struct TALER_EXCHANGEDB_Refund
+{
+  /**
+   * Information about the coin that is being refunded.
+   */
+  struct TALER_CoinPublicInfo coin;
+
+  /**
+   * Details about the refund.
+   */
+  struct TALER_EXCHANGEDB_RefundListEntry details;
+
+};
+
+
+/**
+ * @brief Specification for coin in a melt operation.
+ */
+struct TALER_EXCHANGEDB_Refresh
+{
+  /**
+   * Information about the coin that is being melted.
+   */
+  struct TALER_CoinPublicInfo coin;
+
+  /**
+   * Signature over the melting operation.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Refresh commitment this coin is melted into.
+   */
+  struct TALER_RefreshCommitmentP rc;
+
+  /**
+   * How much value is being melted?  This amount includes the fees,
+   * so the final amount contributed to the melt is this value minus
+   * the fee for melting the coin.  We include the fee in what is
+   * being signed so that we can verify a reserve's remaining total
+   * balance without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Index (smaller #TALER_CNC_KAPPA) which the exchange has chosen to not
+   * have revealed during cut and choose.
+   */
+  uint32_t noreveal_index;
+
+};
+
+
+/**
+ * Information about a /coins/$COIN_PUB/melt operation in a coin transaction 
history.
+ */
+struct TALER_EXCHANGEDB_MeltListEntry
+{
+
+  /**
+   * Signature over the melting operation.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Refresh commitment this coin is melted into.
+   */
+  struct TALER_RefreshCommitmentP rc;
+
+  /**
+   * Hash of the public denomination key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Hash of the age commitment used to sign the coin, if age restriction was
+   * applicable to the denomination.  May be all zeroes if no age restriction
+   * applies.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * true, if no h_age_commitment is applicable
+   */
+  bool no_age_commitment;
+
+  /**
+   * How much value is being melted?  This amount includes the fees,
+   * so the final amount contributed to the melt is this value minus
+   * the fee for melting the coin.  We include the fee in what is
+   * being signed so that we can verify a reserve's remaining total
+   * balance without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_Amount amount_with_fee;
+
+  /**
+   * Melt fee the exchange charged.
+   */
+  struct TALER_Amount melt_fee;
+
+  /**
+   * Index (smaller #TALER_CNC_KAPPA) which the exchange has chosen to not
+   * have revealed during cut and choose.
+   */
+  uint32_t noreveal_index;
+
+};
+
+
+/**
+ * Information about a /purses/$PID/deposit operation in a coin transaction 
history.
+ */
+struct TALER_EXCHANGEDB_PurseDepositListEntry
+{
+
+  /**
+   * Exchange hosting the purse, NULL for this exchange.
+   */
+  char *exchange_base_url;
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Contribution of the coin to the purse, including
+   * deposit fee.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Depositing fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Signature by the coin affirming the deposit.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Hash of the age commitment used to sign the coin, if age restriction was
+   * applicable to the denomination.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Set to true if the coin was refunded.
+   */
+  bool refunded;
+
+  /**
+   * Set to true if there was no age commitment.
+   */
+  bool no_age_commitment;
+
+};
+
+
+/**
+ * @brief Specification for a purse refund operation in a coin's transaction 
list.
+ */
+struct TALER_EXCHANGEDB_PurseRefundListEntry
+{
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Fraction of the original deposit's value to be refunded, including
+   * refund fee (if any).  The coin is identified by @e coin_pub.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Refund fee to be covered by the customer.
+   */
+  struct TALER_Amount refund_fee;
+
+};
+
+
+/**
+ * Information about a /reserves/$RID/open operation in a coin transaction 
history.
+ */
+struct TALER_EXCHANGEDB_ReserveOpenListEntry
+{
+
+  /**
+   * Signature of the reserve.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * Contribution of the coin to the open fee, including
+   * deposit fee.
+   */
+  struct TALER_Amount coin_contribution;
+
+  /**
+   * Signature by the coin affirming the open deposit.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+};
+
+
+/**
+ * Information about a /purses/$PID/deposit operation.
+ */
+struct TALER_EXCHANGEDB_PurseDeposit
+{
+
+  /**
+   * Exchange hosting the purse, NULL for this exchange.
+   */
+  char *exchange_base_url;
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Contribution of the coin to the purse, including
+   * deposit fee.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Depositing fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Signature by the coin affirming the deposit.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Hash of the age commitment used to sign the coin, if age restriction was
+   * applicable to the denomination.  May be all zeroes if no age restriction
+   * applies.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Set to true if @e h_age_commitment is not available.
+   */
+  bool no_age_commitment;
+
+};
+
+/**
+ * Information about a melt operation.
+ */
+struct TALER_EXCHANGEDB_Melt
+{
+
+  /**
+   * Overall session data.
+   */
+  struct TALER_EXCHANGEDB_Refresh session;
+
+  /**
+   * Melt fee the exchange charged.
+   */
+  struct TALER_Amount melt_fee;
+
+};
+
+
+/**
+ * @brief Linked list of refresh information linked to a coin.
+ */
+struct TALER_EXCHANGEDB_LinkList
+{
+  /**
+   * Information is stored in a NULL-terminated linked list.
+   */
+  struct TALER_EXCHANGEDB_LinkList *next;
+
+  /**
+   * Denomination public key, determines the value of the coin.
+   */
+  struct TALER_DenominationPublicKey denom_pub;
+
+  /**
+   * Signature over the blinded envelope.
+   */
+  struct TALER_BlindedDenominationSignature ev_sig;
+
+  /**
+   * Exchange-provided values during the coin generation.
+   */
+  struct TALER_ExchangeWithdrawValues alg_values;
+
+  /**
+   * Signature of the original coin being refreshed over the
+   * link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
+   */
+  struct TALER_CoinSpendSignatureP orig_coin_link_sig;
+
+  /**
+   * CS nonce, if cipher is CS.
+   */
+  struct TALER_CsNonce nonce;
+
+  /**
+   * Offset that generated this coin in the refresh
+   * operation.
+   */
+  uint32_t coin_refresh_offset;
+
+  /**
+   * Set to true if @e nonce was initialized.
+   */
+  bool have_nonce;
+};
+
+
+/**
+ * @brief Enumeration to classify the different types of transactions
+ * that can be done with a coin.
+ */
+enum TALER_EXCHANGEDB_TransactionType
+{
+
+  /**
+   * Deposit operation.
+   */
+  TALER_EXCHANGEDB_TT_DEPOSIT = 0,
+
+  /**
+   * Melt operation.
+   */
+  TALER_EXCHANGEDB_TT_MELT = 1,
+
+  /**
+   * Refund operation.
+   */
+  TALER_EXCHANGEDB_TT_REFUND = 2,
+
+  /**
+   * Recoup-refresh operation (on the old coin, adding to the old coin's value)
+   */
+  TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP = 3,
+
+  /**
+   * Recoup operation.
+   */
+  TALER_EXCHANGEDB_TT_RECOUP = 4,
+
+  /**
+   * Recoup-refresh operation (on the new coin, eliminating its value)
+   */
+  TALER_EXCHANGEDB_TT_RECOUP_REFRESH = 5,
+
+  /**
+   * Purse deposit operation.
+   */
+  TALER_EXCHANGEDB_TT_PURSE_DEPOSIT = 6,
+
+  /**
+   * Purse deposit operation.
+   */
+  TALER_EXCHANGEDB_TT_PURSE_REFUND = 7,
+
+  /**
+   * Reserve open deposit operation.
+   */
+  TALER_EXCHANGEDB_TT_RESERVE_OPEN = 8
+
+};
+
+
+/**
+ * @brief List of transactions we performed for a particular coin.
+ */
+struct TALER_EXCHANGEDB_TransactionList
+{
+
+  /**
+   * Next pointer in the NULL-terminated linked list.
+   */
+  struct TALER_EXCHANGEDB_TransactionList *next;
+
+  /**
+   * Type of the transaction, determines what is stored in @e details.
+   */
+  enum TALER_EXCHANGEDB_TransactionType type;
+
+  /**
+   * Serial ID of this entry in the database.
+   */
+  uint64_t serial_id;
+
+  /**
+   * Details about the transaction, depending on @e type.
+   */
+  union
+  {
+
+    /**
+     * Details if transaction was a deposit operation.
+     * (#TALER_EXCHANGEDB_TT_DEPOSIT)
+     */
+    struct TALER_EXCHANGEDB_DepositListEntry *deposit;
+
+    /**
+     * Details if transaction was a melt operation.
+     * (#TALER_EXCHANGEDB_TT_MELT)
+     */
+    struct TALER_EXCHANGEDB_MeltListEntry *melt;
+
+    /**
+     * Details if transaction was a refund operation.
+     * (#TALER_EXCHANGEDB_TT_REFUND)
+     */
+    struct TALER_EXCHANGEDB_RefundListEntry *refund;
+
+    /**
+     * Details if transaction was a recoup-refund operation where
+     * this coin was the OLD coin.
+     * (#TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP).
+     */
+    struct TALER_EXCHANGEDB_RecoupRefreshListEntry *old_coin_recoup;
+
+    /**
+     * Details if transaction was a recoup operation.
+     * (#TALER_EXCHANGEDB_TT_RECOUP)
+     */
+    struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
+
+    /**
+     * Details if transaction was a recoup-refund operation where
+     * this coin was the REFRESHED coin.
+     * (#TALER_EXCHANGEDB_TT_RECOUP_REFRESH)
+     */
+    struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup_refresh;
+
+    /**
+     * Coin was deposited into a purse.
+     * (#TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)
+     */
+    struct TALER_EXCHANGEDB_PurseDepositListEntry *purse_deposit;
+
+    /**
+     * Coin was refunded upon purse expiration
+     * (#TALER_EXCHANGEDB_TT_PURSE_REFUND)
+     */
+    struct TALER_EXCHANGEDB_PurseRefundListEntry *purse_refund;
+
+    /**
+     * Coin was used to pay to open a reserve.
+     * (#TALER_EXCHANGEDB_TT_RESERVE_OPEN)
+     */
+    struct TALER_EXCHANGEDB_ReserveOpenListEntry *reserve_open;
+
+  } details;
+
+};
+
+
+/**
+ * Callback with data about a prepared wire transfer.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to mark prepared transaction as done
+ * @param wire_method which wire method is this preparation data for
+ * @param buf transaction data that was persisted, NULL on error
+ * @param buf_size number of bytes in @a buf, 0 on error
+ */
+typedef void
+(*TALER_EXCHANGEDB_WirePreparationIterator) (void *cls,
+                                             uint64_t rowid,
+                                             const char *wire_method,
+                                             const char *buf,
+                                             size_t buf_size);
+
+
+/**
+ * Callback with KYC attributes about a particular user.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ */
+typedef void
+(*TALER_EXCHANGEDB_AttributeCallback)(
+  void *cls,
+  const struct TALER_PaytoHashP *h_payto,
+  const char *provider_section,
+  struct GNUNET_TIME_Timestamp collection_time,
+  struct GNUNET_TIME_Timestamp expiration_time,
+  size_t enc_attributes_size,
+  const void *enc_attributes);
+
+
+/**
+ * Function called with details about deposits that have been made,
+ * with the goal of auditing the deposit's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param exchange_timestamp when did the deposit happen
+ * @param deposit deposit details
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param done flag set if the deposit was already executed (or not)
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_DepositCallback)(
+  void *cls,
+  uint64_t rowid,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  const struct TALER_EXCHANGEDB_Deposit *deposit,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  bool done);
+
+
+/**
+ * Function called with details about purse deposits that have been made, with
+ * the goal of auditing the deposit's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param deposit deposit details
+ * @param reserve_pub which reserve is the purse merged into, NULL if unknown
+ * @param flags purse flags
+ * @param auditor_balance purse balance (according to the
+ *          auditor during auditing)
+ * @param purse_total target amount the purse should reach
+ * @param denom_pub denomination public key of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseDepositCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_EXCHANGEDB_PurseDeposit *deposit,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_Amount *auditor_balance,
+  const struct TALER_Amount *purse_total,
+  const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Function called with details about
+ * account merge requests that have been made, with
+ * the goal of auditing the account merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param reserve_pub reserve affected by the merge
+ * @param purse_pub purse being merged
+ * @param h_contract_terms hash over contract of the purse
+ * @param purse_expiration when would the purse expire
+ * @param amount total amount in the purse
+ * @param min_age minimum age of all coins deposited into the purse
+ * @param flags how was the purse created
+ * @param purse_fee if a purse fee was paid, how high is it
+ * @param merge_timestamp when was the merge approved
+ * @param reserve_sig signature by reserve approving the merge
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_AccountMergeCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount,
+  uint32_t min_age,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_Amount *purse_fee,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Function called with details about purse
+ * merges that have been made, with
+ * the goal of auditing the purse merge execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param partner_base_url where is the reserve, NULL for this exchange
+ * @param amount total amount expected in the purse
+ * @param balance current balance in the purse (according to the auditor)
+ * @param flags purse flags
+ * @param merge_pub merge capability key
+ * @param reserve_pub reserve the merge affects
+ * @param merge_sig signature affirming the merge
+ * @param purse_pub purse key
+ * @param merge_timestamp when did the merge happen
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseMergeCallback)(
+  void *cls,
+  uint64_t rowid,
+  const char *partner_base_url,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *balance,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_PurseMergeSignatureP *merge_sig,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Timestamp merge_timestamp);
+
+
+/**
+ * Function called with details about
+ * history requests that have been made, with
+ * the goal of auditing the history request execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param history_fee fee paid for the request
+ * @param ts timestamp of the request
+ * @param reserve_pub reserve history was requested for
+ * @param reserve_sig signature approving the @a history_fee
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_HistoryRequestCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_Amount *history_fee,
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Function called with details about purse decisions that have been made, with
+ * the goal of auditing the purse's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub public key of the purse
+ * @param reserve_pub public key of the target reserve, NULL if not known / 
refunded
+ * @param purse_value what is the (target) value of the purse
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseDecisionCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *purse_value);
+
+
+/**
+ * Function called with details about purse decisions that have been made, with
+ * the goal of auditing the purse's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the deposit in our DB
+ * @param purse_pub public key of the purse
+ * @param refunded true if decision was to refund
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_AllPurseDecisionCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  bool refunded);
+
+
+/**
+ * Function called with details about purse refunds that have been made, with
+ * the goal of auditing the purse refund's execution.
+ *
+ * @param cls closure
+ * @param rowid row of the refund event
+ * @param amount_with_fee amount of the deposit into the purse
+ * @param coin_pub coin that is to be refunded the @a given amount_with_fee
+ * @param denom_pub denomination of @a coin_pub
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseRefundCoinCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_DenominationPublicKey *denom_pub);
+
+
+/**
+ * Function called with details about coins that were melted,
+ * with the goal of auditing the refresh's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param h_age_commitment age commitment that went into the signing of the 
coin, may be NULL
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature from the coin
+ * @param amount_with_fee amount that was deposited including fee
+ * @param noreveal_index which index was picked by the exchange in 
cut-and-choose
+ * @param rc what is the commitment
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_RefreshesCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const struct TALER_Amount *amount_with_fee,
+  uint32_t noreveal_index,
+  const struct TALER_RefreshCommitmentP *rc);
+
+
+/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin and contract.
+ *
+ * @param cls closure
+ * @param amount_with_fee amount being refunded
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_RefundCoinCallback)(
+  void *cls,
+  const struct TALER_Amount *amount_with_fee);
+
+
+/**
+ * Information about a coin that was revealed to the exchange
+ * during reveal.
+ */
+struct TALER_EXCHANGEDB_RefreshRevealedCoin
+{
+  /**
+   * Hash of the public denomination key of the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Signature of the original coin being refreshed over the
+   * link data, of type #TALER_SIGNATURE_WALLET_COIN_LINK
+   */
+  struct TALER_CoinSpendSignatureP orig_coin_link_sig;
+
+  /**
+   * Hash of the blinded new coin, that is @e coin_ev.
+   */
+  struct TALER_BlindedCoinHashP coin_envelope_hash;
+
+  /**
+   * Signature generated by the exchange over the coin (in blinded format).
+   */
+  struct TALER_BlindedDenominationSignature coin_sig;
+
+  /**
+   * Values contributed from the exchange to the
+   * coin generation (see /csr).
+   */
+  struct TALER_ExchangeWithdrawValues exchange_vals;
+
+  /**
+   * Blinded message to be signed (in envelope).
+   */
+  struct TALER_BlindedPlanchet blinded_planchet;
+
+};
+
+
+/**
+ * Information per Clause-Schnorr (CS) fresh coin to
+ * be persisted for idempotency during refreshes-reveal.
+ */
+struct TALER_EXCHANGEDB_CsRevealFreshCoinData
+{
+  /**
+   * Denomination of the fresh coin.
+   */
+  struct TALER_DenominationHashP new_denom_pub_hash;
+
+  /**
+   * Blind signature of the fresh coin (possibly updated
+   * in case if a replay!).
+   */
+  struct TALER_BlindedDenominationSignature bsig;
+
+  /**
+   * Offset of the fresh coin in the reveal operation.
+   * (May not match the array offset as we may have
+   * a mixture of RSA and CS coins being created, and
+   * this request is only made for the CS subset).
+   */
+  uint32_t coin_off;
+};
+
+
+/**
+ * Generic KYC status for some operation.
+ */
+struct TALER_EXCHANGEDB_KycStatus
+{
+  /**
+   * Number that identifies the KYC requirement the operation
+   * was about.
+   */
+  uint64_t requirement_row;
+
+  /**
+   * True if the KYC status is "satisfied".
+   */
+  bool ok;
+
+};
+
+
+struct TALER_EXCHANGEDB_ReserveInInfo
+{
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+  const struct TALER_Amount *balance;
+  struct GNUNET_TIME_Timestamp execution_time;
+  const char *sender_account_details;
+  const char *exchange_account_name;
+  uint64_t wire_reference;
+};
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for a KYC check.
+ *
+ * @param cls closure to allow the KYC module to
+ *        total up amounts and evaluate rules
+ * @param amount encountered transaction amount
+ * @param date when was the amount encountered
+ * @return #GNUNET_OK to continue to iterate,
+ *         #GNUNET_NO to abort iteration
+ *         #GNUNET_SYSERR on internal error (also abort itaration)
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_KycAmountCallback)(
+  void *cls,
+  const struct TALER_Amount *amount,
+  struct GNUNET_TIME_Absolute date);
+
+
+/**
+ * Function called with information about a refresh order.
+ *
+ * @param cls closure
+ * @param num_freshcoins size of the @a rrcs array
+ * @param rrcs array of @a num_freshcoins information about coins to be created
+ */
+typedef void
+(*TALER_EXCHANGEDB_RefreshCallback)(
+  void *cls,
+  uint32_t num_freshcoins,
+  const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs);
+
+
+/**
+ * Function called with details about coins that were refunding,
+ * with the goal of auditing the refund's execution.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refund in our DB
+ * @param denom_pub denomination public key of @a coin_pub
+ * @param coin_pub public key of the coin
+ * @param merchant_pub public key of the merchant
+ * @param merchant_sig signature of the merchant
+ * @param h_contract_terms hash of the proposal data known to merchant and 
customer
+ * @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund true if the refunds total up to the entire value of the 
deposit
+ * @param amount_with_fee amount that was deposited including fee
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_RefundCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint64_t rtransaction_id,
+  bool full_refund,
+  const struct TALER_Amount *amount_with_fee);
+
+
+/**
+ * Function called with details about incoming wire transfers.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param reserve_pub public key of the reserve (also the wire subject)
+ * @param credit amount that was received
+ * @param sender_account_details information about the sender's bank account, 
in payto://-format
+ * @param wire_reference unique identifier for the wire transfer
+ * @param execution_date when did we receive the funds
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_ReserveInCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *credit,
+  const char *sender_account_details,
+  uint64_t wire_reference,
+  struct GNUNET_TIME_Timestamp execution_date);
+
+
+/**
+ * Provide information about a wire account.
+ *
+ * @param cls closure
+ * @param payto_uri the exchange bank account URI
+ * @param conversion_url URL of a conversion service, NULL if there is no 
conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the 
account
+ * @param master_sig master key signature affirming that this is a bank
+ *                   account of the exchange (of purpose 
#TALER_SIGNATURE_MASTER_WIRE_DETAILS)
+ */
+typedef void
+(*TALER_EXCHANGEDB_WireAccountCallback)(
+  void *cls,
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Provide information about wire fees.
+ *
+ * @param cls closure
+ * @param fees the wire fees we charge
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ *                   fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
+ */
+typedef void
+(*TALER_EXCHANGEDB_WireFeeCallback)(
+  void *cls,
+  const struct TALER_WireFeeSet *fees,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Provide information about global fees.
+ *
+ * @param cls closure
+ * @param fees the global fees we charge
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param start_date from when are these fees valid (start date)
+ * @param end_date until when are these fees valid (end date, exclusive)
+ * @param master_sig master key signature affirming that this is the correct
+ *                   fee (of purpose #TALER_SIGNATURE_MASTER_GLOBAL_FEES)
+ */
+typedef void
+(*TALER_EXCHANGEDB_GlobalFeeCallback)(
+  void *cls,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
+/**
+ * Function called with details about withdraw operations.
+ *
+ * @param cls closure
+ * @param rowid unique serial ID for the refresh session in our DB
+ * @param h_blind_ev blinded hash of the coin's public key
+ * @param denom_pub public denomination key of the deposited coin
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature over the withdraw operation
+ * @param execution_date when did the wallet withdraw the coin
+ * @param amount_with_fee amount that was withdrawn
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_WithdrawCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_BlindedCoinHashP *h_blind_ev,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig,
+  struct GNUNET_TIME_Timestamp execution_date,
+  const struct TALER_Amount *amount_with_fee);
+
+
+/**
+ * Function called with the session hashes and transfer secret
+ * information for a given coin.
+ *
+ * @param cls closure
+ * @param transfer_pub public transfer key for the session
+ * @param ldl link data for @a transfer_pub
+ */
+typedef void
+(*TALER_EXCHANGEDB_LinkCallback)(
+  void *cls,
+  const struct TALER_TransferPublicKeyP *transfer_pub,
+  const struct TALER_EXCHANGEDB_LinkList *ldl);
+
+
+/**
+ * Function called with the results of the lookup of the
+ * transaction data associated with a wire transfer identifier.
+ *
+ * @param cls closure
+ * @param rowid which row in the table is the information from (for 
diagnostics)
+ * @param merchant_pub public key of the merchant (should be same for all 
callbacks with the same @e cls)
+ * @param account_payto_uri which account did the transfer go to?
+ * @param h_payto hash over @a account_payto_uri as it is in the DB
+ * @param exec_time execution time of the wire transfer (should be same for 
all callbacks with the same @e cls)
+ * @param h_contract_terms which proposal was this payment about
+ * @param denom_pub denomination of @a coin_pub
+ * @param coin_pub which public key was this payment about
+ * @param coin_value amount contributed by this coin in total (with fee)
+ * @param coin_fee applicable fee for this coin
+ */
+typedef void
+(*TALER_EXCHANGEDB_AggregationDataCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const char *account_payto_uri,
+  const struct TALER_PaytoHashP *h_payto,
+  struct GNUNET_TIME_Timestamp exec_time,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_Amount *coin_fee);
+
+
+/**
+ * Function called with the results of the lookup of the
+ * wire transfer data of the exchange.
+ *
+ * @param cls closure
+ * @param rowid identifier of the respective row in the database
+ * @param date timestamp of the wire transfer (roughly)
+ * @param wtid wire transfer subject
+ * @param payto_uri details of the receiver, URI in payto://-format
+ * @param amount amount that was wired
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_WireTransferOutCallback)(
+  void *cls,
+  uint64_t rowid,
+  struct GNUNET_TIME_Timestamp date,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const char *payto_uri,
+  const struct TALER_Amount *amount);
+
+
+/**
+ * Function called on transient aggregations matching
+ * a particular hash of a payto URI.
+ *
+ * @param cls
+ * @param payto_uri corresponding payto URI
+ * @param wtid wire transfer identifier of transient aggregation
+ * @param merchant_pub public key of the merchant
+ * @param total amount aggregated so far
+ * @return true to continue iterating
+ */
+typedef bool
+(*TALER_EXCHANGEDB_TransientAggregationCallback)(
+  void *cls,
+  const char *payto_uri,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_Amount *total);
+
+
+/**
+ * Callback with data about a prepared wire transfer.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to mark prepared transaction as done
+ * @param wire_method which wire method is this preparation data for
+ * @param buf transaction data that was persisted, NULL on error
+ * @param buf_size number of bytes in @a buf, 0 on error
+ * @param finished did we complete the transfer yet?
+ * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_WirePreparationCallback)(void *cls,
+                                            uint64_t rowid,
+                                            const char *wire_method,
+                                            const char *buf,
+                                            size_t buf_size,
+                                            int finished);
+
+
+/**
+ * Function called about recoups the exchange has to perform.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the recoup operation
+ * @param timestamp when did we receive the recoup request
+ * @param amount how much should be added back to the reserve
+ * @param reserve_pub public key of the reserve
+ * @param coin public information about the coin
+ * @param denom_pub denomination key of @a coin
+ * @param coin_sig signature with @e coin_pub of type 
#TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * @param coin_blind blinding factor used to blind the coin
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_RecoupCallback)(
+  void *cls,
+  uint64_t rowid,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *amount,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_CoinPublicInfo *coin,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const union TALER_DenominationBlindingKeyP *coin_blind);
+
+
+/**
+ * Function called about recoups on refreshed coins the exchange has to
+ * perform.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the recoup operation
+ * @param timestamp when did we receive the recoup request
+ * @param amount how much should be added back to the reserve
+ * @param old_coin_pub original coin that was refreshed to create @a coin
+ * @param old_denom_pub_hash hash of public key of @a old_coin_pub
+ * @param coin public information about the coin
+ * @param denom_pub denomination key of @a coin
+ * @param coin_sig signature with @e coin_pub of type 
#TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * @param coin_blind blinding factor used to blind the coin
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_RecoupRefreshCallback)(
+  void *cls,
+  uint64_t rowid,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *amount,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_DenominationHashP *old_denom_pub_hash,
+  const struct TALER_CoinPublicInfo *coin,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const union TALER_DenominationBlindingKeyP *coin_blind);
+
+
+/**
+ * Function called about reserve opening operations.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the reserve closing 
operation
+ * @param reserve_payment how much to pay from the
+ *        reserve's own balance for opening the reserve
+ * @param request_timestamp when was the request created
+ * @param reserve_expiration desired expiration time for the reserve
+ * @param purse_limit minimum number of purses the client
+ *       wants to have concurrently open for this reserve
+ * @param reserve_pub public key of the reserve
+ * @param reserve_sig signature affirming the operation
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_ReserveOpenCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_Amount *reserve_payment,
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  struct GNUNET_TIME_Timestamp reserve_expiration,
+  uint32_t purse_limit,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig);
+
+
+/**
+ * Function called about reserve closing operations
+ * the aggregator triggered.
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the reserve closing 
operation
+ * @param execution_date when did we execute the close operation
+ * @param amount_with_fee how much did we debit the reserve
+ * @param closing_fee how much did we charge for closing the reserve
+ * @param reserve_pub public key of the reserve
+ * @param receiver_account where did we send the funds, in payto://-format
+ * @param wtid identifier used for the wire transfer
+ * @param close_request_row row with the responsible close
+ *            request, 0 if regular expiration triggered close
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_ReserveClosedCallback)(
+  void *cls,
+  uint64_t rowid,
+  struct GNUNET_TIME_Timestamp execution_date,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *closing_fee,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *receiver_account,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  uint64_t close_request_row);
+
+
+/**
+ * Function called with the amounts historically
+ * withdrawn from the same origin account.
+ *
+ * @param cls closure
+ * @param val one of the withdrawn amounts
+ */
+typedef void
+(*TALER_EXCHANGEDB_WithdrawHistoryCallback)(
+  void *cls,
+  const struct TALER_Amount *val);
+
+/**
+ * Function called with details about expired reserves.
+ *
+ * @param cls closure
+ * @param reserve_pub public key of the reserve
+ * @param left amount left in the reserve
+ * @param account_details information about the reserve's bank account, in 
payto://-format
+ * @param expiration_date when did the reserve expire
+ * @param close_request_row row that caused the reserve
+ *        to be closed, 0 if it expired without request
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO to retry
+ *         #GNUNET_SYSERR on hard failures (exit)
+ */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_ReserveExpiredCallback)(
+  void *cls,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *left,
+  const char *account_details,
+  struct GNUNET_TIME_Timestamp expiration_date,
+  uint64_t close_request_row);
+
+
+/**
+ * Function called with information justifying an aggregate recoup.
+ * (usually implemented by the auditor when verifying losses from recoups).
+ *
+ * @param cls closure
+ * @param rowid row identifier used to uniquely identify the recoup operation
+ * @param coin information about the coin
+ * @param coin_sig signature of the coin of type 
#TALER_SIGNATURE_WALLET_COIN_RECOUP
+ * @param coin_blind blinding key of the coin
+ * @param h_blinded_ev blinded envelope, as calculated by the exchange
+ * @param amount total amount to be paid back
+ */
+typedef void
+(*TALER_EXCHANGEDB_RecoupJustificationCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_CoinPublicInfo *coin,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const union TALER_DenominationBlindingKeyP *coin_blind,
+  const struct TALER_BlindedCoinHashP *h_blinded_ev,
+  const struct TALER_Amount *amount);
+
+
+/**
+ * Function called on deposits that are past their due date
+ * and have not yet seen a wire transfer.
+ *
+ * @param cls closure
+ * @param rowid deposit table row of the coin's deposit
+ * @param coin_pub public key of the coin
+ * @param amount value of the deposit, including fee
+ * @param payto_uri where should the funds be wired; URI in payto://-format
+ * @param deadline what was the requested wire transfer deadline
+ * @param done did the exchange claim that it made a transfer?
+ */
+typedef void
+(*TALER_EXCHANGEDB_WireMissingCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *amount,
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp deadline,
+  bool done);
+
+
+/**
+ * Function called on purse requests.
+ *
+ * @param cls closure
+ * @param rowid purse request table row of the purse
+ * @param purse_pub public key of the purse
+ * @param merge_pub public key representing the merge capability
+ * @param purse_creation when was the purse created?
+ * @param purse_expiration when would an unmerged purse expire
+ * @param h_contract_terms contract associated with the purse
+ * @param age_limit the age limit for deposits into the purse
+ * @param target_amount amount to be put into the purse
+ * @param purse_sig signature of the purse over the initialization data
+ * @return #GNUNET_OK to continue to iterate
+   */
+typedef enum GNUNET_GenericReturnValue
+(*TALER_EXCHANGEDB_PurseRequestCallback)(
+  void *cls,
+  uint64_t rowid,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  struct GNUNET_TIME_Timestamp purse_creation,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint32_t age_limit,
+  const struct TALER_Amount *target_amount,
+  const struct TALER_PurseContractSignatureP *purse_sig);
+
+
+/**
+ * Function called with information about the exchange's denomination keys.
+ * Note that the 'master' field in @a issue will not yet be initialized when
+ * this function is called!
+ *
+ * @param cls closure
+ * @param denom_pub public key of the denomination
+ * @param issue detailed information about the denomination (value, expiration 
times, fees);
+ */
+typedef void
+(*TALER_EXCHANGEDB_DenominationCallback)(
+  void *cls,
+  const struct TALER_DenominationPublicKey *denom_pub,
+  const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+
+/**
+ * Return AML status.
+ *
+ * @param cls closure
+ * @param row_id current row in AML status table
+ * @param h_payto account for which the attribute data is stored
+ * @param threshold currently monthly threshold that would trigger an AML check
+ * @param status what is the current AML decision
+ */
+typedef void
+(*TALER_EXCHANGEDB_AmlStatusCallback)(
+  void *cls,
+  uint64_t row_id,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_Amount *threshold,
+  enum TALER_AmlDecisionState status);
+
+
+/**
+ * Return historic AML decision.
+ *
+ * @param cls closure
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_status AML decision status
+ * @param decision_time when was the decision made
+ * @param justification human-readable text justifying the decision
+ * @param decider_pub public key of the staff member
+ * @param decider_sig signature of the staff member
+ */
+typedef void
+(*TALER_EXCHANGEDB_AmlHistoryCallback)(
+  void *cls,
+  const struct TALER_Amount *new_threshold,
+  enum TALER_AmlDecisionState new_status,
+  struct GNUNET_TIME_Timestamp decision_time,
+  const char *justification,
+  const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+  const struct TALER_AmlOfficerSignatureP *decider_sig);
+
+
+/**
+ * @brief The plugin API, returned from the plugin's "init" function.
+ * The argument given to "init" is simply a configuration handle.
+ */
+struct TALER_EXCHANGEDB_Plugin
+{
+
+  /**
+   * Closure for all callbacks.
+   */
+  void *cls;
+
+  /**
+   * Name of the library which generated this plugin.  Set by the
+   * plugin loader.
+   */
+  char *library_name;
+
+
+  /**
+   * Drop the Taler tables.  This should only be used in testcases.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+   */
+  enum GNUNET_GenericReturnValue
+  (*drop_tables)(void *cls);
+
+  /**
+   * Create the necessary tables if they are not present
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param support_partitions true to enable partitioning support (disables 
foreign key constraints)
+   * @param num_partitions number of partitions to create,
+   *     (0 to not actually use partitions, 1 to only
+   *      setup a default partition, >1 for real partitions)
+   * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+   */
+  enum GNUNET_GenericReturnValue
+  (*create_tables)(void *cls,
+                   bool support_partitions,
+                   uint32_t num_partitions);
+
+
+  /**
+   * Start a transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param name unique name identifying the transaction (for debugging),
+   *             must point to a constant
+   * @return #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue
+  (*start)(void *cls,
+           const char *name);
+
+
+  /**
+   * Start a READ COMMITTED transaction.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param name unique name identifying the transaction (for debugging)
+   *             must point to a constant
+   * @return #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue
+  (*start_read_committed)(void *cls,
+                          const char *name);
+
+  /**
+   * Start a READ ONLY serializable transaction.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param name unique name identifying the transaction (for debugging)
+   *             must point to a constant
+   * @return #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue
+  (*start_read_only)(void *cls,
+                     const char *name);
+
+
+  /**
+   * Commit a transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*commit)(void *cls);
+
+
+  /**
+   * Do a pre-flight check that we are not in an uncommitted transaction.
+   * If we are, try to commit the previous transaction and output a warning.
+   * Does not return anything, as we will continue regardless of the outcome.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @return #GNUNET_OK if everything is fine
+   *         #GNUNET_NO if a transaction was rolled back
+   *         #GNUNET_SYSERR on hard errors
+   */
+  enum GNUNET_GenericReturnValue
+  (*preflight)(void *cls);
+
+
+  /**
+   * Abort/rollback a transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   */
+  void
+  (*rollback) (void *cls);
+
+
+  /**
+   * Register callback to be invoked on events of type @a es.
+   *
+   * @param cls database context to use
+   * @param timeout how long to wait at most
+   * @param es specification of the event to listen for
+   * @param cb function to call when the event happens, possibly
+   *         multiple times (until cancel is invoked)
+   * @param cb_cls closure for @a cb
+   * @return handle useful to cancel the listener
+   */
+  struct GNUNET_DB_EventHandler *
+  (*event_listen)(void *cls,
+                  struct GNUNET_TIME_Relative timeout,
+                  const struct GNUNET_DB_EventHeaderP *es,
+                  GNUNET_DB_EventCallback cb,
+                  void *cb_cls);
+
+  /**
+   * Stop notifications.
+   *
+   * @param cls database context to use
+   * @param eh handle to unregister.
+   */
+  void
+  (*event_listen_cancel)(void *cls,
+                         struct GNUNET_DB_EventHandler *eh);
+
+
+  /**
+   * Notify all that listen on @a es of an event.
+   *
+   * @param cls database context to use
+   * @param es specification of the event to generate
+   * @param extra additional event data provided
+   * @param extra_size number of bytes in @a extra
+   */
+  void
+  (*event_notify)(void *cls,
+                  const struct GNUNET_DB_EventHeaderP *es,
+                  const void *extra,
+                  size_t extra_size);
+
+
+  /**
+   * Insert information about a denomination key and in particular
+   * the properties (value, fees, expiration times) the coins signed
+   * with this key have.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param denom_pub the public key used for signing coins of this 
denomination
+   * @param issue issuing information with value, fees and other info about 
the denomination
+   * @return status of the query
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_denomination_info)(
+    void *cls,
+    const struct TALER_DenominationPublicKey *denom_pub,
+    const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+
+  /**
+   * Fetch information about a denomination key.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param denom_pub_hash hash of the public key used for signing coins of 
this denomination
+   * @param[out] issue set to issue information with value, fees and other 
info about the coin
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_denomination_info)(
+    void *cls,
+    const struct TALER_DenominationHashP *denom_pub_hash,
+    struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+
+  /**
+   * Function called on every known denomination key.  Runs in its
+   * own read-only transaction (hence no session provided).  Note that
+   * the "master" field in the callback's 'issue' argument will NOT
+   * be initialized yet.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param cb function to call on each denomination key
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_denomination_info)(void *cls,
+                               TALER_EXCHANGEDB_DenominationCallback cb,
+                               void *cb_cls);
+
+
+  /**
+   * Function called to invoke @a cb on every known denomination key (revoked
+   * and non-revoked) that has been signed by the master key. Runs in its own
+   * read-only transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param cb function to call on each denomination key
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_denominations)(void *cls,
+                           TALER_EXCHANGEDB_DenominationsCallback cb,
+                           void *cb_cls);
+
+  /**
+   * Function called to invoke @a cb on every non-revoked exchange signing key
+   * that has been signed by the master key.  Revoked and (for signing!)
+   * expired keys are skipped. Runs in its own read-only transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param cb function to call on each signing key
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_active_signkeys)(void *cls,
+                             TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+                             void *cb_cls);
+
+
+  /**
+   * Function called to invoke @a cb on every active auditor. Disabled
+   * auditors are skipped. Runs in its own read-only transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param cb function to call on each active auditor
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_active_auditors)(void *cls,
+                             TALER_EXCHANGEDB_AuditorsCallback cb,
+                             void *cb_cls);
+
+
+  /**
+   * Function called to invoke @a cb on every denomination with an active
+   * auditor. Disabled auditors and denominations without auditor are
+   * skipped. Runs in its own read-only transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param cb function to call on each active auditor-denomination pair
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_auditor_denominations)(
+    void *cls,
+    TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Get the summary of a reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param[in,out] reserve the reserve data.  The public key of the reserve 
should be set
+   *          in this structure; it is used to query the database.  The balance
+   *          and expiration are then filled accordingly.
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*reserves_get)(void *cls,
+                  struct TALER_EXCHANGEDB_Reserve *reserve);
+
+
+  /**
+   * Get the origin of funds of a reserve.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param reserve_pub public key of the reserve
+   * @param[out] h_payto set to hash of the wire source payto://-URI
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*reserves_get_origin)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    struct TALER_PaytoHashP *h_payto);
+
+
+  /**
+   * Extract next KYC alert.  Deletes the alert.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param trigger_type which type of alert to drain
+   * @param[out] h_payto set to hash of payto-URI where KYC status changed
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*drain_kyc_alert)(void *cls,
+                     uint32_t trigger_type,
+                     struct TALER_PaytoHashP *h_payto);
+
+
+  /**
+   * Insert a batch of incoming transaction into reserves.  New reserves are
+   * also created through this function.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserves
+   * @param reserves_length length of the @a reserves array
+   * @param[out] results array of transaction status codes of length @a 
reserves_length,
+   *             set to the status of the
+   */
+  enum GNUNET_DB_QueryStatus
+  (*reserves_in_insert)(
+    void *cls,
+    const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+    unsigned int reserves_length,
+    enum GNUNET_DB_QueryStatus *results);
+
+
+  /**
+   * Locate a nonce for use with a particular public key.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param nonce the nonce to be locked
+   * @param denom_pub_hash hash of the public key of the denomination
+   * @param target public key the nonce is to be locked to
+   * @return statement execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lock_nonce)(void *cls,
+                const struct TALER_CsNonce *nonce,
+                const struct TALER_DenominationHashP *denom_pub_hash,
+                const union TALER_EXCHANGEDB_NonceLockTargetP *target);
+
+
+  /**
+   * Locate the response for a withdraw request under a hash that uniquely
+   * identifies the withdraw operation.  Used to ensure idempotency of the
+   * request.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param bch hash that uniquely identifies the withdraw operation
+   * @param[out] collectable corresponding collectable coin (blind signature)
+   *                    if a coin is found
+   * @return statement execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_withdraw_info)(void *cls,
+                       const struct TALER_BlindedCoinHashP *bch,
+                       struct TALER_EXCHANGEDB_CollectableBlindcoin 
*collectable);
+
+
+  /**
+   * Perform withdraw operation, checking for sufficient balance
+   * and possibly persisting the withdrawal details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+   * @param collectable corresponding collectable coin (blind signature)
+   * @param now current time (rounded)
+   * @param do_age_check set to true if age requirements must be checked.
+   * @param[out] found set to true if the reserve was found
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] nonce_ok set to false if the nonce was reused
+   * @param[out] age_ok set to true if no age requirements were defined on the 
reserve or @e do_age_check was false
+   * @param[out] allowed_maximum_age when @e age_ok is false, set to the 
allowed maximum age for withdrawal from the reserve.  The client MUST then use 
the age-withdraw endpoint
+   * @param[out] ruuid set to the reserve's UUID (reserves table row)
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_withdraw)(
+    void *cls,
+    const struct TALER_CsNonce *nonce,
+    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+    struct GNUNET_TIME_Timestamp now,
+    bool do_age_check,
+    bool *found,
+    bool *balance_ok,
+    bool *nonce_ok,
+    bool *age_ok,
+    uint16_t *allowed_maximum_age,
+    uint64_t *ruuid);
+
+
+  /**
+   * FIXME: merge do_batch_withdraw and do_batch_withdraw_insert into one API,
+   * which takes as input (among others)
+   *   - denom_serial[]
+   *   - blinded_coin_evs[]
+   *   - denom_sigs[]
+   * The implementation should persist the data as _arrays_ in the DB.
+   */
+
+  /**
+   * Perform reserve update as part of a batch withdraw operation, checking
+   * for sufficient balance. Persisting the withdrawal details is done
+   * separately!
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param now current time (rounded)
+   * @param reserve_pub public key of the reserve to debit
+   * @param amount total amount to withdraw
+   * @param do_age_check if set, the batch-withdrawal can only succeed when 
the reserve has no age restriction (birthday) set.
+   * @param[out] found set to true if the reserve was found
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] age_ok set to true if no age requirements were defined on the 
reserve or @e do_age_check was false
+   * @param[out] allowed_maximum_age when @e age_ok is false, set to the 
allowed maximum age for withdrawal from the reserve.  The client MUST then use 
the age-withdraw endpoint
+   * @param[out] ruuid set to the reserve's UUID (reserves table row)
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_batch_withdraw)(
+    void *cls,
+    struct GNUNET_TIME_Timestamp now,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    const struct TALER_Amount *amount,
+    bool do_age_check,
+    bool *found,
+    bool *balance_ok,
+    bool *age_ok,
+    uint16_t *allowed_maximum_age,
+    uint64_t *ruuid);
+
+
+  /**
+   * Perform insert as part of a batch withdraw operation, and persisting the
+   * withdrawal details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param nonce client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+   * @param collectable corresponding collectable coin (blind signature)
+   * @param now current time (rounded)
+   * @param ruuid reserve UUID
+   * @param[out] denom_unknown set if the denomination is unknown in the DB
+   * @param[out] conflict if the envelope was already in the DB
+   * @param[out] nonce_reuse if @a nonce was non-NULL and reused
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_batch_withdraw_insert)(
+    void *cls,
+    const struct TALER_CsNonce *nonce,
+    const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+    struct GNUNET_TIME_Timestamp now,
+    uint64_t ruuid,
+    bool *denom_unknown,
+    bool *conflict,
+    bool *nonce_reuse);
+
+  /**
+   * Locate the response for a age-withdraw request under a hash of the
+   * commitment and reserve_pub that uniquely identifies the age-withdraw
+   * operation.  Used to ensure idempotency of the request.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub public key of the reserve for which the age-withdraw 
request is made
+   * @param ach hash that uniquely identifies the age-withdraw operation
+   * @param[out] aw corresponding details of the previous age-withdraw request 
if an entry was found
+   * @return statement execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_age_withdraw)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    const struct TALER_AgeWithdrawCommitmentHashP *ach,
+    struct TALER_EXCHANGEDB_AgeWithdraw *aw);
+
+  /**
+   * Perform an age-withdraw operation, checking for sufficient balance and
+   * fulfillment of age requirements and possibly persisting the withdrawal
+   * details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param commitment corresponding commitment for the age-withdraw
+   * @param[out] found set to true if the reserve was found
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @param[out] age_ok set to true if age requirements were met
+   * @param[out] allowed_maximum_age if @e age_ok is FALSE, this is set to the 
allowed maximum age
+   * @param[out] reserve_birthday if @e age_ok is FALSE, this is set to the 
reserve's birthday
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_age_withdraw)(
+    void *cls,
+    const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+    struct GNUNET_TIME_Timestamp now,
+    bool *found,
+    bool *balance_ok,
+    bool *age_ok,
+    uint16_t *allowed_maximum_age,
+    uint32_t *reserve_birthday,
+    bool *conflict);
+
+  /**
+   * Retrieve the details to a policy given by its hash_code
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param hc Hash code that identifies the policy
+   * @param[out] detail retrieved policy details
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_policy_details)(
+    void *cls,
+    const struct GNUNET_HashCode *hc,
+    struct TALER_PolicyDetails *detail);
+
+  /**
+   * Persist the policy details that extends a deposit.  The particular policy
+   * - referenced by details->hash_code - might already exist in the table, in
+   * which case the call will update the contents of the record with @e details
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param details The parsed `struct TALER_PolicyDetails` according to the 
responsible policy extension.
+   * @param[out] policy_details_serial_id The ID of the entry in the 
policy_details table
+   * @param[out] accumulated_total The total amount accumulated in that policy
+   * @param[out] fulfillment_state The state of policy.  If the state was 
Insufficient prior to the call and the provided deposit raises the 
accumulated_total above the commitment, it will be set to Ready.
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*persist_policy_details)(
+    void *cls,
+    const struct TALER_PolicyDetails *details,
+    uint64_t *policy_details_serial_id,
+    struct TALER_Amount *accumulated_total,
+    enum TALER_PolicyFulfillmentState *fulfillment_state);
+
+
+  /**
+   * Perform deposit operation, checking for sufficient balance
+   * of the coin and possibly persisting the deposit details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @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] 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_BatchDeposit *bd,
+    struct GNUNET_TIME_Timestamp *exchange_timestamp,
+    bool *balance_ok,
+    uint32_t *bad_balance_index,
+    bool *ctr_conflict);
+
+
+  /**
+   * Perform melt operation, checking for sufficient balance
+   * of the coin and possibly persisting the melt details.
+   *
+   * @param cls the plugin-specific state
+   * @param rms client-contributed input for CS denominations that must be 
checked for idempotency, or NULL for non-CS withdrawals
+   * @param[in,out] refresh refresh operation details; the noreveal_index
+   *                is set in case the coin was already melted before
+   * @param known_coin_id row of the coin in the known_coins table
+   * @param[in,out] zombie_required true if the melt must only succeed if the 
coin is a zombie, set to false if the requirement was satisfied
+   * @param[out] balance_ok set to true if the balance was sufficient
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_melt)(
+    void *cls,
+    const struct TALER_RefreshMasterSecretP *rms,
+    struct TALER_EXCHANGEDB_Refresh *refresh,
+    uint64_t known_coin_id,
+    bool *zombie_required,
+    bool *balance_ok);
+
+
+  /**
+   * Add a proof of fulfillment of an policy
+   *
+   * @param cls the plugin-specific state
+   * @param[in,out] fulfillment The proof of fulfillment and serial_ids of the 
policy_details along with their new state and potential new amounts.
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*add_policy_fulfillment_proof)(
+    void *cls,
+    struct TALER_PolicyFulfillmentTransactionData *fulfillment);
+
+
+  /**
+   * Check if the given @a nonce was properly locked to the given @a 
old_coin_pub. If so, check if we already
+   * created CS signatures for the given @a nonce and @a new_denom_pub_hashes,
+   * and if so, return them in @a s_scalars.  Otherwise, persist the
+   * signatures from @a s_scalars in the database.
+   *
+   * @param cls the plugin-specific state
+   * @param nonce the client-provided nonce where we must prevent reuse
+   * @param old_coin_pub public key the nonce was locked to
+   * @param num_fresh_coins array length, number of fresh coins revealed
+   * @param[in,out] crfcds array of data about the fresh coins, of length @a 
num_fresh_coins
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*cs_refreshes_reveal)(
+    void *cls,
+    const struct TALER_CsNonce *nonce,
+    const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+    unsigned int num_fresh_coins,
+    struct TALER_EXCHANGEDB_CsRevealFreshCoinData *crfcds);
+
+
+  /**
+   * Perform refund operation, checking for sufficient deposits
+   * of the coin and possibly persisting the refund details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param refund refund operation details
+   * @param deposit_fee deposit fee applicable for the coin, possibly refunded
+   * @param known_coin_id row of the coin in the known_coins table
+   * @param[out] not_found set if the deposit was not found
+   * @param[out] refund_ok  set if the refund succeeded (below deposit amount)
+   * @param[out] gone if the merchant was already paid
+   * @param[out] conflict set if the refund ID was re-used
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_refund)(
+    void *cls,
+    const struct TALER_EXCHANGEDB_Refund *refund,
+    const struct TALER_Amount *deposit_fee,
+    uint64_t known_coin_id,
+    bool *not_found,
+    bool *refund_ok,
+    bool *gone,
+    bool *conflict);
+
+
+  /**
+   * Perform recoup operation, checking for sufficient deposits
+   * of the coin and possibly persisting the recoup details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param reserve_pub public key of the reserve to credit
+   * @param reserve_out_serial_id row in the reserves_out table justifying the 
recoup
+   * @param coin_bks coin blinding key secret to persist
+   * @param coin_pub public key of the coin being recouped
+   * @param known_coin_id row of the @a coin_pub in the known_coins table
+   * @param coin_sig signature of the coin requesting the recoup
+   * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+   * @param[out] recoup_ok  set if the recoup succeeded (balance ok)
+   * @param[out] internal_failure set on internal failures
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_recoup)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    uint64_t reserve_out_serial_id,
+    const union TALER_DenominationBlindingKeyP *coin_bks,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    uint64_t known_coin_id,
+    const struct TALER_CoinSpendSignatureP *coin_sig,
+    struct GNUNET_TIME_Timestamp *recoup_timestamp,
+    bool *recoup_ok,
+    bool *internal_failure);
+
+
+  /**
+   * Perform recoup-refresh operation, checking for sufficient deposits of the
+   * coin and possibly persisting the recoup-refresh details.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param old_coin_pub public key of the old coin to credit
+   * @param rrc_serial row in the refresh_revealed_coins table justifying the 
recoup-refresh
+   * @param coin_bks coin blinding key secret to persist
+   * @param coin_pub public key of the coin being recouped
+   * @param known_coin_id row of the @a coin_pub in the known_coins table
+   * @param coin_sig signature of the coin requesting the recoup
+   * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+   * @param[out] recoup_ok  set if the recoup-refresh succeeded (balance ok)
+   * @param[out] internal_failure set on internal failures
+   * @return query execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_recoup_refresh)(
+    void *cls,
+    const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+    uint64_t rrc_serial,
+    const union TALER_DenominationBlindingKeyP *coin_bks,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    uint64_t known_coin_id,
+    const struct TALER_CoinSpendSignatureP *coin_sig,
+    struct GNUNET_TIME_Timestamp *recoup_timestamp,
+    bool *recoup_ok,
+    bool *internal_failure);
+
+
+  /**
+   * Get all of the transaction history associated with the specified
+   * reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub public key of the reserve
+   * @param[out] balance set to the reserve balance
+   * @param[out] rhp set to known transaction history (NULL if reserve is 
unknown)
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_reserve_history)(void *cls,
+                         const struct TALER_ReservePublicKeyP *reserve_pub,
+                         struct TALER_Amount *balance,
+                         struct TALER_EXCHANGEDB_ReserveHistory **rhp);
+
+
+  /**
+   * Get truncated transaction history associated with the specified
+   * reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub public key of the reserve
+   * @param[out] balance_in set to the total of inbound
+   *             transactions in the returned history
+   * @param[out] balance_out set to the total of outbound
+   *             transactions in the returned history
+   * @param[out] rhp set to known transaction history (NULL if reserve is 
unknown)
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_reserve_status)(void *cls,
+                        const struct TALER_ReservePublicKeyP *reserve_pub,
+                        struct TALER_Amount *balance_in,
+                        struct TALER_Amount *balance_out,
+                        struct TALER_EXCHANGEDB_ReserveHistory **rhp);
+
+
+  /**
+   * The current reserve balance of the specified reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub public key of the reserve
+   * @param[out] balance set to the reserve balance
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_reserve_balance)(void *cls,
+                         const struct TALER_ReservePublicKeyP *reserve_pub,
+                         struct TALER_Amount *balance);
+
+
+  /**
+   * Free memory associated with the given reserve history.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param rh history to free.
+   */
+  void
+  (*free_reserve_history) (void *cls,
+                           struct TALER_EXCHANGEDB_ReserveHistory *rh);
+
+
+  /**
+   * Count the number of known coins by denomination.
+   *
+   * @param cls database connection plugin state
+   * @param denom_pub_hash denomination to count by
+   * @return number of coins if non-negative, otherwise an `enum 
GNUNET_DB_QueryStatus`
+   */
+  long long
+  (*count_known_coins) (void *cls,
+                        const struct TALER_DenominationHashP *denom_pub_hash);
+
+
+  /**
+   * Make sure the given @a coin is known to the database.
+   *
+   * @param cls database connection plugin state
+   * @param coin the coin that must be made known
+   * @param[out] known_coin_id set to the unique row of the coin
+   * @param[out] denom_pub_hash set to the conflicting denomination hash on 
conflict
+   * @param[out] age_hash set to the conflicting age hash on conflict
+   * @return database transaction status, non-negative on success
+   */
+  enum TALER_EXCHANGEDB_CoinKnownStatus
+  {
+    /**
+     * The coin was successfully added.
+     */
+    TALER_EXCHANGEDB_CKS_ADDED = 1,
+
+    /**
+     * The coin was already present.
+     */
+    TALER_EXCHANGEDB_CKS_PRESENT = 0,
+
+    /**
+     * Serialization failure.
+     */
+    TALER_EXCHANGEDB_CKS_SOFT_FAIL = -1,
+
+    /**
+     * Hard database failure.
+     */
+    TALER_EXCHANGEDB_CKS_HARD_FAIL = -2,
+
+    /**
+     * Conflicting coin (different denomination key) already in database.
+     */
+    TALER_EXCHANGEDB_CKS_DENOM_CONFLICT = -3,
+
+    /**
+     * Conflicting coin (different age hash) already in database.
+     */
+    TALER_EXCHANGEDB_CKS_AGE_CONFLICT = -4,
+  }
+  (*ensure_coin_known)(void *cls,
+                       const struct TALER_CoinPublicInfo *coin,
+                       uint64_t *known_coin_id,
+                       struct TALER_DenominationHashP *denom_pub_hash,
+                       struct TALER_AgeCommitmentHash *age_hash);
+
+
+  /**
+   * Make sure the array of given @a coin is known to the database.
+   *
+   * @param cls database connection plugin state
+   * @param coin array of coins that must be made known
+   * @param[out] result array where to store information about each coin
+   * @param coin_length length of the @a coin and @a result arraysf
+   * @param batch_size desired (maximum) batch size
+   * @return database transaction status, non-negative on success
+   */
+  enum GNUNET_DB_QueryStatus
+  (*batch_ensure_coin_known)(
+    void *cls,
+    const struct TALER_CoinPublicInfo *coin,
+    struct TALER_EXCHANGEDB_CoinInfo *result,
+    unsigned int coin_length,
+    unsigned int batch_size);
+
+
+  /**
+   * Retrieve information about the given @a coin from the database.
+   *
+   * @param cls database connection plugin state
+   * @param coin the coin that must be made known
+   * @return database transaction status, non-negative on success
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_known_coin)(void *cls,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    struct TALER_CoinPublicInfo *coin_info);
+
+
+  /**
+   * Retrieve the denomination of a known coin.
+   *
+   * @param cls the plugin closure
+   * @param coin_pub the public key of the coin to search for
+   * @param[out] known_coin_id set to the ID of the coin in the known_coins 
table
+   * @param[out] denom_hash where to store the hash of the coins denomination
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_coin_denomination)(void *cls,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           uint64_t *known_coin_id,
+                           struct TALER_DenominationHashP *denom_hash);
+
+
+  /**
+   * Check if we have the specified deposit already in the database.
+   *
+   * @param cls the `struct PostgresClosure` with the plugin-specific state
+   * @param h_contract_terms contract to check for
+   * @param h_wire wire hash to check for
+   * @param coin_pub public key of the coin to check for
+   * @param merchant merchant public key to check for
+   * @param refund_deadline expected refund deadline
+   * @param[out] deposit_fee set to the deposit fee the exchange charged
+   * @param[out] exchange_timestamp set to the time when the exchange received 
the deposit
+   * @return 1 if we know this operation,
+   *         0 if this exact deposit is unknown to us,
+   *         otherwise transaction error status
+   */
+  // FIXME: rename!
+  enum GNUNET_DB_QueryStatus
+  (*have_deposit2)(
+    void *cls,
+    const struct TALER_PrivateContractHashP *h_contract_terms,
+    const struct TALER_MerchantWireHashP *h_wire,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    const struct TALER_MerchantPublicKeyP *merchant,
+    struct GNUNET_TIME_Timestamp refund_deadline,
+    struct TALER_Amount *deposit_fee,
+    struct GNUNET_TIME_Timestamp *exchange_timestamp);
+
+
+  /**
+   * Insert information about refunded coin into the database.
+   * Used in tests and for benchmarking.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param refund refund information to store
+   * @return query result status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_refund)(void *cls,
+                   const struct TALER_EXCHANGEDB_Refund *refund);
+
+
+  /**
+   * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
+   *
+   * @param cls closure of plugin
+   * @param coin_pub coin to get refunds for
+   * @param merchant_pub merchant to get refunds for
+   * @param h_contract_pub contract (hash) to get refunds for
+   * @param cb function to call for each refund found
+   * @param cb_cls closure for @a cb
+   * @return query result status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_refunds_by_coin)(void *cls,
+                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                            const struct TALER_MerchantPublicKeyP 
*merchant_pub,
+                            const struct TALER_PrivateContractHashP 
*h_contract,
+                            TALER_EXCHANGEDB_RefundCoinCallback cb,
+                            void *cb_cls);
+
+
+  /**
+   * Obtain information about deposits that are ready to be executed.
+   * Such deposits must not be marked as "done", and the
+   * execution time, the refund deadlines must both be in the past and
+   * the KYC status must be 'ok'.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param start_shard_row minimum shard row to select
+   * @param end_shard_row maximum shard row to select (inclusive)
+   * @param[out] merchant_pub set to the public key of a merchant with a ready 
deposit
+   * @param[out] payto_uri set to the account of the merchant, to be freed by 
caller
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_ready_deposit)(void *cls,
+                       uint64_t start_shard_row,
+                       uint64_t end_shard_row,
+                       struct TALER_MerchantPublicKeyP *merchant_pub,
+                       char **payto_uri);
+
+
+  /**
+   * Aggregate all matching deposits for @a h_payto and
+   * @a merchant_pub, returning the total amounts.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param merchant_pub public key of the merchant
+   * @param wtid wire transfer ID to set for the aggregate
+   * @param[out] total set to the sum of the total deposits minus applicable 
deposit fees and refunds
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*aggregate)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_MerchantPublicKeyP *merchant_pub,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    struct TALER_Amount *total);
+
+
+  /**
+   * Create a new entry in the transient aggregation table.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param exchange_account_section exchange account to use
+   * @param merchant_pub public key of the merchant
+   * @param wtid the raw wire transfer identifier to be used
+   * @param kyc_requirement_row row in legitimization_requirements that need 
to be satisfied to continue, or 0 for none
+   * @param total amount to be wired in the future
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*create_aggregation_transient)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const char *exchange_account_section,
+    const struct TALER_MerchantPublicKeyP *merchant_pub,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    uint64_t kyc_requirement_row,
+    const struct TALER_Amount *total);
+
+
+  /**
+   * Select existing entry in the transient aggregation table.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param merchant_pub public key of the merchant
+   * @param exchange_account_section exchange account to use
+   * @param[out] wtid set to the raw wire transfer identifier to be used
+   * @param[out] total existing amount to be wired in the future
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_aggregation_transient)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_MerchantPublicKeyP *merchant_pub,
+    const char *exchange_account_section,
+    struct TALER_WireTransferIdentifierRawP *wtid,
+    struct TALER_Amount *total);
+
+
+  /**
+   * Find existing entry in the transient aggregation table.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param cb function to call on each matching entry
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*find_aggregation_transient)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_TransientAggregationCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Update existing entry in the transient aggregation table.
+   * @a h_payto is only needed for query performance.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param wtid the raw wire transfer identifier to update
+   * @param kyc_requirement_row row in legitimization_requirements that need 
to be satisfied to continue, or 0 for none
+   * @param total new total amount to be wired in the future
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_aggregation_transient)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    uint64_t kyc_requirement_row,
+    const struct TALER_Amount *total);
+
+
+  /**
+   * Delete existing entry in the transient aggregation table.
+   * @a h_payto is only needed for query performance.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto destination of the wire transfer
+   * @param wtid the raw wire transfer identifier to update
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_aggregation_transient)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_WireTransferIdentifierRawP *wtid);
+
+
+  /**
+   * Lookup melt commitment data under the given @a rc.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param rc commitment to use for the lookup
+   * @param[out] melt where to store the result; note that
+   *             melt->session.coin.denom_sig will be set to NULL
+   *             and is not fetched by this routine (as it is not needed by 
the client)
+   * @param[out] melt_serial_id set to the row ID of @a rc in the 
refresh_commitments table
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_melt)(void *cls,
+              const struct TALER_RefreshCommitmentP *rc,
+              struct TALER_EXCHANGEDB_Melt *melt,
+              uint64_t *melt_serial_id);
+
+
+  /**
+   * Store in the database which coin(s) the wallet wanted to create
+   * in a given refresh operation and all of the other information
+   * we learned or created in the reveal step.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param melt_serial_id row ID of the commitment / melt operation in 
refresh_commitments
+   * @param num_rrcs number of coins to generate, size of the @a rrcs array
+   * @param rrcs information about the new coins
+   * @param num_tprivs number of entries in @a tprivs, should be 
#TALER_CNC_KAPPA - 1
+   * @param tprivs transfer private keys to store
+   * @param tp public key to store
+   * @return query status for the transaction
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_refresh_reveal)(
+    void *cls,
+    uint64_t melt_serial_id,
+    uint32_t num_rrcs,
+    const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
+    unsigned int num_tprivs,
+    const struct TALER_TransferPrivateKeyP *tprivs,
+    const struct TALER_TransferPublicKeyP *tp);
+
+
+  /**
+   * Lookup in the database for the fresh coins that we
+   * created in the given refresh operation.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param rc identify commitment and thus refresh operation
+   * @param cb function to call with the results
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_refresh_reveal)(void *cls,
+                        const struct TALER_RefreshCommitmentP *rc,
+                        TALER_EXCHANGEDB_RefreshCallback cb,
+                        void *cb_cls);
+
+
+  /**
+   * Obtain shared secret and transfer public key from the public key of
+   * the coin.  This information and the link information returned by
+   * @e get_link_data_list() enable the owner of an old coin to determine
+   * the private keys of the new coins after the melt.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param coin_pub public key of the coin
+   * @param ldc function to call for each session the coin was melted into
+   * @param ldc_cls closure for @a tdc
+   * @return statement execution status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_link_data)(void *cls,
+                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                   TALER_EXCHANGEDB_LinkCallback ldc,
+                   void *tdc_cls);
+
+
+  /**
+   * Compile a list of all (historic) transactions performed
+   * with the given coin (melt, refund, recoup and deposit operations).
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param coin_pub coin to investigate
+   * @param[out] tlp set to list of transactions, NULL if coin is fresh
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_coin_transactions)(void *cls,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           struct TALER_EXCHANGEDB_TransactionList **tlp);
+
+
+  /**
+   * Free linked list of transactions.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param list list to free
+   */
+  void
+  (*free_coin_transaction_list) (void *cls,
+                                 struct TALER_EXCHANGEDB_TransactionList 
*list);
+
+
+  /**
+   * Lookup the list of Taler transactions that was aggregated
+   * into a wire transfer by the respective @a raw_wtid.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param wtid the raw wire transfer identifier we used
+   * @param cb function to call on each transaction found
+   * @param cb_cls closure for @a cb
+   * @return query status of the transaction
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_wire_transfer)(void *cls,
+                          const struct TALER_WireTransferIdentifierRawP *wtid,
+                          TALER_EXCHANGEDB_AggregationDataCallback cb,
+                          void *cb_cls);
+
+
+  /**
+   * Try to find the wire transfer details for a deposit operation.
+   * If we did not execute the deposit yet, return when it is supposed
+   * to be executed.
+   *
+   * @param cls closure
+   * @param h_contract_terms hash of the proposal data
+   * @param h_wire hash of merchant wire details
+   * @param coin_pub public key of deposited coin
+   * @param merchant_pub merchant public key
+   * @param[out] pending set to true if the transaction is still pending
+   * @param[out] wtid wire transfer identifier, only set if @a pending is false
+   * @param[out] coin_contribution how much did the coin we asked about
+   *        contribute to the total transfer value? (deposit value including 
fee)
+   * @param[out] coin_fee how much did the exchange charge for the deposit fee
+   * @param[out] execution_time when was the transaction done, or
+   *         when we expect it to be done (if @a pending is false)
+   * @param[out] kyc set to the kyc status of the receiver (if @a pending)
+   * @param[out] aml_decision set to the current AML status for the target 
account
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_transfer_by_deposit)(
+    void *cls,
+    const struct TALER_PrivateContractHashP *h_contract_terms,
+    const struct TALER_MerchantWireHashP *h_wire,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    const struct TALER_MerchantPublicKeyP *merchant_pub,
+    bool *pending,
+    struct TALER_WireTransferIdentifierRawP *wtid,
+    struct GNUNET_TIME_Timestamp *exec_time,
+    struct TALER_Amount *amount_with_fee,
+    struct TALER_Amount *deposit_fee,
+    struct TALER_EXCHANGEDB_KycStatus *kyc,
+    enum TALER_AmlDecisionState *aml_decision);
+
+
+  /**
+   * Insert wire transfer fee into database.
+   *
+   * @param cls closure
+   * @param wire_method which wire method is the fee about?
+   * @param start_date when does the fee go into effect
+   * @param end_date when does the fee end being valid
+   * @param fees how high is are the wire fees
+   * @param master_sig signature over the above by the exchange master key
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_wire_fee)(void *cls,
+                     const char *wire_method,
+                     struct GNUNET_TIME_Timestamp start_date,
+                     struct GNUNET_TIME_Timestamp end_date,
+                     const struct TALER_WireFeeSet *fees,
+                     const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Insert global fee set into database.
+   *
+   * @param cls closure
+   * @param start_date when does the fees go into effect
+   * @param end_date when does the fees end being valid
+   * @param fees how high is are the global fees
+   * @param purse_timeout when do purses time out
+   * @param history_expiration how long are account histories preserved
+   * @param purse_account_limit how many purses are free per account
+   * @param master_sig signature over the above by the exchange master key
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_global_fee)(void *cls,
+                       struct GNUNET_TIME_Timestamp start_date,
+                       struct GNUNET_TIME_Timestamp end_date,
+                       const struct TALER_GlobalFeeSet *fees,
+                       struct GNUNET_TIME_Relative purse_timeout,
+                       struct GNUNET_TIME_Relative history_expiration,
+                       uint32_t purse_account_limit,
+
+                       const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Obtain wire fee from database.
+   *
+   * @param cls closure
+   * @param type type of wire transfer the fee applies for
+   * @param date for which date do we want the fee?
+   * @param[out] start_date when does the fee go into effect
+   * @param[out] end_date when does the fee end being valid
+   * @param[out] fees how high are the wire fees
+   * @param[out] master_sig signature over the above by the exchange master key
+   * @return query status of the transaction
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_wire_fee)(void *cls,
+                  const char *type,
+                  struct GNUNET_TIME_Timestamp date,
+                  struct GNUNET_TIME_Timestamp *start_date,
+                  struct GNUNET_TIME_Timestamp *end_date,
+                  struct TALER_WireFeeSet *fees,
+                  struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Obtain global fees from database.
+   *
+   * @param cls closure
+   * @param date for which date do we want the fee?
+   * @param[out] start_date when does the fee go into effect
+   * @param[out] end_date when does the fee end being valid
+   * @param[out] fees how high are the global fees
+   * @param[out] purse_timeout when do purses time out
+   * @param[out] history_expiration how long are account histories preserved
+   * @param[out] purse_account_limit how many purses are free per account
+   * @param[out] master_sig signature over the above by the exchange master key
+   * @return query status of the transaction
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_global_fee)(void *cls,
+                    struct GNUNET_TIME_Timestamp date,
+                    struct GNUNET_TIME_Timestamp *start_date,
+                    struct GNUNET_TIME_Timestamp *end_date,
+                    struct TALER_GlobalFeeSet *fees,
+                    struct GNUNET_TIME_Relative *purse_timeout,
+                    struct GNUNET_TIME_Relative *history_expiration,
+                    uint32_t *purse_account_limit,
+                    struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Obtain information about expired reserves and their
+   * remaining balances.
+   *
+   * @param cls closure of the plugin
+   * @param now timestamp based on which we decide expiration
+   * @param rec function to call on expired reserves
+   * @param rec_cls closure for @a rec
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_expired_reserves)(void *cls,
+                          struct GNUNET_TIME_Timestamp now,
+                          TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+                          void *rec_cls);
+
+
+  /**
+   * Obtain information about force-closed reserves
+   * where the close was not yet done (and their remaining
+   * balances).  Updates the returned reserve's close
+   * status to "done".
+   *
+   * @param cls closure of the plugin
+   * @param rec function to call on (to be) closed reserves
+   * @param rec_cls closure for @a rec
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_unfinished_close_requests)(
+    void *cls,
+    TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+    void *rec_cls);
+
+
+  /**
+   * Insert reserve open coin deposit data into database.
+   * Subtracts the @a coin_total from the coin's balance.
+   *
+   * @param cls closure
+   * @param cpi public information about the coin
+   * @param coin_sig signature with @e coin_pub of type 
#TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+   * @param known_coin_id ID of the coin in the known_coins table
+   * @param coin_total amount to be spent of the coin (including deposit fee)
+   * @param reserve_sig signature by the reserve affirming the open operation
+   * @param reserve_pub public key of the reserve being opened
+   * @param[out] insufficient_funds set to true if the coin's balance is 
insufficient, otherwise to false
+   * @return transaction status code, 0 if operation is already in the DB
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_reserve_open_deposit)(
+    void *cls,
+    const struct TALER_CoinPublicInfo *cpi,
+    const struct TALER_CoinSpendSignatureP *coin_sig,
+    uint64_t known_coin_id,
+    const struct TALER_Amount *coin_total,
+    const struct TALER_ReserveSignatureP *reserve_sig,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    bool *insufficient_funds);
+
+
+  /**
+   * Insert reserve close operation into database.
+   *
+   * @param cls closure
+   * @param reserve_pub which reserve is this about?
+   * @param total_paid total amount paid (coins and reserve)
+   * @param reserve_payment amount to be paid from the reserve
+   * @param min_purse_limit minimum number of purses we should be able to open
+   * @param reserve_sig signature by the reserve for the operation
+   * @param desired_expiration when should the reserve expire (earliest time)
+   * @param now when did we the client initiate the action
+   * @param open_fee annual fee to be charged for the open operation by the 
exchange
+   * @param[out] no_funds set to true if reserve balance is insufficient
+   * @param[out] open_cost set to the actual cost
+   * @param[out] final_expiration when will the reserve expire now
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_reserve_open)(void *cls,
+                     const struct TALER_ReservePublicKeyP *reserve_pub,
+                     const struct TALER_Amount *total_paid,
+                     const struct TALER_Amount *reserve_payment,
+                     uint32_t min_purse_limit,
+                     const struct TALER_ReserveSignatureP *reserve_sig,
+                     struct GNUNET_TIME_Timestamp desired_expiration,
+                     struct GNUNET_TIME_Timestamp now,
+                     const struct TALER_Amount *open_fee,
+                     bool *no_funds,
+                     struct TALER_Amount *open_cost,
+                     struct GNUNET_TIME_Timestamp *final_expiration);
+
+
+  /**
+   * Select information needed to see if we can close
+   * a reserve.
+   *
+   * @param cls closure
+   * @param reserve_pub which reserve is this about?
+   * @param[out] balance current reserve balance
+   * @param[out] payto_uri set to URL of account that
+   *             originally funded the reserve;
+   *             could be set to NULL if not known
+   * @return transaction status code, 0 if reserve unknown
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserve_close_info)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    struct TALER_Amount *balance,
+    char **payto_uri);
+
+
+  /**
+   * Select information about reserve close requests.
+   *
+   * @param cls closure
+   * @param reserve_pub which reserve is this about?
+   * @param rowid row ID of the close request
+   * @param[out] reserve_sig reserve signature affirming
+   * @param[out] request_timestamp when was the request made
+   * @param[out] close_balance reserve balance at close time
+   * @param[out] close_fee closing fee to be charged
+   * @param[out] payto_uri set to URL of account that
+   *             should receive the money;
+   *             could be set to NULL for origin
+   * @return transaction status code, 0 if reserve unknown
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserve_close_request_info)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    uint64_t rowid,
+    struct TALER_ReserveSignatureP *reserve_sig,
+    struct GNUNET_TIME_Timestamp *request_timestamp,
+    struct TALER_Amount *close_balance,
+    struct TALER_Amount *close_fee,
+    char **payto_uri);
+
+
+  /**
+   * Select information needed for KYC checks on reserve close: historic
+   * reserve closures going to the same account.
+   *
+   * @param cls closure
+   * @param h_payto which target account is this about?
+   * @param h_payto account identifier
+   * @param time_limit oldest transaction that could be relevant
+   * @param kac function to call for each applicable amount, in reverse 
chronological order (or until @a kac aborts by returning anything except 
#GNUNET_OK).
+   * @param kac_cls closure for @a kac
+   * @return transaction status code, @a kac aborting with #GNUNET_NO is not 
an error
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_reserve_close_info)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    struct GNUNET_TIME_Absolute time_limit,
+    TALER_EXCHANGEDB_KycAmountCallback kac,
+    void *kac_cls);
+
+
+  /**
+   * Insert reserve close operation into database.
+   *
+   * @param cls closure
+   * @param reserve_pub which reserve is this about?
+   * @param execution_date when did we perform the transfer?
+   * @param receiver_account to which account do we transfer, in 
payto://-format
+   * @param wtid identifier for the wire transfer
+   * @param amount_with_fee amount we charged to the reserve
+   * @param closing_fee how high is the closing fee
+   * @param close_request_row identifies explicit close request, 0 for none
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_reserve_closed)(void *cls,
+                           const struct TALER_ReservePublicKeyP *reserve_pub,
+                           struct GNUNET_TIME_Timestamp execution_date,
+                           const char *receiver_account,
+                           const struct TALER_WireTransferIdentifierRawP *wtid,
+                           const struct TALER_Amount *amount_with_fee,
+                           const struct TALER_Amount *closing_fee,
+                           uint64_t close_request_row);
+
+
+  /**
+   * Function called to insert wire transfer commit data into the DB.
+   *
+   * @param cls closure
+   * @param type type of the wire transfer (i.e. "iban")
+   * @param buf buffer with wire transfer preparation data
+   * @param buf_size number of bytes in @a buf
+   * @return query status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*wire_prepare_data_insert)(void *cls,
+                              const char *type,
+                              const char *buf,
+                              size_t buf_size);
+
+
+  /**
+   * Function called to mark wire transfer commit data as finished.
+   *
+   * @param cls closure
+   * @param rowid which entry to mark as finished
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*wire_prepare_data_mark_finished)(void *cls,
+                                     uint64_t rowid);
+
+
+  /**
+   * Function called to mark wire transfer as failed.
+   *
+   * @param cls closure
+   * @param rowid which entry to mark as failed
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*wire_prepare_data_mark_failed)(void *cls,
+                                   uint64_t rowid);
+
+
+  /**
+   * Function called to get an unfinished wire transfer
+   * preparation data.
+   *
+   * @param cls closure
+   * @param start_row offset to query table at
+   * @param limit maximum number of results to return
+   * @param cb function to call for unfinished work
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*wire_prepare_data_get)(void *cls,
+                           uint64_t start_row,
+                           uint64_t limit,
+                           TALER_EXCHANGEDB_WirePreparationIterator cb,
+                           void *cb_cls);
+
+
+  /**
+   * Starts a READ COMMITTED transaction where we transiently violate the 
foreign
+   * constraints on the "wire_out" table as we insert aggregations
+   * and only add the wire transfer out at the end.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @return #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue
+  (*start_deferred_wire_out)(void *cls);
+
+
+  /**
+   * Store information about an outgoing wire transfer that was executed.
+   *
+   * @param cls closure
+   * @param date time of the wire transfer
+   * @param h_payto identifies the receiver account of the wire transfer
+   * @param wire_account details about the receiver account of the wire 
transfer,
+   *        including 'url' in payto://-format
+   * @param amount amount that was transmitted
+   * @param exchange_account_section configuration section of the exchange 
specifying the
+   *        exchange's bank account being used
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*store_wire_transfer_out)(
+    void *cls,
+    struct GNUNET_TIME_Timestamp date,
+    const struct TALER_WireTransferIdentifierRawP *wtid,
+    const struct TALER_PaytoHashP *h_payto,
+    const char *exchange_account_section,
+    const struct TALER_Amount *amount);
+
+
+  /**
+   * Function called to perform "garbage collection" on the
+   * database, expiring records we no longer require.
+   *
+   * @param cls closure
+   * @return #GNUNET_OK on success,
+   *         #GNUNET_SYSERR on DB errors
+   */
+  enum GNUNET_GenericReturnValue
+  (*gc)(void *cls);
+
+
+  /**
+   * Select deposits above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_coin_deposits_above_serial_id)(void *cls,
+                                          uint64_t serial_id,
+                                          TALER_EXCHANGEDB_DepositCallback cb,
+                                          void *cb_cls);
+
+
+  /**
+   * Function called to return meta data about a purses
+   * above a certain serial ID.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param serial_id number to select requests by
+   * @param cb function to call on each request
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_requests_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_PurseRequestCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select purse deposits above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_deposits_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_PurseDepositCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select account merges above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_account_merges_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_AccountMergeCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select purse merges deposits above @a serial_id in monotonically 
increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_merges_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_PurseMergeCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select history requests above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_history_requests_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_HistoryRequestCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select purse refunds above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param refunded which refund status to select for
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_decisions_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    bool refunded,
+    TALER_EXCHANGEDB_PurseDecisionCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select all purse refunds above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_all_purse_decisions_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select coins deposited into a purse.
+   *
+   * @param cls closure
+   * @param purse_pub public key of the purse
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_deposits_by_purse)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select refresh sessions above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_refreshes_above_serial_id)(void *cls,
+                                      uint64_t serial_id,
+                                      TALER_EXCHANGEDB_RefreshesCallback cb,
+                                      void *cb_cls);
+
+
+  /**
+   * Select refunds above @a serial_id in monotonically increasing
+   * order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_refunds_above_serial_id)(void *cls,
+                                    uint64_t serial_id,
+                                    TALER_EXCHANGEDB_RefundCallback cb,
+                                    void *cb_cls);
+
+
+  /**
+   * Select inbound wire transfers into reserves_in above @a serial_id
+   * in monotonically increasing order.
+   *
+   * @param cls closure
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserves_in_above_serial_id)(void *cls,
+                                        uint64_t serial_id,
+                                        TALER_EXCHANGEDB_ReserveInCallback cb,
+                                        void *cb_cls);
+
+
+  /**
+   * Select inbound wire transfers into reserves_in above @a serial_id
+   * in monotonically increasing order by @a account_name.
+   *
+   * @param cls closure
+   * @param account_name name of the account for which we do the selection
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserves_in_above_serial_id_by_account)(
+    void *cls,
+    const char *account_name,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_ReserveInCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Select withdraw operations from reserves_out above @a serial_id
+   * in monotonically increasing order.
+   *
+   * @param cls closure
+   * @param account_name name of the account for which we do the selection
+   * @param serial_id highest serial ID to exclude (select strictly larger)
+   * @param cb function to call on each result
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_withdrawals_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_WithdrawCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Function called to select outgoing wire transfers the exchange
+   * executed, ordered by serial ID (monotonically increasing).
+   *
+   * @param cls closure
+   * @param serial_id lowest serial ID to include (select larger or equal)
+   * @param cb function to call for ONE unfinished item
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_wire_out_above_serial_id)(void *cls,
+                                     uint64_t serial_id,
+                                     TALER_EXCHANGEDB_WireTransferOutCallback 
cb,
+                                     void *cb_cls);
+
+  /**
+   * Function called to select outgoing wire transfers the exchange
+   * executed, ordered by serial ID (monotonically increasing).
+   *
+   * @param cls closure
+   * @param account_name name to select by
+   * @param serial_id lowest serial ID to include (select larger or equal)
+   * @param cb function to call for ONE unfinished item
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_wire_out_above_serial_id_by_account)(
+    void *cls,
+    const char *account_name,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_WireTransferOutCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Function called to select recoup requests the exchange
+   * received, ordered by serial ID (monotonically increasing).
+   *
+   * @param cls closure
+   * @param serial_id lowest serial ID to include (select larger or equal)
+   * @param cb function to call for ONE unfinished item
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_recoup_above_serial_id)(void *cls,
+                                   uint64_t serial_id,
+                                   TALER_EXCHANGEDB_RecoupCallback cb,
+                                   void *cb_cls);
+
+
+  /**
+   * Function called to select recoup requests the exchange received for
+   * refreshed coins, ordered by serial ID (monotonically increasing).
+   *
+   * @param cls closure
+   * @param serial_id lowest serial ID to include (select larger or equal)
+   * @param cb function to call for ONE unfinished item
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_recoup_refresh_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_RecoupRefreshCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Function called to select reserve open operations, ordered by serial ID
+   * (monotonically increasing).
+   *
+   * @param cls closure
+   * @param serial_id lowest serial ID to include (select larger or equal)
+   * @param cb function to call
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserve_open_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_ReserveOpenCallback cb,
+    void *cb_cls);
+
+
+  /**
+ * Function called to select reserve close operations the aggregator
+ * triggered, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+  enum GNUNET_DB_QueryStatus
+  (*select_reserve_closed_above_serial_id)(
+    void *cls,
+    uint64_t serial_id,
+    TALER_EXCHANGEDB_ReserveClosedCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Obtain information about which reserve a coin was generated
+   * from given the hash of the blinded coin.
+   *
+   * @param cls closure
+   * @param bch hash identifying the withdraw operation
+   * @param[out] reserve_pub set to information about the reserve (on success 
only)
+   * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in 
reserves_out
+   * @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);
+
+
+  /**
+   * Obtain information about which old coin a coin was refreshed
+   * given the hash of the blinded (fresh) coin.
+   *
+   * @param cls closure
+   * @param h_blind_ev hash of the blinded coin
+   * @param[out] old_coin_pub set to information about the old coin (on 
success only)
+   * @param[out] rrc_serial set to the row of the @a h_blind_ev in the 
refresh_revealed_coins table
+   * @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);
+
+
+  /**
+   * Store information that a denomination key was revoked
+   * in the database.
+   *
+   * @param cls closure
+   * @param denom_pub_hash hash of the revoked denomination key
+   * @param master_sig signature affirming the revocation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_denomination_revocation)(
+    void *cls,
+    const struct TALER_DenominationHashP *denom_pub_hash,
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Obtain information about a denomination key's revocation from
+   * the database.
+   *
+   * @param cls closure
+   * @param denom_pub_hash hash of the revoked denomination key
+   * @param[out] master_sig signature affirming the revocation
+   * @param[out] rowid row where the information is stored
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_denomination_revocation)(
+    void *cls,
+    const struct TALER_DenominationHashP *denom_pub_hash,
+    struct TALER_MasterSignatureP *master_sig,
+    uint64_t *rowid);
+
+
+  /**
+   * Select all of those deposits in the database for which we do
+   * not have a wire transfer (or a refund) and which should have
+   * been deposited between @a start_date and @a end_date.
+   *
+   * @param cls closure
+   * @param start_date lower bound on the requested wire execution date
+   * @param end_date upper bound on the requested wire execution date
+   * @param cb function to call on all such deposits
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*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);
+
+
+  /**
+   * Check the last date an auditor was modified.
+   *
+   * @param cls closure
+   * @param auditor_pub key to look up information for
+   * @param[out] last_date last modification date to auditor status
+   * @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 current state of an auditor.
+   *
+   * @param cls closure
+   * @param auditor_pub key to look up information for
+   * @param[out] auditor_url set to the base URL of the auditor's REST API; 
memory to be
+   *            released by the caller!
+   * @param[out] enabled set if the auditor is currently in use
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_auditor_status)(
+    void *cls,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    char **auditor_url,
+    bool *enabled);
+
+
+  /**
+   * Insert information about an auditor that will audit this exchange.
+   *
+   * @param cls closure
+   * @param auditor_pub key of the auditor
+   * @param auditor_url base URL of the auditor's REST service
+   * @param auditor_name name of the auditor (for humans)
+   * @param start_date date when the auditor was added by the offline system
+   *                      (only to be used for replay detection)
+   * @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);
+
+
+  /**
+   * Update information about an auditor that will audit this exchange.
+   *
+   * @param cls closure
+   * @param auditor_pub key of the auditor (primary key for the existing 
record)
+   * @param auditor_url base URL of the auditor's REST service, to be updated
+   * @param auditor_name name of the auditor (for humans)
+   * @param change_date date when the auditor status was last changed
+   *                      (only to be used for replay detection)
+   * @param enabled true to enable, false to disable
+   * @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);
+
+
+  /**
+   * Check the last date an exchange wire account was modified.
+   *
+   * @param cls closure
+   * @param payto_uri key to look up information for
+   * @param[out] last_date last modification date to auditor status
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_wire_timestamp)(void *cls,
+                           const char *payto_uri,
+                           struct GNUNET_TIME_Timestamp *last_date);
+
+
+  /**
+   * Insert information about an wire account used by this exchange.
+   *
+   * @param cls closure
+   * @param payto_uri wire account of the exchange
+   * @param conversion_url URL of a conversion service, NULL if there is no 
conversion
+   * @param debit_restrictions JSON array with debit restrictions on the 
account
+   * @param credit_restrictions JSON array with credit restrictions on the 
account
+   * @param start_date date when the account was added by the offline system
+   *                      (only to be used for replay detection)
+   * @param master_sig public signature affirming the existence of the account,
+   *         must be of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_wire)(void *cls,
+                 const char *payto_uri,
+                 const char *conversion_url,
+                 const json_t *debit_restrictions,
+                 const json_t *credit_restrictions,
+                 struct GNUNET_TIME_Timestamp start_date,
+                 const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Update information about a wire account of the exchange.
+   *
+   * @param cls closure
+   * @param payto_uri account the update is about
+   * @param conversion_url URL of a conversion service, NULL if there is no 
conversion
+   * @param debit_restrictions JSON array with debit restrictions on the 
account; NULL allowed if not @a enabled
+   * @param credit_restrictions JSON array with credit restrictions on the 
account; NULL allowed if not @a enabled
+   * @param change_date date when the account status was last changed
+   *                      (only to be used for replay detection)
+   * @param enabled true to enable, false to disable (the actual change)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_wire)(void *cls,
+                 const char *payto_uri,
+                 const char *conversion_url,
+                 const json_t *debit_restrictions,
+                 const json_t *credit_restrictions,
+                 struct GNUNET_TIME_Timestamp change_date,
+                 bool enabled);
+
+
+  /**
+   * Obtain information about the enabled wire accounts of the exchange.
+   *
+   * @param cls closure
+   * @param cb function to call on each account
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_wire_accounts)(void *cls,
+                       TALER_EXCHANGEDB_WireAccountCallback cb,
+                       void *cb_cls);
+
+
+  /**
+   * Obtain information about the fee structure of the exchange for
+   * a given @a wire_method
+   *
+   * @param cls closure
+   * @param wire_method which wire method to obtain fees for
+   * @param cb function to call on each account
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_wire_fees)(void *cls,
+                   const char *wire_method,
+                   TALER_EXCHANGEDB_WireFeeCallback cb,
+                   void *cb_cls);
+
+
+  /**
+   * Obtain information about the global fee structure of the exchange.
+   *
+   * @param cls closure
+   * @param cb function to call on each fee entry
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_global_fees)(void *cls,
+                     TALER_EXCHANGEDB_GlobalFeeCallback cb,
+                     void *cb_cls);
+
+
+  /**
+   * Store information about a revoked online signing key.
+   *
+   * @param cls closure
+   * @param exchange_pub exchange online signing key that was revoked
+   * @param master_sig signature affirming the revocation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_signkey_revocation)(
+    void *cls,
+    const struct TALER_ExchangePublicKeyP *exchange_pub,
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Obtain information about a revoked online signing key.
+   *
+   * @param cls closure
+   * @param exchange_pub exchange online signing key that was revoked
+   * @param[out] master_sig signature affirming the revocation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_signkey_revocation)(
+    void *cls,
+    const struct TALER_ExchangePublicKeyP *exchange_pub,
+    struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Lookup information about current denomination key.
+   *
+   * @param cls closure
+   * @param h_denom_pub hash of the denomination public key
+   * @param[out] meta set to various meta data about the key
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_denomination_key)(
+    void *cls,
+    const struct TALER_DenominationHashP *h_denom_pub,
+    struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+
+  /**
+   * Add denomination key.
+   *
+   * @param cls closure
+   * @param h_denom_pub hash of the denomination public key
+   * @param denom_pub the denomination public key
+   * @param meta meta data about the denomination
+   * @param master_sig master signature to add
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*add_denomination_key)(
+    void *cls,
+    const struct TALER_DenominationHashP *h_denom_pub,
+    const struct TALER_DenominationPublicKey *denom_pub,
+    const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Activate future signing key, turning it into a "current" or "valid"
+   * denomination key by adding the master signature.
+   *
+   * @param cls closure
+   * @param exchange_pub the exchange online signing public key
+   * @param meta meta data about @a exchange_pub
+   * @param master_sig master signature to add
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*activate_signing_key)(
+    void *cls,
+    const struct TALER_ExchangePublicKeyP *exchange_pub,
+    const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Lookup signing key meta data.
+   *
+   * @param cls closure
+   * @param exchange_pub the exchange online signing public key
+   * @param[out] meta meta data about @a exchange_pub
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_signing_key)(
+    void *cls,
+    const struct TALER_ExchangePublicKeyP *exchange_pub,
+    struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+
+
+  /**
+   * Insert information about an auditor auditing a denomination key.
+   *
+   * @param cls closure
+   * @param h_denom_pub the audited denomination
+   * @param auditor_pub the auditor's key
+   * @param auditor_sig signature affirming the auditor's audit activity
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_auditor_denom_sig)(
+    void *cls,
+    const struct TALER_DenominationHashP *h_denom_pub,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    const struct TALER_AuditorSignatureP *auditor_sig);
+
+
+  /**
+   * Obtain information about an auditor auditing a denomination key.
+   *
+   * @param cls closure
+   * @param h_denom_pub the audited denomination
+   * @param auditor_pub the auditor's key
+   * @param[out] auditor_sig set to signature affirming the auditor's audit 
activity
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_auditor_denom_sig)(
+    void *cls,
+    const struct TALER_DenominationHashP *h_denom_pub,
+    const struct TALER_AuditorPublicKeyP *auditor_pub,
+    struct TALER_AuditorSignatureP *auditor_sig);
+
+
+  /**
+   * Lookup information about known wire fees.
+   *
+   * @param cls closure
+   * @param wire_method the wire method to lookup fees for
+   * @param start_time starting time of fee
+   * @param end_time end time of fee
+   * @param[out] fees set to wire fees for that time period; if
+   *             different wire fee exists within this time
+   *             period, an 'invalid' amount is returned.
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_wire_fee_by_time)(
+    void *cls,
+    const char *wire_method,
+    struct GNUNET_TIME_Timestamp start_time,
+    struct GNUNET_TIME_Timestamp end_time,
+    struct TALER_WireFeeSet *fees);
+
+
+  /**
+   * Lookup information about known global fees.
+   *
+   * @param cls closure
+   * @param start_time starting time of fee
+   * @param end_time end time of fee
+   * @param[out] fees set to wire fees for that time period; if
+   *             different global fee exists within this time
+   *             period, an 'invalid' amount is returned.
+   * @param[out] purse_timeout set to when unmerged purses expire
+   * @param[out] history_expiration set to when we expire reserve histories
+   * @param[out] purse_account_limit set to number of free purses
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_global_fee_by_time)(
+    void *cls,
+    struct GNUNET_TIME_Timestamp start_time,
+    struct GNUNET_TIME_Timestamp end_time,
+    struct TALER_GlobalFeeSet *fees,
+    struct GNUNET_TIME_Relative *purse_timeout,
+    struct GNUNET_TIME_Relative *history_expiration,
+    uint32_t *purse_account_limit);
+
+
+  /**
+   * Lookup the latest serial number of @a table.  Used in
+   * exchange-auditor database replication.
+   *
+   * @param cls closure
+   * @param table table for which we should return the serial
+   * @param[out] latest serial number in use
+   * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+   *         @a table does not have a serial number
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_serial_by_table)(void *cls,
+                            enum TALER_EXCHANGEDB_ReplicatedTable table,
+                            uint64_t *serial);
+
+  /**
+   * Lookup records above @a serial number in @a table. Used in
+   * exchange-auditor database replication.
+   *
+   * @param cls closure
+   * @param table table for which we should return the serial
+   * @param serial largest serial number to exclude
+   * @param cb function to call on the records
+   * @param cb_cls closure for @a cb
+   * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+   *         @a table does not have a serial number
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_records_by_table)(void *cls,
+                             enum TALER_EXCHANGEDB_ReplicatedTable table,
+                             uint64_t serial,
+                             TALER_EXCHANGEDB_ReplicationCallback cb,
+                             void *cb_cls);
+
+
+  /**
+   * Insert record set into @a table.  Used in exchange-auditor database
+   * replication.
+   *
+  memset (&awc, 0, sizeof (awc));
+   * @param cls closure
+   * @param tb table data to insert
+   * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+   *         @a table does not have a serial number
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_records_by_table)(void *cls,
+                             const struct TALER_EXCHANGEDB_TableData *td);
+
+
+  /**
+   * Function called to grab a work shard on an operation @a op. Runs in its
+   * own transaction.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param job_name name of the operation to grab a word shard for
+   * @param delay minimum age of a shard to grab
+   * @param size desired shard size
+   * @param[out] start_row inclusive start row of the shard (returned)
+   * @param[out] end_row exclusive end row of the shard (returned)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*begin_shard)(void *cls,
+                 const char *job_name,
+                 struct GNUNET_TIME_Relative delay,
+                 uint64_t shard_size,
+                 uint64_t *start_row,
+                 uint64_t *end_row);
+
+  /**
+   * Function called to abort work on a shard.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param job_name name of the operation to abort a word shard for
+   * @param start_row inclusive start row of the shard
+   * @param end_row exclusive end row of the shard
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*abort_shard)(void *cls,
+                 const char *job_name,
+                 uint64_t start_row,
+                 uint64_t end_row);
+
+  /**
+   * Function called to persist that work on a shard was completed.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param job_name name of the operation to grab a word shard for
+   * @param start_row inclusive start row of the shard
+   * @param end_row exclusive end row of the shard
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*complete_shard)(void *cls,
+                    const char *job_name,
+                    uint64_t start_row,
+                    uint64_t end_row);
+
+
+  /**
+   * Function called to grab a revolving work shard on an operation @a op. Runs
+   * in its own transaction. Returns the oldest inactive shard.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param job_name name of the operation to grab a revolving shard for
+   * @param shard_size desired shard size
+   * @param shard_limit exclusive end of the shard range
+   * @param[out] start_row inclusive start row of the shard (returned)
+   * @param[out] end_row inclusive end row of the shard (returned)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*begin_revolving_shard)(void *cls,
+                           const char *job_name,
+                           uint32_t shard_size,
+                           uint32_t shard_limit,
+                           uint32_t *start_row,
+                           uint32_t *end_row);
+
+
+  /**
+   * Function called to release a revolving shard back into the work pool.
+   * Clears the "completed" flag.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param job_name name of the operation to grab a word shard for
+   * @param start_row inclusive start row of the shard
+   * @param end_row inclusive end row of the shard
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*release_revolving_shard)(void *cls,
+                             const char *job_name,
+                             uint32_t start_row,
+                             uint32_t end_row);
+
+
+  /**
+   * Function called to delete all revolving shards.
+   * To be used after a crash or when the shard size is
+   * changed.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @return #GNUNET_OK on success
+   *         #GNUNET_SYSERR on failure
+   */
+  enum GNUNET_GenericReturnValue
+  (*delete_shard_locks)(void *cls);
+
+
+  /**
+   * Function called to save the manifest of an extension
+   * (age-restriction, policy-extension, ...)
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param extension_name the name of the extension
+   * @param manifest JSON object of the Manifest as string, maybe NULL (== 
disabled extension)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*set_extension_manifest)(void *cls,
+                            const char *extension_name,
+                            const char *manifest);
+
+
+  /**
+   * Function called to retrieve the manifest of an extension
+   * (age-restriction, policy-extension, ...)
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param extension_name the name of the extension
+   * @param[out] manifest Manifest of the extension in JSON encoding, maybe 
NULL (== disabled extension)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_extension_manifest)(void *cls,
+                            const char *extension_name,
+                            char **manifest);
+
+
+  /**
+   * Function called to store configuration data about a partner
+   * exchange that we are federated with.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param master_pub public offline signing key of the partner exchange
+   * @param start_date when does the following data start to be valid
+   * @param end_date when does the validity end (exclusive)
+   * @param wad_frequency how often do we do exchange-to-exchange settlements?
+   * @param wad_fee how much do we charge for transfers to the partner
+   * @param partner_base_url base URL of the partner exchange
+   * @param master_sig signature with our offline signing key affirming the 
above
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_partner)(void *cls,
+                    const struct TALER_MasterPublicKeyP *master_pub,
+                    struct GNUNET_TIME_Timestamp start_date,
+                    struct GNUNET_TIME_Timestamp end_date,
+                    struct GNUNET_TIME_Relative wad_frequency,
+                    const struct TALER_Amount *wad_fee,
+                    const char *partner_base_url,
+                    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Function called to persist an encrypted contract associated with a 
reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param econtract the encrypted contract
+   * @param[out] econtract_sig set to the signature over the encrypted contract
+   * @param[out] in_conflict set to true if @a econtract
+   *             conflicts with an existing contract;
+   *             in this case, the return value will be
+   *             #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_contract)(void *cls,
+                     const struct TALER_PurseContractPublicKeyP *purse_pub,
+                     const struct TALER_EncryptedContract *econtract,
+                     bool *in_conflict);
+
+
+  /**
+   * Function called to retrieve an encrypted contract.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param pub_ckey set to the ephemeral DH used to encrypt the contract, key 
used to lookup the contract by
+   * @param[out] purse_pub public key of the purse of the contract
+   * @param[out] econtract_sig set to the signature over the encrypted contract
+   * @param[out] econtract_size set to the number of bytes in @a econtract
+   * @param[out] econtract set to the encrypted contract on success, to be 
freed by the caller
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_contract)(
+    void *cls,
+    const struct TALER_ContractDiffiePublicP *pub_ckey,
+    struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct TALER_PurseContractSignatureP *econtract_sig,
+    size_t *econtract_size,
+    void **econtract);
+
+
+  /**
+   * Function called to retrieve an encrypted contract.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub key to lookup the contract by
+   * @param[out] econtract set to the encrypted contract on success, to be 
freed by the caller
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_contract_by_purse)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct TALER_EncryptedContract *econtract);
+
+
+  /**
+   * Function called to create a new purse with certain meta data.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub public key of the new purse
+   * @param merge_pub public key providing the merge capability
+   * @param purse_expiration time when the purse will expire
+   * @param h_contract_terms hash of the contract for the purse
+   * @param age_limit age limit to enforce for payments into the purse
+   * @param flags flags for the operation
+   * @param purse_fee fee we are allowed to charge to the reserve (depending 
on @a flags)
+   * @param amount target amount (with fees) to be put into the purse
+   * @param purse_sig signature with @a purse_pub's private key affirming the 
above
+   * @param[out] in_conflict set to true if the meta data
+   *             conflicts with an existing purse;
+   *             in this case, the return value will be
+   *             #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_purse_request)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_PurseMergePublicKeyP *merge_pub,
+    struct GNUNET_TIME_Timestamp purse_expiration,
+    const struct TALER_PrivateContractHashP *h_contract_terms,
+    uint32_t age_limit,
+    enum TALER_WalletAccountMergeFlags flags,
+    const struct TALER_Amount *purse_fee,
+    const struct TALER_Amount *amount,
+    const struct TALER_PurseContractSignatureP *purse_sig,
+    bool *in_conflict);
+
+
+  /**
+   * Function called to clean up one expired purse.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param start_time select purse expired after this time
+   * @param end_time select purse expired before this time
+   * @return transaction status code (#GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if 
no purse expired in the given time interval).
+   */
+  enum GNUNET_DB_QueryStatus
+  (*expire_purse)(
+    void *cls,
+    struct GNUNET_TIME_Absolute start_time,
+    struct GNUNET_TIME_Absolute end_time);
+
+
+  /**
+   * Function called to obtain information about a purse.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub public key of the new purse
+   * @param[out] purse_creation set to time when the purse was created
+   * @param[out] purse_expiration set to time when the purse will expire
+   * @param[out] amount set to target amount (with fees) to be put into the 
purse
+   * @param[out] deposited set to actual amount put into the purse so far
+   * @param[out] h_contract_terms set to hash of the contract for the purse
+   * @param[out] merge_timestamp set to time when the purse was merged, or 
NEVER if not
+   * @param[out] purse_deleted set to true if purse was deleted
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct GNUNET_TIME_Timestamp *purse_creation,
+    struct GNUNET_TIME_Timestamp *purse_expiration,
+    struct TALER_Amount *amount,
+    struct TALER_Amount *deposited,
+    struct TALER_PrivateContractHashP *h_contract_terms,
+    struct GNUNET_TIME_Timestamp *merge_timestamp,
+    bool *purse_deleted);
+
+
+  /**
+   * Function called to return meta data about a purse by the
+   * purse public key.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub public key of the purse
+   * @param[out] merge_pub public key representing the merge capability
+   * @param[out] purse_expiration when would an unmerged purse expire
+   * @param[out] h_contract_terms contract associated with the purse
+   * @param[out] age_limit the age limit for deposits into the purse
+   * @param[out] target_amount amount to be put into the purse
+   * @param[out] balance amount put so far into the purse
+   * @param[out] purse_sig signature of the purse over the initialization data
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_purse_request)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct TALER_PurseMergePublicKeyP *merge_pub,
+    struct GNUNET_TIME_Timestamp *purse_expiration,
+    struct TALER_PrivateContractHashP *h_contract_terms,
+    uint32_t *age_limit,
+    struct TALER_Amount *target_amount,
+    struct TALER_Amount *balance,
+    struct TALER_PurseContractSignatureP *purse_sig);
+
+
+  /**
+   * Function called to return meta data about a purse by the
+   * merge capability key.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param merge_pub public key representing the merge capability
+   * @param[out] purse_pub public key of the purse
+   * @param[out] purse_expiration when would an unmerged purse expire
+   * @param[out] h_contract_terms contract associated with the purse
+   * @param[out] age_limit the age limit for deposits into the purse
+   * @param[out] target_amount amount to be put into the purse
+   * @param[out] balance amount put so far into the purse
+   * @param[out] purse_sig signature of the purse over the initialization data
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_by_merge_pub)(
+    void *cls,
+    const struct TALER_PurseMergePublicKeyP *merge_pub,
+    struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct GNUNET_TIME_Timestamp *purse_expiration,
+    struct TALER_PrivateContractHashP *h_contract_terms,
+    uint32_t *age_limit,
+    struct TALER_Amount *target_amount,
+    struct TALER_Amount *balance,
+    struct TALER_PurseContractSignatureP *purse_sig);
+
+
+  /**
+   * Function called to execute a transaction crediting
+   * a purse with @a amount from @a coin_pub. Reduces the
+   * value of @a coin_pub and increase the balance of
+   * the @a purse_pub purse. If the balance reaches the
+   * target amount and the purse has been merged, triggers
+   * the updates of the reserve/account balance.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub purse to credit
+   * @param coin_pub coin to deposit (debit)
+   * @param amount fraction of the coin's value to deposit
+   * @param coin_sig signature affirming the operation
+   * @param amount_minus_fee amount to add to the purse
+   * @param[out] balance_ok set to false if the coin's
+   *        remaining balance is below @a amount;
+   *             in this case, the return value will be
+   *             #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+   * @param[out] too_late it is too late to deposit into this purse
+   * @param[out] conflict the same coin was deposited into
+   *        this purse with a different amount already
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_purse_deposit)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    const struct TALER_Amount *amount,
+    const struct TALER_CoinSpendSignatureP *coin_sig,
+    const struct TALER_Amount *amount_minus_fee,
+    bool *balance_ok,
+    bool *too_late,
+    bool *conflict);
+
+
+  /**
+   * Function called to explicitly delete a purse.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub purse to delete
+   * @param purse_sig signature affirming the deletion
+   * @param[out] decided set to true if the purse was
+   *        already decided and thus could not be deleted
+   * @param[out] found set to true if the purse was found
+   *        (if false, purse could not be deleted)
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_purse_delete)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_PurseContractSignatureP *purse_sig,
+    bool *decided,
+    bool *found);
+
+
+  /**
+   * Set the current @a balance in the purse
+   * identified by @a purse_pub. Used by the auditor
+   * to update the balance as calculated by the auditor.
+   *
+   * @param cls closure
+   * @param purse_pub public key of a purse
+   * @param balance new balance to store under the purse
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*set_purse_balance)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_Amount *balance);
+
+
+  /**
+   * Function called to obtain a coin deposit data from
+   * depositing the coin into a purse.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub purse to credit
+   * @param coin_pub coin to deposit (debit)
+   * @param[out] amount set fraction of the coin's value that was deposited 
(with fee)
+   * @param[out] h_denom_pub set to hash of denomination of the coin
+   * @param[out] phac set to hash of age restriction on the coin
+   * @param[out] coin_sig set to signature affirming the operation
+   * @param[out] partner_url set to the URL of the partner exchange, or NULL 
for ourselves, must be freed by caller
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_purse_deposit)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+    struct TALER_Amount *amount,
+    struct TALER_DenominationHashP *h_denom_pub,
+    struct TALER_AgeCommitmentHash *phac,
+    struct TALER_CoinSpendSignatureP *coin_sig,
+    char **partner_url);
+
+
+  /**
+   * Function called to approve merging a purse into a
+   * reserve by the respective purse merge key. The purse
+   * must not have been merged into a different reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub purse to merge
+   * @param merge_sig signature affirming the merge
+   * @param merge_timestamp time of the merge
+   * @param reserve_sig signature of the reserve affirming the merge
+   * @param partner_url URL of the partner exchange, can be NULL if the 
reserves lives with us
+   * @param reserve_pub public key of the reserve to credit
+   * @param[out] no_partner set to true if @a partner_url is unknown
+   * @param[out] no_balance set to true if the @a purse_pub is not paid up yet
+   * @param[out] no_reserve set to true if the @a reserve_pub is not known
+   * @param[out] in_conflict set to true if @a purse_pub was merged into a 
different reserve already
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_purse_merge)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_PurseMergeSignatureP *merge_sig,
+    const struct GNUNET_TIME_Timestamp merge_timestamp,
+    const struct TALER_ReserveSignatureP *reserve_sig,
+    const char *partner_url,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    bool *no_partner,
+    bool *no_balance,
+    bool *in_conflict);
+
+
+  /**
+   * Function called insert request to merge a purse into a reserve by the
+   * respective purse merge key. The purse must not have been merged into a
+   * different reserve.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub purse to merge
+   * @param merge_sig signature affirming the merge
+   * @param merge_timestamp time of the merge
+   * @param reserve_sig signature of the reserve affirming the merge
+   * @param purse_fee amount to charge the reserve for the purse creation, 
NULL to use the quota
+   * @param reserve_pub public key of the reserve to credit
+   * @param[out] in_conflict set to true if @a purse_pub was merged into a 
different reserve already
+   * @param[out] no_reserve set to true if @a reserve_pub is not a known 
reserve
+   * @param[out] insufficient_funds set to true if @a reserve_pub has 
insufficient capacity to create another purse
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*do_reserve_purse)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    const struct TALER_PurseMergeSignatureP *merge_sig,
+    const struct GNUNET_TIME_Timestamp merge_timestamp,
+    const struct TALER_ReserveSignatureP *reserve_sig,
+    const struct TALER_Amount *purse_fee,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    bool *in_conflict,
+    bool *no_reserve,
+    bool *insufficient_funds);
+
+
+  /**
+   * Function called to approve merging of a purse with
+   * an account, made by the receiving account.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param purse_pub public key of the purse
+   * @param[out] merge_sig set to the signature confirming the merge
+   * @param[out] merge_timestamp set to the time of the merge
+   * @param[out] partner_url set to the URL of the target exchange, or NULL if 
the target exchange is us. To be freed by the caller.
+   * @param[out] reserve_pub set to the public key of the reserve/account 
being credited
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_purse_merge)(
+    void *cls,
+    const struct TALER_PurseContractPublicKeyP *purse_pub,
+    struct TALER_PurseMergeSignatureP *merge_sig,
+    struct GNUNET_TIME_Timestamp *merge_timestamp,
+    char **partner_url,
+    struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+  /**
+   * Function called to persist a signature that
+   * prove that the client requested an
+   * account history.  Debits the @a history_fee from
+   * the reserve (if possible).
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub account that the history was requested for
+   * @param reserve_sig signature affirming the request
+   * @param request_timestamp when was the request made
+   * @param history_fee how much should the @a reserve_pub be charged for the 
request
+   * @param[out] balance_ok set to TRUE if the reserve balance
+   *         was sufficient
+   * @param[out] idempotent set to TRUE if the request is already in the DB
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_history_request)(
+    void *cls,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    const struct TALER_ReserveSignatureP *reserve_sig,
+    struct GNUNET_TIME_Timestamp request_timestamp,
+    const struct TALER_Amount *history_fee,
+    bool *balance_ok,
+    bool *idempotent);
+
+
+  /**
+   * Function called to initiate closure of an account.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param reserve_pub public key of the account to close
+   * @param payto_uri where to wire the funds
+   * @param reserve_sig signature affiming that the account is to be closed
+   * @param request_timestamp timestamp of the close request
+   * @param balance balance at the time of closing
+   * @param closing_fee closing fee to charge
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_close_request)(void *cls,
+                          const struct TALER_ReservePublicKeyP *reserve_pub,
+                          const char *payto_uri,
+                          const struct TALER_ReserveSignatureP *reserve_sig,
+                          struct GNUNET_TIME_Timestamp request_timestamp,
+                          const struct TALER_Amount *balance,
+                          const struct TALER_Amount *closing_fee);
+
+
+  /**
+   * Function called to persist a request to drain profits.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param wtid wire transfer ID to use
+   * @param account_section account to drain
+   * @param payto_uri account to wire funds to
+   * @param request_timestamp time of the signature
+   * @param amount amount to wire
+   * @param master_sig signature affirming the operation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_drain_profit)(void *cls,
+                         const struct TALER_WireTransferIdentifierRawP *wtid,
+                         const char *account_section,
+                         const char *payto_uri,
+                         struct GNUNET_TIME_Timestamp request_timestamp,
+                         const struct TALER_Amount *amount,
+                         const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Function called to get information about a profit drain event.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param wtid wire transfer ID to look up drain event for
+   * @param[out] serial set to serial ID of the entry
+   * @param[out] account_section set to account to drain
+   * @param[out] payto_uri set to account to wire funds to
+   * @param[out] request_timestamp set to time of the signature
+   * @param[out] amount set to amount to wire
+   * @param[out] master_sig set to signature affirming the operation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*get_drain_profit)(void *cls,
+                      const struct TALER_WireTransferIdentifierRawP *wtid,
+                      uint64_t *serial,
+                      char **account_section,
+                      char **payto_uri,
+                      struct GNUNET_TIME_Timestamp *request_timestamp,
+                      struct TALER_Amount *amount,
+                      struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Get profit drain operation ready to execute.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param[out] serial set to serial ID of the entry
+   * @param[out] wtid set set to wire transfer ID to use
+   * @param[out] account_section set to  account to drain
+   * @param[out] payto_uri set to account to wire funds to
+   * @param[out] request_timestamp set to time of the signature
+   * @param[out] amount set to amount to wire
+   * @param[out] master_sig set to signature affirming the operation
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*profit_drains_get_pending)(
+    void *cls,
+    uint64_t *serial,
+    struct TALER_WireTransferIdentifierRawP *wtid,
+    char **account_section,
+    char **payto_uri,
+    struct GNUNET_TIME_Timestamp *request_timestamp,
+    struct TALER_Amount *amount,
+    struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Set profit drain operation to finished.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param serial serial ID of the entry to mark finished
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*profit_drains_set_finished)(
+    void *cls,
+    uint64_t serial);
+
+
+  /**
+   * Insert KYC requirement for @a h_payto account into table.
+   *
+   * @param cls closure
+   * @param requirements requirements that must be checked
+   * @param h_payto account that must be KYC'ed
+   * @param reserve_pub if account is a reserve, its public key, NULL otherwise
+   * @param[out] requirement_row set to legitimization requirement row for 
this check
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_kyc_requirement_for_account)(
+    void *cls,
+    const char *requirements,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_ReservePublicKeyP *reserve_pub,
+    uint64_t *requirement_row);
+
+
+  /**
+   * Begin KYC requirement process.
+   *
+   * @param cls closure
+   * @param h_payto account that must be KYC'ed
+   * @param provider_section provider that must be checked
+   * @param provider_account_id provider account ID
+   * @param provider_legitimization_id provider legitimization ID
+   * @param[out] process_row row the process is stored under
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_kyc_requirement_process)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const char *provider_section,
+    const char *provider_account_id,
+    const char *provider_legitimization_id,
+    uint64_t *process_row);
+
+
+  /**
+   * Update KYC process with updated provider-linkage and/or
+   * expiration data.
+   *
+   * @param cls closure
+   * @param process_row row to select by
+   * @param provider_section provider that must be checked (technically 
redundant)
+   * @param h_payto account that must be KYC'ed (helps access by shard, 
otherwise also redundant)
+   * @param provider_account_id provider account ID
+   * @param provider_legitimization_id provider legitimization ID
+   * @param expiration how long is this KYC check set to be valid (in the past 
if invalid)
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*update_kyc_process_by_row)(
+    void *cls,
+    uint64_t process_row,
+    const char *provider_section,
+    const struct TALER_PaytoHashP *h_payto,
+    const char *provider_account_id,
+    const char *provider_legitimization_id,
+    struct GNUNET_TIME_Absolute expiration);
+
+
+  /**
+   * Lookup KYC requirement.
+   *
+   * @param cls closure
+   * @param legi_row identifies requirement to look up
+   * @param[out] requirements space-separated list of requirements
+   * @param[out] aml_status set to the AML status of the account
+   * @param[out] h_payto account that must be KYC'ed
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_kyc_requirement_by_row)(
+    void *cls,
+    uint64_t requirement_row,
+    char **requirements,
+    enum TALER_AmlDecisionState *aml_status,
+    struct TALER_PaytoHashP *h_payto);
+
+
+  /**
+   * Lookup KYC process meta data.
+   *
+   * @param cls closure
+   * @param provider_section provider that must be checked
+   * @param h_payto account that must be KYC'ed
+   * @param[out] process_row set to row with the legitimization data
+   * @param[out] expiration how long is this KYC check set to be valid (in the 
past if invalid)
+   * @param[out] provider_account_id provider account ID
+   * @param[out] provider_legitimization_id provider legitimization ID
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_kyc_process_by_account)(
+    void *cls,
+    const char *provider_section,
+    const struct TALER_PaytoHashP *h_payto,
+    uint64_t *process_row,
+    struct GNUNET_TIME_Absolute *expiration,
+    char **provider_account_id,
+    char **provider_legitimization_id);
+
+
+  /**
+   * Lookup an
+   * @a h_payto by @a provider_legitimization_id.
+   *
+   * @param cls closure
+   * @param provider_section
+   * @param provider_legitimization_id legi to look up
+   * @param[out] h_payto where to write the result
+   * @param[out] process_row identifies the legitimization process on our end
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*kyc_provider_account_lookup)(
+    void *cls,
+    const char *provider_section,
+    const char *provider_legitimization_id,
+    struct TALER_PaytoHashP *h_payto,
+    uint64_t *process_row);
+
+
+  /**
+   * Call us on KYC processes satisfied for the given
+   * account.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param spc function to call for each satisfied KYC process
+   * @param spc_cls closure for @a spc
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_satisfied_kyc_processes)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+    void *spc_cls);
+
+
+  /**
+   * Call us on KYC legitimization processes satisfied and not expired for the
+   * given account.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param lpc function to call for each satisfied KYC legitimization process
+   * @param lpc_cls closure for @a lpc
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*iterate_kyc_reference)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+    void *lpc_cls);
+
+
+  /**
+   * Call @a kac on withdrawn amounts after @a time_limit which are relevant
+   * for a KYC trigger for a the (debited) account identified by @a h_payto.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param time_limit oldest transaction that could be relevant
+   * @param kac function to call for each applicable amount, in reverse 
chronological order (or until @a kac aborts by returning anything except 
#GNUNET_OK).
+   * @param kac_cls closure for @a kac
+   * @return transaction status code, @a kac aborting with #GNUNET_NO is not 
an error
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_withdraw_amounts_for_kyc_check)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    struct GNUNET_TIME_Absolute time_limit,
+    TALER_EXCHANGEDB_KycAmountCallback kac,
+    void *kac_cls);
+
+
+  /**
+   * Call @a kac on aggregated amounts after @a time_limit which are relevant 
for a
+   * KYC trigger for a the (credited) account identified by @a h_payto.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param time_limit oldest transaction that could be relevant
+   * @param kac function to call for each applicable amount, in reverse 
chronological order (or until @a kac aborts by returning anything except 
#GNUNET_OK).
+   * @param kac_cls closure for @a kac
+   * @return transaction status code, @a kac aborting with #GNUNET_NO is not 
an error
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_aggregation_amounts_for_kyc_check)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    struct GNUNET_TIME_Absolute time_limit,
+    TALER_EXCHANGEDB_KycAmountCallback kac,
+    void *kac_cls);
+
+
+  /**
+   * Call @a kac on merged reserve amounts after @a time_limit which are 
relevant for a
+   * KYC trigger for a the wallet identified by @a h_payto.
+   *
+   * @param cls the @e cls of this struct with the plugin-specific state
+   * @param h_payto account identifier
+   * @param time_limit oldest transaction that could be relevant
+   * @param kac function to call for each applicable amount, in reverse 
chronological order (or until @a kac aborts by returning anything except 
#GNUNET_OK).
+   * @param kac_cls closure for @a kac
+   * @return transaction status code, @a kac aborting with #GNUNET_NO is not 
an error
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_merge_amounts_for_kyc_check)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    struct GNUNET_TIME_Absolute time_limit,
+    TALER_EXCHANGEDB_KycAmountCallback kac,
+    void *kac_cls);
+
+
+  /**
+   * Store KYC attribute data, update KYC process status and
+   * AML status for the given account.
+   *
+   * @param cls closure
+   * @param process_row KYC process row to update
+   * @param h_payto account for which the attribute data is stored
+   * @param kyc_prox key for similarity search
+   * @param provider_section provider that must be checked
+   * @param provider_account_id provider account ID
+   * @param provider_legitimization_id provider legitimization ID
+   * @param birthday birthdate of user, in days after 1990, or 0 if unknown or 
definitively adult
+   * @param collection_time when was the data collected
+   * @param expiration_time when does the data expire
+   * @param enc_attributes_size number of bytes in @a enc_attributes
+   * @param enc_attributes encrypted attribute data
+   * @param require_aml true to trigger AML
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_kyc_attributes)(
+    void *cls,
+    uint64_t process_row,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct GNUNET_ShortHashCode *kyc_prox,
+    const char *provider_section,
+    uint32_t birthday,
+    struct GNUNET_TIME_Timestamp collection_time,
+    const char *provider_account_id,
+    const char *provider_legitimization_id,
+    struct GNUNET_TIME_Absolute expiration_time,
+    size_t enc_attributes_size,
+    const void *enc_attributes,
+    bool require_aml);
+
+
+  /**
+   * Lookup similar KYC attribute data.
+   *
+   * @param cls closure
+   * @param kyc_prox key for similarity search
+   * @param cb callback to invoke on each match
+   * @param cb_cls closure for @a cb
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_similar_kyc_attributes)(
+    void *cls,
+    const struct GNUNET_ShortHashCode *kyc_prox,
+    TALER_EXCHANGEDB_AttributeCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Lookup KYC attribute data for a specific account.
+   *
+   * @param cls closure
+   * @param h_payto account for which the attribute data is stored
+   * @param cb callback to invoke on each match
+   * @param cb_cls closure for @a cb
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_kyc_attributes)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_AttributeCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Insert AML staff record.
+   *
+   * @param cls closure
+   * @param decider_pub public key of the staff member
+   * @param master_sig offline signature affirming the AML officer
+   * @param decider_name full name of the staff member
+   * @param is_active true to enable, false to set as inactive
+   * @param read_only true to set read-only access
+   * @param last_change when was the change made effective
+   * @param[out] previous_change when was the previous change made
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_aml_officer)(
+    void *cls,
+    const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+    const struct TALER_MasterSignatureP *master_sig,
+    const char *decider_name,
+    bool is_active,
+    bool read_only,
+    struct GNUNET_TIME_Timestamp last_change,
+    struct GNUNET_TIME_Timestamp *previous_change);
+
+
+  /**
+   * Test if the given AML staff member is active
+   * (at least read-only).
+   *
+   * @param cls closure
+   * @param decider_pub public key of the staff member
+   * @return database transaction status, if member is unknown or not active, 
1 if member is active
+   */
+  enum GNUNET_DB_QueryStatus
+  (*test_aml_officer)(
+    void *cls,
+    const struct TALER_AmlOfficerPublicKeyP *decider_pub);
+
+
+  /**
+   * Fetch AML staff record.
+   *
+   * @param cls closure
+   * @param decider_pub public key of the staff member
+   * @param[out] master_sig offline signature affirming the AML officer
+   * @param[out] decider_name full name of the staff member
+   * @param[out] is_active true to enable, false to set as inactive
+   * @param[out] read_only true to set read-only access
+   * @param[out] last_change when was the change made effective
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_aml_officer)(
+    void *cls,
+    const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+    struct TALER_MasterSignatureP *master_sig,
+    char **decider_name,
+    bool *is_active,
+    bool *read_only,
+    struct GNUNET_TIME_Absolute *last_change);
+
+
+  /**
+   * Obtain the current AML threshold set for an account.
+   *
+   * @param cls closure
+   * @param h_payto account for which the AML threshold is stored
+   * @param[out] decision set to current AML decision
+   * @param[out] threshold set to the existing threshold
+   * @return database transaction status, 0 if no threshold was set
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_aml_threshold)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    enum TALER_AmlDecisionState *decision,
+    struct TALER_EXCHANGEDB_KycStatus *kyc,
+    struct TALER_Amount *threshold);
+
+
+  /**
+   * Trigger AML process, an account has crossed the threshold. Inserts or
+   * updates the AML status.
+   *
+   * @param cls closure
+   * @param h_payto account for which the attribute data is stored
+   * @param threshold_crossed existing threshold that was crossed
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*trigger_aml_process)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_Amount *threshold_crossed);
+
+
+  /**
+   * Lookup AML decisions that have a particular state.
+   *
+   * @param cls closure
+   * @param decision which decision states to filter by
+   * @param row_off offset to start from
+   * @param forward true to go forward in time, false to go backwards
+   * @param cb callback to invoke on each match
+   * @param cb_cls closure for @a cb
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_aml_process)(
+    void *cls,
+    enum TALER_AmlDecisionState decision,
+    uint64_t row_off,
+    uint64_t limit,
+    bool forward,
+    TALER_EXCHANGEDB_AmlStatusCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Lookup AML decision history for a particular account.
+   *
+   * @param cls closure
+   * @param h_payto which account should we return the AML decision history for
+   * @param cb callback to invoke on each match
+   * @param cb_cls closure for @a cb
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_aml_history)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    TALER_EXCHANGEDB_AmlHistoryCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Insert an AML decision. Inserts into AML history and insert or updates AML
+   * status.
+   *
+   * @param cls closure
+   * @param h_payto account for which the attribute data is stored
+   * @param new_threshold new monthly threshold that would trigger an AML check
+   * @param new_status AML decision status
+   * @param decision_time when was the decision made
+   * @param justification human-readable text justifying the decision
+   * @param kyc_requirements specific KYC requiremnts being imposed
+   * @param requirements_row row in the KYC table for this process, 0 for none
+   * @param decider_pub public key of the staff member
+   * @param decider_sig signature of the staff member
+   * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed 
to make decisions right now
+   * @param[out] last_date set to the previous decision time;
+   *   the INSERT is not performed if @a last_date is not before @a 
decision_time
+   * @return database transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_aml_decision)(
+    void *cls,
+    const struct TALER_PaytoHashP *h_payto,
+    const struct TALER_Amount *new_threshold,
+    enum TALER_AmlDecisionState new_status,
+    struct GNUNET_TIME_Timestamp decision_time,
+    const char *justification,
+    const json_t *kyc_requirements,
+    uint64_t requirements_row,
+    const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+    const struct TALER_AmlOfficerSignatureP *decider_sig,
+    bool *invalid_officer,
+    struct GNUNET_TIME_Timestamp *last_date);
+
+
+};
+
+#endif /* _TALER_EXCHANGE_DB_H */
diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h
new file mode 100644
index 0000000..367b54b
--- /dev/null
+++ b/src/include/taler_testing_lib.h
@@ -0,0 +1,2762 @@
+/*
+  This file is part of TALER
+  (C) 2018-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file include/taler_testing_lib.h
+ * @brief API for writing an interpreter to test Taler components
+ * @author Christian Grothoff <christian@grothoff.org>
+ * @author Marcello Stanisci
+ */
+#ifndef TALER_TESTING_LIB_H
+#define TALER_TESTING_LIB_H
+
+#include "taler_util.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_json_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_bank_service.h"
+#include "taler_exchange_service.h"
+#include "taler_fakebank_lib.h"
+
+
+/* ********************* Helper functions ********************* */
+
+/**
+ * Print failing line number and trigger shutdown.  Useful
+ * quite any time after the command "run" method has been called.
+ */
+#define TALER_TESTING_FAIL(is) \
+  do \
+  { \
+    GNUNET_break (0); \
+    TALER_TESTING_interpreter_fail (is); \
+    return; \
+  } while (0)
+
+
+/**
+ * Log an error message about us receiving an unexpected HTTP
+ * status code at the current command and fail the test.
+ *
+ * @param is interpreter to fail
+ * @param status unexpected HTTP status code received
+ * @param expected expected HTTP status code
+ */
+#define TALER_TESTING_unexpected_status(is,status,expected)             \
+  do {                                                                  \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Unexpected response code %u (expected: %u) to command %s in 
%s:%u\n", \
+                status,                                                 \
+                expected,                                               \
+                TALER_TESTING_interpreter_get_current_label (is),       \
+                __FILE__,                                               \
+                __LINE__);                                              \
+    TALER_TESTING_interpreter_fail (is);                                \
+  } while (0)
+
+/**
+ * Log an error message about us receiving an unexpected HTTP
+ * status code at the current command and fail the test and print the response
+ * body (expected as json).
+ *
+ * @param is interpreter to fail
+ * @param status unexpected HTTP status code received
+ * @param expected expected HTTP status code
+ * @param body received JSON-reply
+ */
+#define TALER_TESTING_unexpected_status_with_body(is,status,expected,body) \
+  do {                                                                  \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Unexpected response code %u (expected: %u) to "        \
+                "command %s in %s:%u\nwith body:\n>>%s<<\n",            \
+                status,                                                 \
+                expected,                                               \
+                TALER_TESTING_interpreter_get_current_label (is),       \
+                __FILE__,                                               \
+                __LINE__,                                               \
+                json_dumps (body, JSON_INDENT (2)));                    \
+    TALER_TESTING_interpreter_fail (is);                                \
+  } while (0)
+
+
+/**
+ * Log an error message about a command not having
+ * run to completion.
+ *
+ * @param is interpreter
+ * @param label command label of the incomplete command
+ */
+#define TALER_TESTING_command_incomplete(is,label)                      \
+  do {                                                                  \
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                                \
+                "Command %s (%s:%u) did not complete (at %s)\n",        \
+                label,                                                  \
+                __FILE__,                                               \
+                __LINE__,                                               \
+                TALER_TESTING_interpreter_get_current_label (is));      \
+  } while (0)
+
+
+/**
+ * Common credentials used in a test.
+ */
+struct TALER_TESTING_Credentials
+{
+  /**
+   * Bank authentication details for the exchange bank
+   * account.
+   */
+  struct TALER_BANK_AuthenticationData ba;
+
+  /**
+   * Configuration file data.
+   */
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+
+  /**
+   * Base URL of the exchange.
+   */
+  char *exchange_url;
+
+  /**
+   * Base URL of the auditor.
+   */
+  char *auditor_url;
+
+  /**
+   * RFC 8905 URI of the exchange.
+   */
+  char *exchange_payto;
+
+  /**
+   * RFC 8905 URI of a user.
+   */
+  char *user42_payto;
+
+  /**
+   * RFC 8905 URI of a user.
+   */
+  char *user43_payto;
+};
+
+
+/**
+ * What type of bank are we using?
+ */
+enum TALER_TESTING_BankSystem
+{
+  TALER_TESTING_BS_FAKEBANK = 1,
+  TALER_TESTING_BS_IBAN = 2
+};
+
+
+/**
+ * Obtain bank credentials for a given @a cfg_file using
+ * @a exchange_account_section as the basis for the
+ * exchange account.
+ *
+ * @param cfg_file name of configuration to parse
+ * @param exchange_account_section configuration section name for the exchange 
account to use
+ * @param bs type of bank to use
+ * @param[out] ua where to write user account details
+ *         and other credentials
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+  const char *cfg_file,
+  const char *exchange_account_section,
+  enum TALER_TESTING_BankSystem bs,
+  struct TALER_TESTING_Credentials *ua);
+
+
+/**
+ * Allocate and return a piece of wire-details.  Combines
+ * a @a payto -URL and adds some salt to create the JSON.
+ *
+ * @param payto payto://-URL to encapsulate
+ * @return JSON describing the account, including the
+ *         payto://-URL of the account, must be manually decref'd
+ */
+json_t *
+TALER_TESTING_make_wire_details (const char *payto);
+
+
+/**
+ * Remove files from previous runs
+ *
+ * @param cls NULL
+ * @param cfg configuration
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_cleanup_files_cfg (void *cls,
+                                 const struct GNUNET_CONFIGURATION_Handle 
*cfg);
+
+
+/**
+ * Find denomination key matching the given amount.
+ *
+ * @param keys array of keys to search
+ * @param amount coin value to look for
+ * @param age_restricted must the denomination be age restricted?
+ * @return NULL if no matching key was found
+ */
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (const struct TALER_EXCHANGE_Keys *keys,
+                       const struct TALER_Amount *amount,
+                       bool age_restricted);
+
+
+/**
+ * Test port in URL string for availability.
+ *
+ * @param url URL to extract port from, 80 is default
+ * @return #GNUNET_OK if the port is free
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_url_port_free (const char *url);
+
+
+/* ******************* Generic interpreter logic ************ */
+
+/**
+ * Global state of the interpreter, used by a command
+ * to access information about other commands.
+ */
+struct TALER_TESTING_Interpreter;
+
+
+/**
+ * A command to be run by the interpreter.
+ */
+struct TALER_TESTING_Command
+{
+
+  /**
+   * Closure for all commands with command-specific context
+   * information.
+   */
+  void *cls;
+
+  /**
+   * Label for the command.
+   */
+  const char *label;
+
+  /**
+   * Variable name for the command, NULL for none.
+   */
+  const char *name;
+
+  /**
+   * Runs the command.  Note that upon return, the interpreter
+   * will not automatically run the next command, as the command
+   * may continue asynchronously in other scheduler tasks.  Thus,
+   * the command must ensure to eventually call
+   * #TALER_TESTING_interpreter_next() or
+   * #TALER_TESTING_interpreter_fail().
+   *
+   * @param cls closure
+   * @param cmd command being run
+   * @param is interpreter state
+   */
+  void
+  (*run)(void *cls,
+         const struct TALER_TESTING_Command *cmd,
+         struct TALER_TESTING_Interpreter *is);
+
+
+  /**
+   * Clean up after the command.  Run during forced termination
+   * (CTRL-C) or test failure or test success.
+   *
+   * @param cls closure
+   * @param cmd command being cleaned up
+   */
+  void
+  (*cleanup)(void *cls,
+             const struct TALER_TESTING_Command *cmd);
+
+  /**
+   * Extract information from a command that is useful for other
+   * commands.
+   *
+   * @param cls closure
+   * @param[out] ret result (could be anything)
+   * @param trait name of the trait
+   * @param index index number of the object to extract.
+   * @return #GNUNET_OK on success
+   */
+  enum GNUNET_GenericReturnValue
+  (*traits)(void *cls,
+            const void **ret,
+            const char *trait,
+            unsigned int index);
+
+  /**
+   * When did the execution of this command start?
+   */
+  struct GNUNET_TIME_Absolute start_time;
+
+  /**
+   * When did the execution of this command finish?
+   */
+  struct GNUNET_TIME_Absolute finish_time;
+
+  /**
+   * When did we start the last request of this command?
+   * Delta to @e finish_time gives the latency for the last
+   * successful request.
+   */
+  struct GNUNET_TIME_Absolute last_req_time;
+
+  /**
+   * How often did we try to execute this command? (In case
+   * it is a request that is repated.)
+   */
+  unsigned int num_tries;
+
+};
+
+
+/**
+ * Lookup command by label.
+ *
+ * @param is interpreter state.
+ * @param label label of the command to lookup.
+ * @return the command, if it is found, or NULL.
+ */
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_lookup_command (struct TALER_TESTING_Interpreter *is,
+                                          const char *label);
+
+
+/**
+ * Get command from hash map by variable name.
+ *
+ * @param is interpreter state.
+ * @param name name of the variable to get command by
+ * @return the command, if it is found, or NULL.
+ */
+const struct TALER_TESTING_Command *
+TALER_TESTING_interpreter_get_command (struct TALER_TESTING_Interpreter *is,
+                                       const char *name);
+
+
+/**
+ * Update the last request time of the current command
+ * to the current time.
+ *
+ * @param[in,out] is interpreter state where to show
+ *       that we are doing something
+ */
+void
+TALER_TESTING_touch_cmd (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Increment the 'num_tries' counter for the current
+ * command.
+ *
+ * @param[in,out] is interpreter state where to
+ *   increment the counter
+ */
+void
+TALER_TESTING_inc_tries (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Obtain CURL context for the main loop.
+ *
+ * @param is interpreter state.
+ * @return CURL execution context.
+ */
+struct GNUNET_CURL_Context *
+TALER_TESTING_interpreter_get_context (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Obtain label of the command being now run.
+ *
+ * @param is interpreter state.
+ * @return the label.
+ */
+const char *
+TALER_TESTING_interpreter_get_current_label (
+  struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Current command is done, run the next one.
+ *
+ * @param is interpreter state.
+ */
+void
+TALER_TESTING_interpreter_next (struct TALER_TESTING_Interpreter *is);
+
+/**
+ * Current command failed, clean up and fail the test case.
+ *
+ * @param is interpreter state.
+ */
+void
+TALER_TESTING_interpreter_fail (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Make the instruction pointer point to @a target_label
+ * only if @a counter is greater than zero.
+ *
+ * @param label command label
+ * @param target_label label of the new instruction pointer's destination 
after the jump;
+ *                     must be before the current instruction
+ * @param counter counts how many times the rewinding is to happen.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_rewind_ip (const char *label,
+                             const char *target_label,
+                             unsigned int counter);
+
+
+/**
+ * Wait until we receive SIGCHLD signal.
+ * Then obtain the process trait of the current
+ * command, wait on the the zombie and continue
+ * with the next command.
+ *
+ * @param is interpreter state.
+ */
+void
+TALER_TESTING_wait_for_sigchld (struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Schedule the first CMD in the CMDs array.
+ *
+ * @param is interpreter state.
+ * @param commands array of all the commands to execute.
+ */
+void
+TALER_TESTING_run (struct TALER_TESTING_Interpreter *is,
+                   struct TALER_TESTING_Command *commands);
+
+
+/**
+ * Run the testsuite.  Note, CMDs are copied into
+ * the interpreter state because they are _usually_
+ * defined into the "run" method that returns after
+ * having scheduled the test interpreter.
+ *
+ * @param is the interpreter state
+ * @param commands the list of command to execute
+ * @param timeout how long to wait
+ */
+void
+TALER_TESTING_run2 (struct TALER_TESTING_Interpreter *is,
+                    struct TALER_TESTING_Command *commands,
+                    struct GNUNET_TIME_Relative timeout);
+
+
+/**
+ * The function that contains the array of all the CMDs to run,
+ * which is then on charge to call some fashion of
+ * TALER_TESTING_run*.  In all the test cases, this function is
+ * always the GNUnet-ish "run" method.
+ *
+ * @param cls closure.
+ * @param is interpreter state.
+ */
+typedef void
+(*TALER_TESTING_Main)(void *cls,
+                      struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Run Taler testing loop.  Starts the GNUnet SCHEDULER (event loop).
+ *
+ * @param main_cb main function to run
+ * @param main_cb_cls closure for @a main_cb
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_loop (TALER_TESTING_Main main_cb,
+                    void *main_cb_cls);
+
+
+/**
+ * Convenience function to run a test.
+ *
+ * @param argv command-line arguments given
+ * @param loglevel log level to use
+ * @param cfg_file configuration file to use
+ * @param exchange_account_section configuration section
+ *   with exchange bank account to use
+ * @param bs bank system to use
+ * @param[in,out] cred global credentials to initialize
+ * @param main_cb main test function to run
+ * @param main_cb_cls closure for @a main_cb
+ * @return 0 on success, 77 on setup trouble, non-zero process status code 
otherwise
+ */
+int
+TALER_TESTING_main (char *const *argv,
+                    const char *loglevel,
+                    const char *cfg_file,
+                    const char *exchange_account_section,
+                    enum TALER_TESTING_BankSystem bs,
+                    struct TALER_TESTING_Credentials *cred,
+                    TALER_TESTING_Main main_cb,
+                    void *main_cb_cls);
+
+
+/**
+ * Callback over commands of an interpreter.
+ *
+ * @param cls closure
+ * @param cmd a command to process
+ */
+typedef void
+(*TALER_TESTING_CommandIterator)(
+  void *cls,
+  const struct TALER_TESTING_Command *cmd);
+
+
+/**
+ * Iterates over all of the top-level commands of an
+ * interpreter.
+ *
+ * @param[in] is interpreter to iterate over
+ * @param asc true in execution order, false for reverse execution order
+ * @param cb function to call on each command
+ * @param cb_cls closure for cb
+ */
+void
+TALER_TESTING_iterate (struct TALER_TESTING_Interpreter *is,
+                       bool asc,
+                       TALER_TESTING_CommandIterator cb,
+                       void *cb_cls);
+
+
+/**
+ * Look for substring in a programs' name.
+ *
+ * @param prog program's name to look into
+ * @param marker chunk to find in @a prog
+ * @return true if @a marker is in @a prog
+ */
+bool
+TALER_TESTING_has_in_name (const char *prog,
+                           const char *marker);
+
+
+/**
+ * Wait for an HTTPD service to have started. Waits for at
+ * most 10s, after that returns 77 to indicate an error.
+ *
+ * @param base_url what URL should we expect the exchange
+ *        to be running at
+ * @return 0 on success
+ */
+int
+TALER_TESTING_wait_httpd_ready (const char *base_url);
+
+
+/**
+ * Parse reference to a coin.
+ *
+ * @param coin_reference of format $LABEL['#' $INDEX]?
+ * @param[out] cref where we return a copy of $LABEL
+ * @param[out] idx where we set $INDEX
+ * @return #GNUNET_SYSERR if $INDEX is present but not numeric
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_parse_coin_reference (
+  const char *coin_reference,
+  char **cref,
+  unsigned int *idx);
+
+
+/**
+ * Compare @a h1 and @a h2.
+ *
+ * @param h1 a history entry
+ * @param h2 a history entry
+ * @return 0 if @a h1 and @a h2 are equal
+ */
+int
+TALER_TESTING_history_entry_cmp (
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *h2);
+
+
+/* ************** Specific interpreter commands ************ */
+
+
+/**
+ * Create command array terminator.
+ *
+ * @return a end-command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_end (void);
+
+
+/**
+ * Set variable to command as side-effect of
+ * running a command.
+ *
+ * @param name name of the variable to set
+ * @param cmd command to set to variable when run
+ * @return modified command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_var (const char *name,
+                           struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Launch GNU Taler setup.
+ *
+ * @param label command label.
+ * @param config_file configuration file to use
+ * @param ... NULL-terminated (const char *) arguments to pass to 
taler-benchmark-setup.sh
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_system_start (
+  const char *label,
+  const char *config_file,
+  ...);
+
+
+/**
+ * Connects to the exchange.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param last_keys_ref reference to command with prior /keys response, NULL 
for none
+ * @param wait_for_keys block until we got /keys
+ * @param load_private_key obtain private key from file indicated in @a cfg
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  const char *last_keys_ref,
+  bool wait_for_keys,
+  bool load_private_key);
+
+
+/**
+ * Connects to the auditor.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param load_auditor_keys obtain auditor keys from file indicated in @a cfg
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_auditor (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  bool load_auditor_keys);
+
+
+/**
+ * Runs the Fakebank in-process by guessing / extracting the portnumber
+ * from the base URL.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param exchange_account_section configuration section
+ *   to use to determine bank account of the exchange
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_run_fakebank (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  const char *exchange_account_section);
+
+
+/**
+ * Command to modify authorization header used in the CURL context.
+ * This will destroy the existing CURL context and create a fresh
+ * one. The command will fail (badly) if the existing CURL context
+ * still has active HTTP requests associated with it.
+ *
+ * @param label command label.
+ * @param auth_token auth token to use henceforth, can be NULL
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_authorization (const char *label,
+                                     const char *auth_token);
+
+
+/**
+ * Make a credit "history" CMD.
+ *
+ * @param label command label.
+ * @param auth login data to use
+ * @param start_row_reference reference to a command that can
+ *        offer a row identifier, to be used as the starting row
+ *        to accept in the result.
+ * @param num_results how many rows we want in the result,
+ *        and ascending/descending call
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_bank_credits (
+  const char *label,
+  const struct TALER_BANK_AuthenticationData *auth,
+  const char *start_row_reference,
+  long long num_results);
+
+
+/**
+ * Make a debit "history" CMD.
+ *
+ * @param label command label.
+ * @param auth authentication data
+ * @param start_row_reference reference to a command that can
+ *        offer a row identifier, to be used as the starting row
+ *        to accept in the result.
+ * @param num_results how many rows we want in the result.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_bank_debits (const char *label,
+                               const struct TALER_BANK_AuthenticationData 
*auth,
+                               const char *start_row_reference,
+                               long long num_results);
+
+
+/**
+ * Create transfer command.
+ *
+ * @param label command label
+ * @param amount amount to transfer
+ * @param auth authentication data to use
+ * @param payto_debit_account which account to withdraw money from
+ * @param payto_credit_account which account receives money
+ * @param wtid wire transfer identifier to use
+ * @param exchange_base_url exchange URL to use
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_transfer (const char *label,
+                            const char *amount,
+                            const struct TALER_BANK_AuthenticationData *auth,
+                            const char *payto_debit_account,
+                            const char *payto_credit_account,
+                            const struct TALER_WireTransferIdentifierRawP 
*wtid,
+                            const char *exchange_base_url);
+
+
+/**
+ * Modify a transfer command to enable retries when the reserve is not yet
+ * full or we get other transient errors from the bank.
+ *
+ * @param cmd a fakebank transfer command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_transfer_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Make the "exec-auditor" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor (const char *label,
+                                const char *config_filename);
+
+
+/**
+ * Make the "exec-auditor-dbinit" CMD. Always run with the "-r" option.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor_dbinit (const char *label,
+                                       const char *config_filename);
+
+
+/**
+ * Create a "deposit-confirmation" command.
+ *
+ * @param label command label.
+ * @param deposit_reference reference to any operation that can
+ *        provide a coin.
+ * @param coin_index if @a deposit_reference offers an array of
+ *        coins, this parameter selects which one in that array.
+ *        This value is currently ignored, as only one-coin
+ *        deposits are implemented.
+ * @param amount_without_fee deposited amount without the fee
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_confirmation (const char *label,
+                                        const char *deposit_reference,
+                                        unsigned int coin_index,
+                                        const char *amount_without_fee,
+                                        unsigned int expected_response_code);
+
+
+/**
+ * Modify a deposit confirmation command to enable retries when we get
+ * transient errors from the auditor.
+ *
+ * @param cmd a deposit confirmation command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_confirmation_with_retry (
+  struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a "list exchanges" command.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exchanges (const char *label,
+                             unsigned int expected_response_code);
+
+
+/**
+ * Create a "list exchanges" command and check whether
+ * a particular exchange belongs to the returned bundle.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP response code.
+ * @param exchange_url URL of the exchange supposed to
+ *  be included in the response.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exchanges_with_url (const char *label,
+                                      unsigned int expected_response_code,
+                                      const char *exchange_url);
+
+/**
+ * Modify an exchanges command to enable retries when we get
+ * transient errors from the auditor.
+ *
+ * @param cmd a deposit confirmation command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exchanges_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create /admin/add-incoming command.
+ *
+ * @param label command label.
+ * @param amount amount to transfer.
+ * @param payto_debit_account which account sends money.
+ * @param auth authentication data
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_admin_add_incoming (
+  const char *label,
+  const char *amount,
+  const struct TALER_BANK_AuthenticationData *auth,
+  const char *payto_debit_account);
+
+
+/**
+ * Create "fakebank transfer" CMD, letting the caller specify
+ * a reference to a command that can offer a reserve private key.
+ * This private key will then be used to construct the subject line
+ * of the wire transfer.
+ *
+ * @param label command label.
+ * @param amount the amount to transfer.
+ * @param payto_debit_account which account sends money.
+ * @param auth authentication data
+ * @param ref reference to a command that can offer a reserve
+ *        private key or public key.
+ * @param http_status expected HTTP status
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_admin_add_incoming_with_ref (
+  const char *label,
+  const char *amount,
+  const struct TALER_BANK_AuthenticationData *auth,
+  const char *payto_debit_account,
+  const char *ref,
+  unsigned int http_status);
+
+
+/**
+ * Modify a fakebank transfer command to enable retries when the
+ * reserve is not yet full or we get other transient errors from
+ * the fakebank.
+ *
+ * @param cmd a fakebank transfer command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_admin_add_incoming_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Make a "wirewatch" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wirewatch (const char *label,
+                                  const char *config_filename);
+
+
+/**
+ * Make a "wirewatch" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param account_section section to run wirewatch against
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wirewatch2 (const char *label,
+                                   const char *config_filename,
+                                   const char *account_section);
+
+
+/**
+ * Request URL via "wget".
+ *
+ * @param label command label.
+ * @param url URL to fetch
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_wget (const char *label,
+                             const char *url);
+
+
+/**
+ * Request fetch-transactions via "wget".
+ *
+ * @param label command label.
+ * @param username username to use
+ * @param password password to use
+ * @param bank_base_url base URL of the nexus
+ * @param account_id account to fetch transactions for
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_nexus_fetch_transactions (const char *label,
+                                            const char *username,
+                                            const char *password,
+                                            const char *bank_base_url,
+                                            const char *account_id);
+
+
+/**
+ * Make a "expire" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_expire (const char *label,
+                               const char *config_filename);
+
+
+/**
+ * Make a "router" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_router (const char *label,
+                               const char *config_filename);
+
+
+/**
+ * Run a "taler-exchange-aggregator" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ *                        aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_aggregator (const char *label,
+                                   const char *config_filename);
+
+
+/**
+ * Run a "taler-auditor-offline" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ *                        aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_auditor_offline (const char *label,
+                                        const char *config_filename);
+
+
+/**
+ * Make a "aggregator" CMD and do not disable KYC checks.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ *                        aggregator to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_aggregator_with_kyc (const char *label,
+                                            const char *config_filename);
+
+
+/**
+ * Make a "closer" CMD.  Note that it is right now not supported to run the
+ * closer to close multiple reserves in combination with a subsequent reserve
+ * status call, as we cannot generate the traits necessary for multiple closed
+ * reserves.  You can work around this by using multiple closer commands, one
+ * per reserve that is being closed.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ *                        closer to use.
+ * @param expected_amount amount we expect to see wired from a @a 
expected_reserve_ref
+ * @param expected_fee closing fee we expect to see
+ * @param expected_reserve_ref reference to a reserve we expect the closer to 
drain;
+ *          NULL if we do not expect the closer to do anything
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_closer (const char *label,
+                               const char *config_filename,
+                               const char *expected_amount,
+                               const char *expected_fee,
+                               const char *expected_reserve_ref);
+
+
+/**
+ * Make a "transfer" CMD.
+ *
+ * @param label command label.
+ * @param config_filename configuration file for the
+ *                        transfer to use.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_transfer (const char *label,
+                                 const char *config_filename);
+
+
+/**
+ * Create a withdraw command, letting the caller specify
+ * the desired amount as string.
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw 
from
+ * @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies
+ * @param expected_response_code which HTTP response code
+ *        we expect from the exchange.
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount (const char *label,
+                                   const char *reserve_reference,
+                                   const char *amount,
+                                   uint8_t age,
+                                   unsigned int expected_response_code);
+
+
+/**
+ * Create a batch withdraw command, letting the caller specify
+ * the desired amounts as string.  Takes a variable, non-empty
+ * list of the denomination amounts via VARARGS, similar to
+ * #TALER_TESTING_cmd_withdraw_amount(), just using a batch withdraw.
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw 
from
+ * @param age if > 0, age restriction applies (same for all coins)
+ * @param expected_response_code which HTTP response code
+ *        we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per 
coin)
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_withdraw (const char *label,
+                                  const char *reserve_reference,
+                                  uint8_t age,
+                                  unsigned int expected_response_code,
+                                  const char *amount,
+                                  ...);
+
+/**
+ * Create an age-withdraw command, letting the caller specify
+ * the maximum agend and desired amounts as string.  Takes a variable,
+ * non-empty list of the denomination amounts via VARARGS, similar to
+ * #TALER_TESTING_cmd_withdraw_amount(), just using a batch withdraw.
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw 
from
+ * @param max_age maximum allowed age, same for each coin
+ * @param expected_response_code which HTTP response code
+ *        we expect from the exchange.
+ * @param amount how much we withdraw for the first coin
+ * @param ... NULL-terminated list of additional amounts to withdraw (one per 
coin)
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_age_withdraw (const char *label,
+                                const char *reserve_reference,
+                                uint8_t max_age,
+                                unsigned int expected_response_code,
+                                const char *amount,
+                                ...);
+
+/**
+ * Create a "age-withdraw reveal" command.
+ *
+ * @param label command label.
+ * @param age_withdraw_reference reference to a "age-withdraw" command.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_age_withdraw_reveal (
+  const char *label,
+  const char *age_withdraw_reference,
+  unsigned int expected_response_code);
+
+/**
+ * Create a withdraw command, letting the caller specify
+ * the desired amount as string and also re-using an existing
+ * coin private key in the process (violating the specification,
+ * which will result in an error when spending the coin!).
+ *
+ * @param label command label.
+ * @param reserve_reference command providing us with a reserve to withdraw 
from
+ * @param amount how much we withdraw.
+ * @param age if > 0, age restriction applies.
+ * @param coin_ref reference to (withdraw/reveal) command of a coin
+ *        from which we should re-use the private key
+ * @param expected_response_code which HTTP response code
+ *        we expect from the exchange.
+ * @return the withdraw command to be executed by the interpreter.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+  const char *label,
+  const char *reserve_reference,
+  const char *amount,
+  uint8_t age,
+  const char *coin_ref,
+  unsigned int expected_response_code);
+
+
+/**
+ * Create withdraw command, letting the caller specify the
+ * amount by a denomination key.
+ *
+ * @param label command label.
+ * @param reserve_reference reference to the reserve to withdraw
+ *        from; will provide reserve priv to sign the request.
+ * @param dk denomination public key.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_denomination (
+  const char *label,
+  const char *reserve_reference,
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  unsigned int expected_response_code);
+
+
+/**
+ * Modify a withdraw command to enable retries when the
+ * reserve is not yet full or we get other transient
+ * errors from the exchange.
+ *
+ * @param cmd a withdraw command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a GET "reserves" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_status (const char *label,
+                          const char *reserve_reference,
+                          const char *expected_balance,
+                          unsigned int expected_response_code);
+
+
+/**
+ * Create a GET "reserves" command with a @a timeout.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param timeout how long to long-poll for the reserve to exist.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll (const char *label,
+                                const char *reserve_reference,
+                                const char *expected_balance,
+                                struct GNUNET_TIME_Relative timeout,
+                                unsigned int expected_response_code);
+
+
+/**
+ * Wait for #TALER_TESTING_cmd_reserve_poll() to finish.
+ * Fail if it did not conclude by the timeout.
+ *
+ * @param label our label
+ * @param timeout how long to give the long poll to finish
+ * @param poll_reference reference to a #TALER_TESTING_cmd_reserve_poll() 
command
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll_finish (const char *label,
+                                       struct GNUNET_TIME_Relative timeout,
+                                       const char *poll_reference);
+
+
+/**
+ * Create a POST "/reserves/$RID/history" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_history (const char *label,
+                                   const char *reserve_reference,
+                                   const char *expected_balance,
+                                   unsigned int expected_response_code);
+
+
+/**
+ * Create a POST "/reserves/$RID/status" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to check.
+ * @param expected_balance expected balance for the reserve.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_status (const char *label,
+                                  const char *reserve_reference,
+                                  const char *expected_balance,
+                                  unsigned int expected_response_code);
+
+
+/**
+ * Create a POST "/reserves/$RID/open" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to open.
+ * @param reserve_pay amount to pay from the reserve balance
+ * @param expiration_time how long into the future should the reserve remain 
open
+ * @param min_purses minimum number of purses to allow
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL terminated list of pairs of coin references and amounts
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_open (const char *label,
+                                const char *reserve_reference,
+                                const char *reserve_pay,
+                                struct GNUNET_TIME_Relative expiration_time,
+                                uint32_t min_purses,
+                                unsigned int expected_response_code,
+                                ...);
+
+
+/**
+ * Create a GET "/reserves/$RID/attest" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to get attestable 
attributes of.
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list of attributes expected
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_get_attestable (const char *label,
+                                          const char *reserve_reference,
+                                          unsigned int expected_response_code,
+                                          ...);
+
+
+/**
+ * Create a POST "/reserves/$RID/attest" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to get attests for
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list of attributes that should be attested
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_attest (const char *label,
+                                  const char *reserve_reference,
+                                  unsigned int expected_response_code,
+                                  ...);
+
+
+/**
+ * Create a POST "/reserves/$RID/close" command.
+ *
+ * @param label the command label.
+ * @param reserve_reference reference to the reserve to close.
+ * @param target_account where to wire funds remaining, can be NULL
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_close (const char *label,
+                                 const char *reserve_reference,
+                                 const char *target_account,
+                                 unsigned int expected_response_code);
+
+
+/**
+ * Create a "deposit" command.
+ *
+ * @param label command label.
+ * @param coin_reference reference to any operation that can
+ *        provide a coin.
+ * @param coin_index if @a withdraw_reference offers an array of
+ *        coins, this parameter selects which one in that array.
+ *        This value is currently ignored, as only one-coin
+ *        withdrawals are implemented.
+ * @param target_account_payto target account for the "deposit"
+ *        request.
+ * @param contract_terms contract terms to be signed over by the
+ *        coin.
+ * @param refund_deadline refund deadline, zero means 'no refunds'.
+ * @param amount how much is going to be deposited.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit (const char *label,
+                           const char *coin_reference,
+                           unsigned int coin_index,
+                           const char *target_account_payto,
+                           const char *contract_terms,
+                           struct GNUNET_TIME_Relative refund_deadline,
+                           const char *amount,
+                           unsigned int expected_response_code);
+
+/**
+ * Create a "deposit" command that references an existing merchant key.
+ *
+ * @param label command label.
+ * @param coin_reference reference to any operation that can
+ *        provide a coin.
+ * @param coin_index if @a withdraw_reference offers an array of
+ *        coins, this parameter selects which one in that array.
+ *        This value is currently ignored, as only one-coin
+ *        withdrawals are implemented.
+ * @param target_account_payto target account for the "deposit"
+ *        request.
+ * @param contract_terms contract terms to be signed over by the
+ *        coin.
+ * @param refund_deadline refund deadline, zero means 'no refunds'.
+ *        Note, if time were absolute, then it would have come
+ *        one day and disrupt tests meaning.
+ * @param amount how much is going to be deposited.
+ * @param expected_response_code expected HTTP response code.
+ * @param merchant_priv_reference reference to another operation
+ *        that has a merchant private key trait
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_with_ref (const char *label,
+                                    const char *coin_reference,
+                                    unsigned int coin_index,
+                                    const char *target_account_payto,
+                                    const char *contract_terms,
+                                    struct GNUNET_TIME_Relative 
refund_deadline,
+                                    const char *amount,
+                                    unsigned int expected_response_code,
+                                    const char *merchant_priv_reference);
+
+/**
+ * Modify a deposit command to enable retries when we get transient
+ * errors from the exchange.
+ *
+ * @param cmd a deposit command
+ * @return the command with retries enabled
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a "deposit" command that repeats an existing
+ * deposit command.
+ *
+ * @param label command label.
+ * @param deposit_reference which deposit command should we repeat
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_replay (const char *label,
+                                  const char *deposit_reference,
+                                  unsigned int expected_response_code);
+
+
+/**
+ * Create a "batch deposit" command.
+ *
+ * @param label command label.
+ * @param target_account_payto target account for the "deposit"
+ *        request.
+ * @param contract_terms contract terms to be signed over by the
+ *        coin.
+ * @param refund_deadline refund deadline, zero means 'no refunds'.
+ * @param expected_response_code expected HTTP response code.
+ * @param ... NULL-terminated list with an even number of
+ *            strings that alternate referring to coins
+ *            (possibly with index using label#index notation)
+ *            and the amount of that coin to deposit
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_deposit (const char *label,
+                                 const char *target_account_payto,
+                                 const char *contract_terms,
+                                 struct GNUNET_TIME_Relative refund_deadline,
+                                 unsigned int expected_response_code,
+                                 ...);
+
+
+/**
+ * Create a "refresh melt" command.
+ *
+ * @param label command label.
+ * @param coin_reference reference to a command
+ *        that will provide a coin to refresh.
+ * @param expected_response_code expected HTTP code.
+ * @param ... NULL-terminated list of amounts to be melted
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_melt (const char *label,
+                        const char *coin_reference,
+                        unsigned int expected_response_code,
+                        ...);
+
+
+/**
+ * Create a "refresh melt" CMD that does TWO /refresh/melt
+ * requests.  This was needed to test the replay of a valid melt
+ * request, see #5312.
+ *
+ * @param label command label
+ * @param coin_reference reference to a command that will provide
+ *        a coin to refresh
+ * @param expected_response_code expected HTTP code
+ * @param ... NULL-terminated list of amounts to be melted
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_melt_double (const char *label,
+                               const char *coin_reference,
+                               unsigned int expected_response_code,
+                               ...);
+
+
+/**
+ * Modify a "refresh melt" command to enable retries.
+ *
+ * @param cmd command
+ * @return modified command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_melt_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a "refresh reveal" command.
+ *
+ * @param label command label.
+ * @param melt_reference reference to a "refresh melt" command.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_reveal (const char *label,
+                                  const char *melt_reference,
+                                  unsigned int expected_response_code);
+
+
+/**
+ * Modify a "refresh reveal" command to enable retries.
+ *
+ * @param cmd command
+ * @return modified command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_reveal_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a "refresh link" command.
+ *
+ * @param label command label.
+ * @param reveal_reference reference to a "refresh reveal" CMD.
+ * @param expected_response_code expected HTTP response code
+ * @return the "refresh link" command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_link (const char *label,
+                                const char *reveal_reference,
+                                unsigned int expected_response_code);
+
+
+/**
+ * Modify a "refresh link" command to enable retries.
+ *
+ * @param cmd command
+ * @return modified command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refresh_link_with_retry (struct TALER_TESTING_Command cmd);
+
+
+/**
+ * Create a "track transaction" command.
+ *
+ * @param label the command label.
+ * @param transaction_reference reference to a deposit operation,
+ *        will be used to get the input data for the track.
+ * @param coin_index index of the coin involved in the transaction.
+ * @param expected_response_code expected HTTP response code.
+ * @param bank_transfer_reference reference to a command that
+ *        can offer a WTID so as to check that against what WTID
+ *        the tracked operation has.  Set as NULL if not needed.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transaction (const char *label,
+                                     const char *transaction_reference,
+                                     unsigned int coin_index,
+                                     unsigned int expected_response_code,
+                                     const char *bank_transfer_reference);
+
+/**
+ * Make a "track transfer" CMD where no "expected"-arguments,
+ * except the HTTP response code, are given.  The best use case
+ * is when what matters to check is the HTTP response code, e.g.
+ * when a bogus WTID was passed.
+ *
+ * @param label the command label
+ * @param wtid_reference reference to any command which can provide
+ *        a wtid.  If NULL is given, then a all zeroed WTID is
+ *        used that will at 99.9999% probability NOT match any
+ *        existing WTID known to the exchange.
+ * @param expected_response_code expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transfer_empty (const char *label,
+                                        const char *wtid_reference,
+                                        unsigned int expected_response_code);
+
+
+/**
+ * Make a "track transfer" command, specifying which amount and
+ * wire fee are expected.
+ *
+ * @param label the command label.
+ * @param wtid_reference reference to any command which can provide
+ *        a wtid.  Will be the one tracked.
+ * @param expected_response_code expected HTTP response code.
+ * @param expected_total_amount how much money we expect being moved
+ *        with this wire-transfer.
+ * @param expected_wire_fee expected wire fee.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transfer (const char *label,
+                                  const char *wtid_reference,
+                                  unsigned int expected_response_code,
+                                  const char *expected_total_amount,
+                                  const char *expected_wire_fee);
+
+
+/**
+ * Make a "bank check" CMD.  It checks whether a particular wire transfer from
+ * the exchange (debit) has been made or not.
+ *
+ * @param label the command label.
+ * @param exchange_base_url base url of the exchange involved in
+ *        the wire transfer.
+ * @param amount the amount expected to be transferred.
+ * @param debit_payto the account that gave money.
+ * @param credit_payto the account that received money.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_transfer (const char *label,
+                                       const char *exchange_base_url,
+                                       const char *amount,
+                                       const char *debit_payto,
+                                       const char *credit_payto);
+
+
+/**
+ * Make a "bank check" CMD.  It checks whether a particular wire transfer to
+ * the exchange (credit) has been made or not.
+ *
+ * @param label the command label.
+ * @param amount the amount expected to be transferred.
+ * @param debit_payto the account that gave money.
+ * @param credit_payto the account that received money.
+ * @param reserve_pub_ref command that provides the reserve public key to 
expect
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_admin_transfer (const char *label,
+                                             const char *amount,
+                                             const char *debit_payto,
+                                             const char *credit_payto,
+                                             const char *reserve_pub_ref);
+
+
+/**
+ * Define a "bank check" CMD that takes the input
+ * data from another CMD that offers it.
+ *
+ * @param label command label.
+ * @param deposit_reference reference to a CMD that is
+ *        able to provide the "check bank transfer" operation
+ *        input data.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_transfer_with_ref (const char *label,
+                                                const char *deposit_reference);
+
+
+/**
+ * Checks whether all the wire transfers got "checked"
+ * by the "bank check" CMD.
+ *
+ * @param label command label.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_bank_empty (const char *label);
+
+
+/**
+ * Create a "refund" command, allow to specify refund transaction
+ * id.  Mainly used to create conflicting requests.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP status code.
+ * @param refund_amount the amount to ask a refund for.
+ * @param coin_reference reference to a command that can
+ *        provide a coin to be refunded.
+ * @param refund_transaction_id transaction id to use
+ *        in the request.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_with_id (const char *label,
+                                  unsigned int expected_response_code,
+                                  const char *refund_amount,
+                                  const char *coin_reference,
+                                  uint64_t refund_transaction_id);
+
+
+/**
+ * Create a "refund" command.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP status code.
+ * @param refund_amount the amount to ask a refund for.
+ * @param coin_reference reference to a command that can
+ *        provide a coin to be refunded.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund (const char *label,
+                          unsigned int expected_response_code,
+                          const char *refund_amount,
+                          const char *coin_reference);
+
+
+/**
+ * Make a "recoup" command.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param coin_reference reference to any command which
+ *        offers a coin and reserve private key.  May specify
+ *        the index of the coin using "$LABEL#$INDEX" syntax.
+ *        Here, $INDEX must be a non-negative number.
+ * @param amount how much do we expect to recoup, NULL for nothing
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup (const char *label,
+                          unsigned int expected_response_code,
+                          const char *coin_reference,
+                          const char *amount);
+
+
+/**
+ * Make a "recoup-refresh" command.
+ *
+ * @param label the command label
+ * @param expected_response_code expected HTTP status code
+ * @param coin_reference reference to any command which
+ *        offers a coin and reserve private key.  May specify
+ *        the index of the coin using "$LABEL#$INDEX" syntax.
+ *        Here, $INDEX must be a non-negative number.
+ * @param melt_reference label of the melt operation
+ * @param amount how much do we expect to recoup, NULL for nothing
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_recoup_refresh (const char *label,
+                                  unsigned int expected_response_code,
+                                  const char *coin_reference,
+                                  const char *melt_reference,
+                                  const char *amount);
+
+
+/**
+ * Make a "revoke" command.
+ *
+ * @param label the command label.
+ * @param expected_response_code expected HTTP status code.
+ * @param coin_reference reference to a CMD that will offer the
+ *        denomination to revoke.
+ * @param config_filename configuration file name.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke (const char *label,
+                          unsigned int expected_response_code,
+                          const char *coin_reference,
+                          const char *config_filename);
+
+
+/**
+ * Create a "signal" CMD.
+ *
+ * @param label command label.
+ * @param process handle to the process to signal.
+ * @param signal signal to send.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_signal (const char *label,
+                          struct GNUNET_OS_Process *process,
+                          int signal);
+
+
+/**
+ * Sleep for @a duration_s seconds.
+ *
+ * @param label command label.
+ * @param duration_s number of seconds to sleep
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_sleep (const char *label,
+                         unsigned int duration_s);
+
+
+/**
+ * This CMD simply tries to connect via HTTP to the
+ * service addressed by @a url.  It attempts 10 times
+ * before giving up and make the test fail.
+ *
+ * @param label label for the command.
+ * @param url complete URL to connect to.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wait_service (const char *label,
+                                const char *url);
+
+/**
+ * Create a "batch" command.  Such command takes a
+ * end_CMD-terminated array of CMDs and executed them.
+ * Once it hits the end CMD, it passes the control
+ * to the next top-level CMD, regardless of it being
+ * another batch or ordinary CMD.
+ *
+ * @param label the command label.
+ * @param batch array of CMDs to execute.
+ *
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch (const char *label,
+                         struct TALER_TESTING_Command *batch);
+
+
+/**
+ * Test if this command is a batch command.
+ *
+ * @return false if not, true if it is a batch command
+ */
+bool
+TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd);
+
+
+/**
+ * Advance internal pointer to next command.
+ *
+ * @param is interpreter state.
+ * @param[in,out] cls closure of the batch
+ * @return true to advance IP in parent
+ */
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+                              void *cls);
+
+
+/**
+ * Obtain what command the batch is at.
+ *
+ * @return cmd current batch command
+ */
+struct TALER_TESTING_Command *
+TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd);
+
+
+/**
+ * Set what command the batch should be at.
+ *
+ * @param cmd current batch command
+ * @param new_ip where to move the IP
+ */
+void
+TALER_TESTING_cmd_batch_set_current (const struct TALER_TESTING_Command *cmd,
+                                     unsigned int new_ip);
+
+
+/**
+ * Make the "insert-deposit" CMD.
+ *
+ * @param label command label.
+ * @param db_cfg configuration to talk to the DB
+ * @param merchant_name Human-readable name of the merchant.
+ * @param merchant_account merchant's account name (NOT a payto:// URI)
+ * @param exchange_timestamp when did the exchange receive the deposit
+ * @param wire_deadline point in time where the aggregator should have
+ *        wired money to the merchant.
+ * @param amount_with_fee amount to deposit (inclusive of deposit fee)
+ * @param deposit_fee deposit fee
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_insert_deposit (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *db_cfg,
+  const char *merchant_name,
+  const char *merchant_account,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Relative wire_deadline,
+  const char *amount_with_fee,
+  const char *deposit_fee);
+
+
+/**
+ * Performance counter.
+ */
+struct TALER_TESTING_Timer
+{
+  /**
+   * For which type of commands.
+   */
+  const char *prefix;
+
+  /**
+   * Total time spend in all commands of this type.
+   */
+  struct GNUNET_TIME_Relative total_duration;
+
+  /**
+   * Total time spend waiting for the *successful* exeuction
+   * in all commands of this type.
+   */
+  struct GNUNET_TIME_Relative success_latency;
+
+  /**
+   * Number of commands summed up.
+   */
+  unsigned int num_commands;
+
+  /**
+   * Number of retries summed up.
+   */
+  unsigned int num_retries;
+};
+
+
+/**
+ * Obtain performance data from the interpreter.
+ *
+ * @param timers what commands (by label) to obtain runtimes for
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers);
+
+
+/**
+ * Add the auditor to the exchange's list of auditors.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
+ *
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add (const char *label,
+                               unsigned int expected_http_status,
+                               bool bad_sig);
+
+
+/**
+ * Remove the auditor from the exchange's list of auditors.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
+ *
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_del (const char *label,
+                               unsigned int expected_http_status,
+                               bool bad_sig);
+
+
+/**
+ * Add affirmation that the auditor is auditing the given
+ * denomination.
+ * The information about the auditor is taken from the
+ * "[auditor]" section in the configuration file.
+ *
+ * @param label command label.
+ * @param expected_http_status expected HTTP status from exchange
+ * @param denom_ref reference to a command identifying a denomination key
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_auditor_add_denom_sig (const char *label,
+                                         unsigned int expected_http_status,
+                                         const char *denom_ref,
+                                         bool bad_sig);
+
+
+/**
+ * Add statement about wire fees of the exchange. This is always
+ * done for a few hours around the current time (for the test).
+ *
+ * @param label command label.
+ * @param wire_method wire method to set wire fees for
+ * @param wire_fee the wire fee to affirm
+ * @param closing_fee the closing fee to affirm
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_wire_fee (const char *label,
+                                const char *wire_method,
+                                const char *wire_fee,
+                                const char *closing_fee,
+                                unsigned int expected_http_status,
+                                bool bad_sig);
+
+
+/**
+ * Add the given payto-URI bank account to the list of bank
+ * accounts used by the exchange.
+ *
+ * @param label command label.
+ * @param payto_uri URI identifying the bank account
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_add (const char *label,
+                            const char *payto_uri,
+                            unsigned int expected_http_status,
+                            bool bad_sig);
+
+
+/**
+ * Remove the given payto-URI bank account from the list of bank
+ * accounts used by the exchange.
+ *
+ * @param label command label.
+ * @param payto_uri URI identifying the bank account
+ * @param expected_http_status expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wire_del (const char *label,
+                            const char *payto_uri,
+                            unsigned int expected_http_status,
+                            bool bad_sig);
+
+/**
+ * Sign all extensions that the exchange has to offer, f. e. the extension for
+ * age restriction.  This has to be run before any withdrawal of age restricted
+ * can be performed.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+                                                const char *config_filename);
+
+
+/**
+ * Sign all exchange denomination and online signing keys
+ * with the "offline" key and provide those signatures to
+ * the exchange. (Downloads the keys, makes the signature
+ * and uploads the result, all in one.)
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_keys (const char *label,
+                                          const char *config_filename);
+
+
+/**
+ * Sign a wire fee structure.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param wire_fee the wire fee to affirm (for the current year)
+ * @param closing_fee the closing fee to affirm (for the current year)
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_fees (const char *label,
+                                          const char *config_filename,
+                                          const char *wire_fee,
+                                          const char *closing_fee);
+
+
+/**
+ * Sign global fee structure.
+ *
+ * @param label command label.
+ * @param config_filename configuration filename.
+ * @param history_fee the history fee to charge (for the current year)
+ * @param account_fee the account fee to charge (for the current year)
+ * @param purse_fee the purse fee to charge (for the current year)
+ * @param purse_timeout when do purses time out
+ * @param history_expiration when does an account history expire
+ * @param num_purses number of (free) active purses per account
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_global_fees (
+  const char *label,
+  const char *config_filename,
+  const char *history_fee,
+  const char *account_fee,
+  const char *purse_fee,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  unsigned int num_purses);
+
+
+/**
+ * Revoke an exchange denomination key.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @param denom_ref reference to a command that identifies
+ *        a denomination key (i.e. because it was used to
+ *        withdraw a coin).
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_denom_key (
+  const char *label,
+  unsigned int expected_response_code,
+  bool bad_sig,
+  const char *denom_ref);
+
+
+/**
+ * Revoke an exchange online signing key.
+ *
+ * @param label command label.
+ * @param expected_response_code expected HTTP status from exchange
+ * @param bad_sig should we use a bogus signature?
+ * @param signkey_ref reference to a command that identifies
+ *        a signing key (i.e. because it was used to
+ *        sign a deposit confirmation).
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_sign_key (
+  const char *label,
+  unsigned int expected_response_code,
+  bool bad_sig,
+  const char *signkey_ref);
+
+
+/**
+ * Create a request for a wallet's KYC UUID.
+ *
+ * @param label command label.
+ * @param reserve_reference command with reserve private key to use (or NULL 
to create a fresh reserve key).
+ * @param threshold_balance balance amount to pass to the exchange
+ * @param expected_response_code expected HTTP status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wallet_kyc_get (const char *label,
+                                  const char *reserve_reference,
+                                  const char *threshold_balance,
+                                  unsigned int expected_response_code);
+
+
+/**
+ * Create a request for an account's KYC status.
+ *
+ * @param label command label.
+ * @param payment_target_reference command with a payment target to query
+ * @param expected_response_code expected HTTP status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_kyc_get (const char *label,
+                                 const char *payment_target_reference,
+                                 unsigned int expected_response_code);
+
+
+/**
+ * Create a KYC proof request. Only useful in conjunction with the OAuth2.0
+ * logic, as it generates an OAuth2.0-specific request.
+ *
+ * @param label command label.
+ * @param payment_target_reference command with a payment target to query
+ * @param logic_section name of the KYC provider section
+ *         in the exchange configuration for this proof
+ * @param code OAuth 2.0 code to use
+ * @param expected_response_code expected HTTP status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proof_kyc_oauth2 (
+  const char *label,
+  const char *payment_target_reference,
+  const char *logic_section,
+  const char *code,
+  unsigned int expected_response_code);
+
+
+/**
+ * Starts a fake OAuth 2.0 service on @a port for testing
+ * KYC processes which also provides a @a birthdate in a response
+ *
+ * @param label command label
+ * @param birthdate fixed birthdate, such as "2022-03-04", "2022-03-00", 
"2022-00-00"
+ * @param port the TCP port to listen on
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_oauth_with_birthdate (const char *label,
+                                        const char *birthdate,
+                                        uint16_t port);
+
+/**
+ * Starts a fake OAuth 2.0 service on @a port for testing
+ * KYC processes.
+ *
+ * @param label command label
+ * @param port the TCP port to listen on
+ */
+#define TALER_TESTING_cmd_oauth(label, port) \
+  TALER_TESTING_cmd_oauth_with_birthdate ((label), NULL, (port))
+
+
+/* ****************** P2P payment commands ****************** */
+
+
+/**
+ * Creates a purse with deposits.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param contract_terms contract, JSON string
+ * @param upload_contract should we upload the contract
+ * @param purse_expiration how long until the purse expires
+ * @param ... NULL-terminated list of references to coins to be deposited
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_deposit (
+  const char *label,
+  unsigned int expected_http_status,
+  const char *contract_terms,
+  bool upload_contract,
+  struct GNUNET_TIME_Relative purse_expiration,
+  ...);
+
+
+/**
+ * Deletes a purse.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param purse_cmd command that created the purse
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_delete (
+  const char *label,
+  unsigned int expected_http_status,
+  const char *purse_cmd);
+
+
+/**
+ * Retrieve contract (also checks that the contract matches
+ * the upload command).
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param for_merge true if for merge, false if for deposit
+ * @param contract_ref reference to a command providing us with the contract 
private key
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_contract_get (
+  const char *label,
+  unsigned int expected_http_status,
+  bool for_merge,
+  const char *contract_ref);
+
+
+/**
+ * Retrieve purse state by merge private key.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param merge_ref reference to a command providing us with the merge private 
key
+ * @param reserve_ref reference to a command providing us with a reserve 
private key; if NULL, we create a fresh reserve
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_merge (
+  const char *label,
+  unsigned int expected_http_status,
+  const char *merge_ref,
+  const char *reserve_ref);
+
+
+/**
+ * Retrieve purse state.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param purse_ref reference to a command providing us with the purse private 
key
+ * @param expected_balance how much should be in the purse
+ * @param wait_for_merge true to wait for a merge event, otherwise wait for a 
deposit event
+ * @param timeout how long to wait
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll (
+  const char *label,
+  unsigned int expected_http_status,
+  const char *purse_ref,
+  const char *expected_balance,
+  bool wait_for_merge,
+  struct GNUNET_TIME_Relative timeout);
+
+
+/**
+ * Wait for the poll command to complete.
+ *
+ * @param label command label
+ * @param timeout how long to wait at most
+ * @param poll_reference which poll command to wait for
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_poll_finish (const char *label,
+                                     struct GNUNET_TIME_Relative timeout,
+                                     const char *poll_reference);
+
+
+/**
+ * Creates a purse with reserve.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param contract_terms contract, JSON string
+ * @param upload_contract should we upload the contract
+ * @param pay_purse_fee should we pay a fee to create the purse
+ * @param expiration when should the purse expire
+ * @param reserve_ref reference to reserve key, or NULL to create a new reserve
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_create_with_reserve (
+  const char *label,
+  unsigned int expected_http_status,
+  const char *contract_terms,
+  bool upload_contract,
+  bool pay_purse_fee,
+  struct GNUNET_TIME_Relative expiration,
+  const char *reserve_ref);
+
+
+/**
+ * Deposit coins into a purse.
+ *
+ * @param label command label
+ * @param expected_http_status what HTTP status do we expect to get returned 
from the exchange
+ * @param min_age age restriction of the purse
+ * @param purse_ref reference to the purse
+ * @param ... NULL-terminated list of references to coins to be deposited
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_purse_deposit_coins (
+  const char *label,
+  unsigned int expected_http_status,
+  uint8_t min_age,
+  const char *purse_ref,
+  ...);
+
+
+/**
+ * Setup AML officer.
+ *
+ * @param label command label
+ * @param ref_cmd command that previously created the
+ *       officer, NULL to create one this time
+ * @param name full legal name of the officer to use
+ * @param is_active true to set the officer to active
+ * @param read_only true to restrict the officer to read-only
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_set_officer (
+  const char *label,
+  const char *ref_cmd,
+  const char *name,
+  bool is_active,
+  bool read_only);
+
+
+/**
+ * Make AML decision.
+ *
+ * @param label command label
+ * @param ref_officer command that previously created an
+ *       officer
+ * @param ref_operation command that previously created an
+ *       h_payto which to make an AML decision about
+ * @param new_threshold new threshold to set
+ * @param justification justification given for the decision
+ * @param new_state new AML state for the account
+ * @param kyc_requirement KYC requirement to impose
+ * @param expected_response expected HTTP return status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_take_aml_decision (
+  const char *label,
+  const char *ref_officer,
+  const char *ref_operation,
+  const char *new_threshold,
+  const char *justification,
+  enum TALER_AmlDecisionState new_state,
+  const char *kyc_requirement,
+  unsigned int expected_response);
+
+
+/**
+ * Fetch AML decision.
+ *
+ * @param label command label
+ * @param ref_officer command that previously created an
+ *       officer
+ * @param ref_operation command that previously created an
+ *       h_payto which to make an AML decision about
+ * @param expected_http_status expected HTTP response status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decision (
+  const char *label,
+  const char *ref_officer,
+  const char *ref_operation,
+  unsigned int expected_http_status);
+
+
+/**
+ * Fetch AML decisions.
+ *
+ * @param label command label
+ * @param ref_officer command that previously created an
+ *       officer
+ * @param filter AML state to filter by
+ * @param expected_http_status expected HTTP response status
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_aml_decisions (
+  const char *label,
+  const char *ref_officer,
+  enum TALER_AmlDecisionState filter,
+  unsigned int expected_http_status);
+
+
+/* ****************** convenience functions ************** */
+
+/**
+ * Get exchange URL from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange URL, or NULL on error
+ */
+const char *
+TALER_TESTING_get_exchange_url (
+  struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Get exchange keys from interpreter. Convenience function.
+ *
+ * @param is interpreter state.
+ * @return the exchange keys, or NULL on error
+ */
+struct TALER_EXCHANGE_Keys *
+TALER_TESTING_get_keys (
+  struct TALER_TESTING_Interpreter *is);
+
+
+/* *** Generic trait logic for implementing traits ********* */
+
+
+/**
+ * Opaque handle to fresh coins generated during refresh.
+ * Details are internal to the refresh logic.
+ */
+struct TALER_TESTING_FreshCoinData;
+
+
+/**
+ * A trait.
+ */
+struct TALER_TESTING_Trait
+{
+  /**
+   * Index number associated with the trait.  This gives the
+   * possibility to have _multiple_ traits on offer under the
+   * same name.
+   */
+  unsigned int index;
+
+  /**
+   * Trait type, for example "reserve-pub" or "coin-priv".
+   */
+  const char *trait_name;
+
+  /**
+   * Pointer to the piece of data to offer.
+   */
+  const void *ptr;
+};
+
+
+/**
+ * "end" trait.  Because traits are offered into arrays,
+ * this type of trait is used to mark the end of such arrays;
+ * useful when iterating over those.
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_trait_end (void);
+
+
+/**
+ * Extract a trait.
+ *
+ * @param traits the array of all the traits.
+ * @param[out] ret where to store the result.
+ * @param trait type of the trait to extract.
+ * @param index index number of the trait to extract.
+ * @return #GNUNET_OK when the trait is found.
+ */
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_trait (const struct TALER_TESTING_Trait *traits,
+                         const void **ret,
+                         const char *trait,
+                         unsigned int index);
+
+
+/* ****** Specific traits supported by this component ******* */
+
+
+/**
+ * Create headers for a trait with name @a name for
+ * statically allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT(name,type)   \
+  enum GNUNET_GenericReturnValue                          \
+    TALER_TESTING_get_trait_ ## name (                    \
+    const struct TALER_TESTING_Command *cmd,              \
+    type **ret);                                          \
+  struct TALER_TESTING_Trait                              \
+    TALER_TESTING_make_trait_ ## name (                   \
+    type * value);
+
+
+/**
+ * Create C implementation for a trait with name @a name for statically
+ * allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_IMPL_SIMPLE_TRAIT(name,type)  \
+  enum GNUNET_GenericReturnValue                         \
+    TALER_TESTING_get_trait_ ## name (                   \
+    const struct TALER_TESTING_Command *cmd,             \
+    type **ret)                                          \
+  {                                                      \
+    if (NULL == cmd->traits) return GNUNET_SYSERR;       \
+    return cmd->traits (cmd->cls,                        \
+                        (const void **) ret,             \
+                        TALER_S (name),                  \
+                        0);                              \
+  }                                                      \
+  struct TALER_TESTING_Trait                             \
+    TALER_TESTING_make_trait_ ## name (                  \
+    type * value)                                        \
+  {                                                      \
+    struct TALER_TESTING_Trait ret = {                   \
+      .trait_name = TALER_S (name),                      \
+      .ptr = (const void *) value                        \
+    };                                                   \
+    return ret;                                          \
+  }
+
+
+/**
+ * Create headers for a trait with name @a name for
+ * statically allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_DECL_INDEXED_TRAIT(name,type)  \
+  enum GNUNET_GenericReturnValue                          \
+    TALER_TESTING_get_trait_ ## name (                    \
+    const struct TALER_TESTING_Command *cmd,              \
+    unsigned int index,                                   \
+    type **ret);                                          \
+  struct TALER_TESTING_Trait                              \
+    TALER_TESTING_make_trait_ ## name (                   \
+    unsigned int index,                                   \
+    type * value);
+
+
+/**
+ * Create C implementation for a trait with name @a name for statically
+ * allocated data of type @a type.
+ */
+#define TALER_TESTING_MAKE_IMPL_INDEXED_TRAIT(name,type) \
+  enum GNUNET_GenericReturnValue                         \
+    TALER_TESTING_get_trait_ ## name (                   \
+    const struct TALER_TESTING_Command *cmd,             \
+    unsigned int index,                                  \
+    type **ret)                                          \
+  {                                                      \
+    if (NULL == cmd->traits) return GNUNET_SYSERR;       \
+    return cmd->traits (cmd->cls,                        \
+                        (const void **) ret,             \
+                        TALER_S (name),                  \
+                        index);                          \
+  }                                                      \
+  struct TALER_TESTING_Trait                             \
+    TALER_TESTING_make_trait_ ## name (                  \
+    unsigned int index,                                  \
+    type * value)                                        \
+  {                                                      \
+    struct TALER_TESTING_Trait ret = {                   \
+      .index = index,                                    \
+      .trait_name = TALER_S (name),                      \
+      .ptr = (const void *) value                        \
+    };                                                   \
+    return ret;                                          \
+  }
+
+
+/**
+ * Call #op on all simple traits.
+ */
+#define TALER_TESTING_SIMPLE_TRAITS(op) \
+  op (bank_row, const uint64_t)                                    \
+  op (officer_pub, const struct TALER_AmlOfficerPublicKeyP)        \
+  op (officer_priv, const struct TALER_AmlOfficerPrivateKeyP)      \
+  op (officer_name, const char)                                    \
+  op (aml_decision, enum TALER_AmlDecisionState)                   \
+  op (aml_justification, const char)                               \
+  op (auditor_priv, const struct TALER_AuditorPrivateKeyP)         \
+  op (auditor_pub, const struct TALER_AuditorPublicKeyP)           \
+  op (master_priv, const struct TALER_MasterPrivateKeyP)           \
+  op (master_pub, const struct TALER_MasterPublicKeyP)             \
+  op (purse_priv, const struct TALER_PurseContractPrivateKeyP)     \
+  op (purse_pub, const struct TALER_PurseContractPublicKeyP)       \
+  op (merge_priv, const struct TALER_PurseMergePrivateKeyP)        \
+  op (merge_pub, const struct TALER_PurseMergePublicKeyP)          \
+  op (contract_priv, const struct TALER_ContractDiffiePrivateP)    \
+  op (reserve_priv, const struct TALER_ReservePrivateKeyP)         \
+  op (reserve_sig, const struct TALER_ReserveSignatureP)           \
+  op (h_payto, const struct TALER_PaytoHashP)                      \
+  op (planchet_secret, const struct TALER_PlanchetMasterSecretP)   \
+  op (refresh_secret, const struct TALER_RefreshMasterSecretP)     \
+  op (reserve_pub, const struct TALER_ReservePublicKeyP)           \
+  op (merchant_priv, const struct TALER_MerchantPrivateKeyP)       \
+  op (merchant_pub, const struct TALER_MerchantPublicKeyP)         \
+  op (merchant_sig, const struct TALER_MerchantSignatureP)         \
+  op (wtid, const struct TALER_WireTransferIdentifierRawP)         \
+  op (bank_auth_data, const struct TALER_BANK_AuthenticationData)  \
+  op (contract_terms, const json_t)                                \
+  op (wire_details, const json_t)                                  \
+  op (exchange_url, const char)                                    \
+  op (auditor_url, const char)                                     \
+  op (exchange_bank_account_url, const char)                       \
+  op (taler_uri, const char)                                       \
+  op (payto_uri, const char)                                       \
+  op (kyc_url, const char)                                         \
+  op (web_url, const char)                                         \
+  op (row, const uint64_t)                                         \
+  op (legi_requirement_row, const uint64_t)                        \
+  op (array_length, const unsigned int)                            \
+  op (credit_payto_uri, const char)                                \
+  op (debit_payto_uri, const char)                                 \
+  op (order_id, const char)                                        \
+  op (amount, const struct TALER_Amount)                           \
+  op (amount_with_fee, const struct TALER_Amount)                  \
+  op (batch_cmds, struct TALER_TESTING_Command)                    \
+  op (uuid, const struct GNUNET_Uuid)                              \
+  op (fresh_coins, const struct TALER_TESTING_FreshCoinData *)     \
+  op (claim_token, const struct TALER_ClaimTokenP)                 \
+  op (relative_time, const struct GNUNET_TIME_Relative)            \
+  op (fakebank, struct TALER_FAKEBANK_Handle)                      \
+  op (keys, struct TALER_EXCHANGE_Keys)                            \
+  op (process, struct GNUNET_OS_Process *)
+
+
+/**
+ * Call #op on all indexed traits.
+ */
+#define TALER_TESTING_INDEXED_TRAITS(op)                                \
+  op (denom_pub, const struct TALER_EXCHANGE_DenomPublicKey)            \
+  op (denom_sig, const struct TALER_DenominationSignature)              \
+  op (amounts, const struct TALER_Amount)                               \
+  op (deposit_amount, const struct TALER_Amount)                        \
+  op (deposit_fee_amount, const struct TALER_Amount)                    \
+  op (age_commitment, const struct TALER_AgeCommitment)                 \
+  op (age_commitment_proof, const struct TALER_AgeCommitmentProof)      \
+  op (h_age_commitment, const struct TALER_AgeCommitmentHash)           \
+  op (reserve_history, const struct TALER_EXCHANGE_ReserveHistoryEntry) \
+  op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)       \
+  op (exchange_wd_value, const struct TALER_ExchangeWithdrawValues)     \
+  op (coin_priv, const struct TALER_CoinSpendPrivateKeyP)               \
+  op (coin_pub, const struct TALER_CoinSpendPublicKeyP)                 \
+  op (absolute_time, const struct GNUNET_TIME_Absolute)                 \
+  op (timestamp, const struct GNUNET_TIME_Timestamp)                    \
+  op (wire_deadline, const struct GNUNET_TIME_Timestamp)                \
+  op (refund_deadline, const struct GNUNET_TIME_Timestamp)              \
+  op (exchange_pub, const struct TALER_ExchangePublicKeyP)              \
+  op (exchange_sig, const struct TALER_ExchangeSignatureP)              \
+  op (blinding_key, const union TALER_DenominationBlindingKeyP)         \
+  op (h_blinded_coin, const struct TALER_BlindedCoinHashP)
+
+TALER_TESTING_SIMPLE_TRAITS (TALER_TESTING_MAKE_DECL_SIMPLE_TRAIT)
+
+TALER_TESTING_INDEXED_TRAITS (TALER_TESTING_MAKE_DECL_INDEXED_TRAIT)
+
+
+#endif
diff --git a/src/include/taler_util.h b/src/include/taler_util.h
new file mode 100644
index 0000000..8762f7d
--- /dev/null
+++ b/src/include/taler_util.h
@@ -0,0 +1,689 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file include/taler_util.h
+ * @brief Interface for common utility functions
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ */
+#ifndef TALER_UTIL_H
+#define TALER_UTIL_H
+
+#include <gnunet/gnunet_common.h>
+#define __TALER_UTIL_LIB_H_INSIDE__
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler_amount_lib.h"
+#include "taler_crypto_lib.h"
+
+/**
+ * Version of the Taler API, in hex.
+ * Thus 0.8.4-1 = 0x00080401.
+ */
+#define TALER_API_VERSION 0x00090200
+
+/**
+ * Stringify operator.
+ *
+ * @param a some expression to stringify. Must NOT be a macro.
+ * @return same expression as a constant string.
+ */
+#define TALER_S(a) #a
+
+/**
+ * Stringify operator.
+ *
+ * @param a some expression to stringify. Can be a macro.
+ * @return macro-expanded expression as a constant string.
+ */
+#define TALER_QUOTE(a) TALER_S (a)
+
+
+/* Define logging functions */
+#define TALER_LOG_DEBUG(...)                                  \
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
+
+#define TALER_LOG_INFO(...)                                  \
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__)
+
+#define TALER_LOG_WARNING(...)                                \
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING, __VA_ARGS__)
+
+#define TALER_LOG_ERROR(...)                                  \
+  GNUNET_log (GNUNET_ERROR_TYPE_ERROR, __VA_ARGS__)
+
+
+/**
+ * Tests a given as assertion and if failed prints it as a warning with the
+ * given reason
+ *
+ * @param EXP the expression to test as assertion
+ * @param reason string to print as warning
+ */
+#define TALER_assert_as(EXP, reason)                           \
+  do {                                                          \
+    if (EXP) break;                                             \
+    TALER_LOG_ERROR ("%s at %s:%d\n", reason, __FILE__, __LINE__);       \
+    abort ();                                                    \
+  } while (0)
+
+
+/**
+ * HTTP header with an AML officer signature to approve the inquiry.
+ * Used only in GET Requests.
+ */
+#define TALER_AML_OFFICER_SIGNATURE_HEADER "Taler-AML-Officer-Signature"
+
+/**
+ * Log an error message at log-level 'level' that indicates
+ * a failure of the command 'cmd' with the message given
+ * by gcry_strerror(rc).
+ */
+#define TALER_LOG_GCRY_ERROR(cmd, rc) do { TALER_LOG_ERROR ( \
+                                             "`%s' failed at %s:%d with error: 
%s\n", \
+                                             cmd, __FILE__, __LINE__, \
+                                             gcry_strerror (rc)); } while (0)
+
+
+#define TALER_gcry_ok(cmd) \
+  do {int rc; rc = cmd; if (! rc) break; \
+      TALER_LOG_ERROR ("A Gcrypt call failed at %s:%d with error: %s\n", \
+                       __FILE__, \
+                       __LINE__, gcry_strerror (rc)); abort (); } while (0)
+
+
+/**
+ * Initialize Gcrypt library.
+ */
+void
+TALER_gcrypt_init (void);
+
+
+/**
+ * Convert a buffer to an 8-character string
+ * representative of the contents. This is used
+ * for logging binary data when debugging.
+ *
+ * @param buf buffer to log
+ * @param buf_size number of bytes in @a buf
+ * @return text representation of buf, valid until next
+ *         call to this function
+ */
+const char *
+TALER_b2s (const void *buf,
+           size_t buf_size);
+
+
+/**
+ * Convert a fixed-sized object to a string using
+ * #TALER_b2s().
+ *
+ * @param obj address of object to convert
+ * @return string representing the binary obj buffer
+ */
+#define TALER_B2S(obj) TALER_b2s ((obj), sizeof (*(obj)))
+
+
+/**
+ * Obtain denomination amount from configuration file.
+ *
+ * @param cfg configuration to extract data from
+ * @param section section of the configuration to access
+ * @param option option of the configuration to access
+ * @param[out] denom set to the amount found in configuration
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+enum GNUNET_GenericReturnValue
+TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                         const char *section,
+                         const char *option,
+                         struct TALER_Amount *denom);
+
+
+/**
+ * Obtain denomination fee structure of a
+ * denomination from configuration file.  All
+ * fee options must start with "fee_" and have
+ * names typical for the respective fees.
+ *
+ * @param cfg configuration to extract data from
+ * @param currency expected currency
+ * @param section section of the configuration to access
+ * @param[out] fees set to the denomination fees
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ */
+enum GNUNET_GenericReturnValue
+TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                             const char *currency,
+                             const char *section,
+                             struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Check that all denominations in @a fees use
+ * @a currency
+ *
+ * @param currency desired currency
+ * @param fees fee set to check
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_denom_fee_check_currency (
+  const char *currency,
+  const struct TALER_DenomFeeSet *fees);
+
+
+/**
+ * Load our currency from the @a cfg (in section [taler]
+ * the option "CURRENCY").
+ *
+ * @param cfg configuration to use
+ * @param[out] currency where to write the result
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure
+ */
+enum GNUNET_GenericReturnValue
+TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
+                           char **currency);
+
+
+/**
+ * Allow user to specify an amount on the command line.
+ *
+ * @param shortName short name of the option
+ * @param name long name of the option
+ * @param argumentHelp help text for the option argument
+ * @param description long help text for the option
+ * @param[out] amount set to the amount specified at the command line
+ */
+struct GNUNET_GETOPT_CommandLineOption
+TALER_getopt_get_amount (char shortName,
+                         const char *name,
+                         const char *argumentHelp,
+                         const char *description,
+                         struct TALER_Amount *amount);
+
+
+/**
+ * Return default project data used by Taler.
+ */
+const struct GNUNET_OS_ProjectData *
+TALER_project_data_default (void);
+
+
+/**
+ * Initialize libtalerutil.
+ */
+void
+TALER_OS_init (void);
+
+
+/**
+ * Re-encode string at @a inp to match RFC 8785 (section 3.2.2.2).
+ *
+ * @param[in,out] inp pointer to string to re-encode
+ * @return number of bytes in resulting @a inp
+ */
+size_t
+TALER_rfc8785encode (char **inp);
+
+
+/**
+ * URL-encode a string according to rfc3986.
+ *
+ * @param s string to encode
+ * @returns the urlencoded string, the caller must free it with GNUNET_free()
+ */
+char *
+TALER_urlencode (const char *s);
+
+
+/**
+ * Test if all characters in @a url are valid for
+ * a URL.
+ *
+ * @param url URL to sanity-check
+ * @return true if @a url only contains valid characters
+ */
+bool
+TALER_url_valid_charset (const char *url);
+
+
+/**
+ * Check if @a lang matches the @a language_pattern, and if so with
+ * which preference.
+ * See also: https://tools.ietf.org/html/rfc7231#section-5.3.1
+ *
+ * @param language_pattern a language preferences string
+ *        like "fr-CH, fr;q=0.9, en;q=0.8, *;q=0.1"
+ * @param lang the 2-digit language to match
+ * @return q-weight given for @a lang in @a language_pattern, 1.0 if no 
weights are given;
+ *         0 if @a lang is not in @a language_pattern
+ */
+double
+TALER_language_matches (const char *language_pattern,
+                        const char *lang);
+
+
+/**
+ * Find out if an MHD connection is using HTTPS (either
+ * directly or via proxy).
+ *
+ * @param connection MHD connection
+ * @returns #GNUNET_YES if the MHD connection is using https,
+ *          #GNUNET_NO if the MHD connection is using http,
+ *          #GNUNET_SYSERR if the connection type couldn't be determined
+ */
+enum GNUNET_GenericReturnValue
+TALER_mhd_is_https (struct MHD_Connection *connection);
+
+
+/**
+ * Make an absolute URL with query parameters.
+ *
+ * If a 'value' is given as NULL, both the key and the value are skipped. Note
+ * that a NULL value does not terminate the list, only a NULL key signals the
+ * end of the list of arguments.
+ *
+ * @param base_url absolute base URL to use
+ * @param path path of the url
+ * @param ... NULL-terminated key-value pairs (char *) for query parameters,
+ *        only the value will be url-encoded
+ * @returns the URL, must be freed with #GNUNET_free
+ */
+char *
+TALER_url_join (const char *base_url,
+                const char *path,
+                ...);
+
+
+/**
+ * Make an absolute URL for the given parameters.
+ *
+ * If a 'value' is given as NULL, both the key and the value are skipped. Note
+ * that a NULL value does not terminate the list, only a NULL key signals the
+ * end of the list of arguments.
+ *
+ * @param proto protocol for the URL (typically https)
+ * @param host hostname for the URL
+ * @param prefix prefix for the URL
+ * @param path path for the URL
+ * @param ... NULL-terminated key-value pairs (char *) for query parameters,
+ *        the value will be url-encoded
+ * @returns the URL, must be freed with #GNUNET_free
+ */
+char *
+TALER_url_absolute_raw (const char *proto,
+                        const char *host,
+                        const char *prefix,
+                        const char *path,
+                        ...);
+
+
+/**
+ * Make an absolute URL for the given parameters.
+ *
+ * If a 'value' is given as NULL, both the key and the value are skipped. Note
+ * that a NULL value does not terminate the list, only a NULL key signals the
+ * end of the list of arguments.
+ *
+ * @param proto protocol for the URL (typically https)
+ * @param host hostname for the URL
+ * @param prefix prefix for the URL
+ * @param path path for the URL
+ * @param args NULL-terminated key-value pairs (char *) for query parameters,
+ *        the value will be url-encoded
+ * @returns the URL, must be freed with #GNUNET_free
+ */
+char *
+TALER_url_absolute_raw_va (const char *proto,
+                           const char *host,
+                           const char *prefix,
+                           const char *path,
+                           va_list args);
+
+
+/**
+ * Make an absolute URL for a given MHD connection.
+ *
+ * @param connection the connection to get the URL for
+ * @param path path of the url
+ * @param ... NULL-terminated key-value pairs (char *) for query parameters,
+ *        the value will be url-encoded
+ * @returns the URL, must be freed with #GNUNET_free
+ */
+char *
+TALER_url_absolute_mhd (struct MHD_Connection *connection,
+                        const char *path,
+                        ...);
+
+
+/**
+ * Obtain the payment method from a @a payto_uri
+ *
+ * @param payto_uri the URL to parse
+ * @return NULL on error (malformed @a payto_uri)
+ */
+char *
+TALER_payto_get_method (const char *payto_uri);
+
+
+/**
+ * Obtain the account name from a payto URL.
+ *
+ * @param payto an x-taler-bank payto URL
+ * @return only the account name from the @a payto URL, NULL if not an 
x-taler-bank
+ *   payto URL
+ */
+char *
+TALER_xtalerbank_account_from_payto (const char *payto);
+
+
+/**
+ * Obtain the receiver name from a payto URL.
+ *
+ * @param payto an x-taler-bank payto URL
+ * @return only the receiver name from the @a payto URL, NULL if not an 
x-taler-bank payto URL
+ */
+char *
+TALER_payto_get_receiver_name (const char *payto);
+
+
+/**
+ * Extract the subject value from the URI parameters.
+ *
+ * @param payto_uri the URL to parse
+ * @return NULL if the subject parameter is not found.
+ *         The caller should free the returned value.
+ */
+char *
+TALER_payto_get_subject (const char *payto_uri);
+
+
+/**
+ * Check that a payto:// URI is well-formed.
+ *
+ * @param payto_uri the URL to check
+ * @return NULL on success, otherwise an error
+ *         message to be freed by the caller!
+ */
+char *
+TALER_payto_validate (const char *payto_uri);
+
+
+/**
+ * Create payto://-URI for a given exchange base URL
+ * and a @a reserve_pub.
+ *
+ * @param exchange_url the base URL of the exchange
+ * @param reserve_pub the public key of the reserve
+ * @return payto://-URI for the reserve (without receiver-name!)
+ */
+char *
+TALER_reserve_make_payto (const char *exchange_url,
+                          const struct TALER_ReservePublicKeyP *reserve_pub);
+
+
+/**
+ * Check that an IBAN number is well-formed.
+ *
+ * Validates given IBAN according to the European Banking Standards.  See:
+ * 
http://www.europeanpaymentscouncil.eu/documents/ECBS%20IBAN%20standard%20EBS204_V3.2.pdf
+ *
+ * @param iban the IBAN to check
+ * @return NULL on success, otherwise an error
+ *         message to be freed by the caller!
+ */
+char *
+TALER_iban_validate (const char *iban);
+
+
+/**
+ * Possible values for a binary filter.
+ */
+enum TALER_EXCHANGE_YesNoAll
+{
+  /**
+   * If condition is yes.
+   */
+  TALER_EXCHANGE_YNA_YES = 1,
+
+  /**
+  * If condition is no.
+  */
+  TALER_EXCHANGE_YNA_NO = 2,
+
+  /**
+   * Condition disabled.
+   */
+  TALER_EXCHANGE_YNA_ALL = 3
+};
+
+
+/**
+ * Convert query argument to @a yna value.
+ *
+ * @param connection connection to take query argument from
+ * @param arg argument to try for
+ * @param default_val value to assign if the argument is not present
+ * @param[out] yna value to set
+ * @return true on success, false if the parameter was malformed
+ */
+bool
+TALER_arg_to_yna (struct MHD_Connection *connection,
+                  const char *arg,
+                  enum TALER_EXCHANGE_YesNoAll default_val,
+                  enum TALER_EXCHANGE_YesNoAll *yna);
+
+
+/**
+ * Convert YNA value to a string.
+ *
+ * @param yna value to convert
+ * @return string representation ("yes"/"no"/"all").
+ */
+const char *
+TALER_yna_to_string (enum TALER_EXCHANGE_YesNoAll yna);
+
+
+#ifdef __APPLE__
+/**
+ * Returns the first occurrence of `c` in `s`, or returns the null-byte
+ * terminating the string if it does not occur.
+ *
+ * @param s the string to search in
+ * @param c the character to search for
+ * @return char* the first occurrence of `c` in `s`
+ */
+char *strchrnul (const char *s, int c);
+
+#endif
+
+/**
+ * @brief Parses a date information into days after 1970-01-01 (or 0)
+ *
+ * The input MUST be of the form
+ *
+ *   1) YYYY-MM-DD, representing a valid date
+ *   2) YYYY-MM-00, representing a valid month in a particular year
+ *   3) YYYY-00-00, representing a valid year.
+ *
+ * In the cases 2) and 3) the out parameter is set to the beginning of the
+ * time, f.e. 1950-00-00 == 1950-01-01 and 1888-03-00 == 1888-03-01
+ *
+ * The output will set to the number of days after 1970-01-01 or 0, if the 
input
+ * represents a date belonging to the largest allowed age group.
+ *
+ * @param in Input string representation of the date
+ * @param mask Age mask
+ * @param[out] out Where to write the result
+ * @return GNUNET_OK on success, GNUNET_SYSERR otherwise
+ */
+enum GNUNET_GenericReturnValue
+TALER_parse_coarse_date (
+  const char *in,
+  const struct TALER_AgeMask *mask,
+  uint32_t *out);
+
+
+/**
+ * @brief Parses a string as a list of age groups.
+ *
+ * The string must consist of a colon-separated list of increasing integers
+ * between 0 and 31.  Each entry represents the beginning of a new age group.
+ * F.e. the string
+ *
+ *  "8:10:12:14:16:18:21"
+ *
+ * represents the following list of eight age groups:
+ *
+ * | Group |    Ages       |
+ * | -----:|:------------- |
+ * |    0  |  0, 1, ..., 7 |
+ * |    1  |  8, 9         |
+ * |    2  | 10, 11        |
+ * |    3  | 12, 13        |
+ * |    4  | 14, 15        |
+ * |    5  | 16, 17        |
+ * |    6  | 18, 19, 20    |
+ * |    7  | 21, ...       |
+ *
+ * which is then encoded as a bit mask with the corresponding bits set:
+ *
+ *  31     24        16        8         0
+ *  |      |         |         |         |
+ *  oooooooo  oo1oo1o1  o1o1o1o1  ooooooo1
+ *
+ * @param groups String representation of age groups
+ * @param[out] mask Mask representation for age restriction.
+ * @return Error, if age groups were invalid, OK otherwise.
+ */
+enum GNUNET_GenericReturnValue
+TALER_parse_age_group_string (
+  const char *groups,
+  struct TALER_AgeMask *mask);
+
+/**
+ * @brief Encodes the age mask into a string, like "8:10:12:14:16:18:21"
+ *
+ * NOTE: This function uses a static buffer.  It is not safe to call this
+ * function concurrently.
+ *
+ * @param mask Age mask
+ * @return String representation of the age mask.
+ *         Can be used as value in the TALER config.
+ */
+const char *
+TALER_age_mask_to_string (
+  const struct TALER_AgeMask *mask);
+
+/*
+ * @brief returns the age group of a given age for a given age mask
+ *
+ * @param mask Age mask
+ * @param age The given age
+ * @return age group
+ */
+uint8_t
+TALER_get_age_group (
+  const struct TALER_AgeMask *mask,
+  uint8_t age);
+
+/**
+ * @brief Parses a JSON object { "age_groups": "a:b:...y:z" }.
+ *
+ * @param root is the json object
+ * @param[out] mask on success, will contain the age mask
+ * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure.
+ */
+enum GNUNET_GenericReturnValue
+TALER_JSON_parse_age_groups (const json_t *root,
+                             struct TALER_AgeMask *mask);
+
+
+/* @brief Return the lowest age in the corresponding group for a given age
+ * according the given age mask.
+ *
+ * @param[IN] mask age mask
+ * @param[IN] age age to check
+ * @return lowest age in corresponding age group
+ */
+uint8_t
+TALER_get_lowest_age (
+  const struct TALER_AgeMask *mask,
+  uint8_t age);
+
+/* @brief Get the lowest age for the largest age group
+ *
+ * @param mask the age mask
+ * @return lowest age for the largest age group
+ */
+#define TALER_adult_age(mask) \
+  sizeof((mask)->bits) * 8 - __builtin_clz ((mask)->bits) - 1
+
+/**
+ * Handle to an external process that will assist
+ * with some JSON-to-JSON conversion.
+ */
+struct TALER_JSON_ExternalConversion;
+
+/**
+ * Type of a callback that receives a JSON @a result.
+ *
+ * @param cls closure
+ * @param status_type how did the process die
+ * @param code termination status code from the process
+ * @param result some JSON result, NULL if we failed to get an JSON output
+ */
+typedef void
+(*TALER_JSON_JsonCallback) (void *cls,
+                            enum GNUNET_OS_ProcessStatusType status_type,
+                            unsigned long code,
+                            const json_t *result);
+
+
+/**
+ * Launch some external helper @a binary to convert some @a input
+ * and eventually call @a cb with the result.
+ *
+ * @param input JSON to serialize and pass to the helper process
+ * @param cb function to call on the result
+ * @param cb_cls closure for @a cb
+ * @param binary name of the binary to execute
+ * @param ... NULL-terminated list of arguments for the @a binary,
+ *        usually starting with again the name of the binary
+ * @return handle to cancel the operation (and kill the helper)
+ */
+struct TALER_JSON_ExternalConversion *
+TALER_JSON_external_conversion_start (const json_t *input,
+                                      TALER_JSON_JsonCallback cb,
+                                      void *cb_cls,
+                                      const char *binary,
+                                      ...);
+
+/**
+ * Abort external conversion, killing the process and preventing
+ * the callback from being called. Must not be called after the
+ * callback was invoked.
+ *
+ * @param[in] ec external conversion handle to cancel
+ */
+void
+TALER_JSON_external_conversion_stop (
+  struct TALER_JSON_ExternalConversion *ec);
+
+#undef __TALER_UTIL_LIB_H_INSIDE__
+
+#endif
diff --git a/src/lib/.gitignore b/src/lib/.gitignore
new file mode 100644
index 0000000..6664876
--- /dev/null
+++ b/src/lib/.gitignore
@@ -0,0 +1 @@
+test_stefan
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
new file mode 100644
index 0000000..6ff8e23
--- /dev/null
+++ b/src/lib/Makefile.am
@@ -0,0 +1,128 @@
+# This Makefile.am is in the public domain
+
+AM_CPPFLAGS = \
+  -I$(top_srcdir)/src/include \
+  $(LIBGCRYPT_CFLAGS) \
+  $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+
+# Libraries
+
+lib_LTLIBRARIES = \
+  libtalerauditor.la \
+  libtalerexchange.la
+
+libtalerexchange_la_LDFLAGS = \
+  -version-info 5:0:0 \
+  -no-undefined
+libtalerexchange_la_SOURCES = \
+  exchange_api_add_aml_decision.c \
+  exchange_api_age_withdraw.c \
+  exchange_api_age_withdraw_reveal.c \
+  exchange_api_auditor_add_denomination.c \
+  exchange_api_batch_deposit.c \
+  exchange_api_batch_withdraw.c \
+  exchange_api_batch_withdraw2.c \
+  exchange_api_curl_defaults.c exchange_api_curl_defaults.h \
+  exchange_api_common.c exchange_api_common.h \
+  exchange_api_contracts_get.c \
+  exchange_api_csr_melt.c \
+  exchange_api_csr_withdraw.c \
+  exchange_api_handle.c exchange_api_handle.h \
+  exchange_api_deposits_get.c \
+  exchange_api_kyc_check.c \
+  exchange_api_kyc_proof.c \
+  exchange_api_kyc_wallet.c \
+  exchange_api_link.c \
+  exchange_api_lookup_aml_decision.c \
+  exchange_api_lookup_aml_decisions.c \
+  exchange_api_management_add_partner.c \
+  exchange_api_management_auditor_disable.c \
+  exchange_api_management_auditor_enable.c \
+  exchange_api_management_drain_profits.c \
+  exchange_api_management_get_keys.c \
+  exchange_api_management_post_keys.c \
+  exchange_api_management_post_extensions.c \
+  exchange_api_management_revoke_denomination_key.c \
+  exchange_api_management_revoke_signing_key.c \
+  exchange_api_management_set_global_fee.c \
+  exchange_api_management_set_wire_fee.c \
+  exchange_api_management_update_aml_officer.c \
+  exchange_api_management_wire_disable.c \
+  exchange_api_management_wire_enable.c \
+  exchange_api_melt.c \
+  exchange_api_purse_create_with_deposit.c \
+  exchange_api_purse_create_with_merge.c \
+  exchange_api_purse_delete.c \
+  exchange_api_purse_deposit.c \
+  exchange_api_purse_merge.c \
+  exchange_api_purses_get.c \
+  exchange_api_recoup.c \
+  exchange_api_recoup_refresh.c \
+  exchange_api_refresh_common.c exchange_api_refresh_common.h \
+  exchange_api_refreshes_reveal.c \
+  exchange_api_refund.c \
+  exchange_api_reserves_attest.c \
+  exchange_api_reserves_close.c \
+  exchange_api_reserves_get.c \
+  exchange_api_reserves_get_attestable.c \
+  exchange_api_reserves_history.c \
+  exchange_api_reserves_open.c \
+  exchange_api_reserves_status.c \
+  exchange_api_stefan.c \
+  exchange_api_transfers_get.c \
+  exchange_api_withdraw.c \
+  exchange_api_withdraw2.c
+libtalerexchange_la_LIBADD = \
+  libtalerauditor.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
+  $(top_builddir)/src/curl/libtalercurl.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetjson \
+  -lgnunetutil \
+  -ljansson \
+  $(LIBGNURLCURL_LIBS) \
+  $(XLIB)
+
+libtalerauditor_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+libtalerauditor_la_SOURCES = \
+  auditor_api_curl_defaults.c auditor_api_curl_defaults.h \
+  auditor_api_get_config.c \
+  auditor_api_deposit_confirmation.c \
+  auditor_api_exchanges.c
+libtalerauditor_la_LIBADD = \
+  $(top_builddir)/src/curl/libtalercurl.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetjson \
+  -lgnunetutil \
+  -ljansson \
+  -lm \
+  $(LIBGNURLCURL_LIBS) \
+  $(XLIB)
+
+
+check_PROGRAMS = \
+ test_stefan
+
+TESTS = \
+ $(check_PROGRAMS)
+
+
+test_stefan_SOURCES = \
+  test_stefan.c
+test_stefan_LDADD = \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil
diff --git a/src/lib/exchange_api_batch_deposit.c 
b/src/lib/exchange_api_batch_deposit.c
new file mode 100644
index 0000000..3aea22b
--- /dev/null
+++ b/src/lib/exchange_api_batch_deposit.c
@@ -0,0 +1,784 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2014-2022 Taler Systems SA
+
+   TALER is free software; you can redistribute it and/or modify it under the
+   terms of the GNU General Public License as published by the Free Software
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see
+   <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file lib/exchange_api_batch_deposit.c
+ * @brief Implementation of the /batch-deposit request of the exchange's HTTP 
API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_auditor_service.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * 1:#AUDITOR_CHANCE is the probability that we report deposits
+ * to the auditor.
+ *
+ * 20==5% of going to auditor. This is possibly still too high, but set
+ * deliberately this high for testing
+ */
+#define AUDITOR_CHANCE 20
+
+
+/**
+ * Entry in list of ongoing interactions with an auditor.
+ */
+struct TEAH_AuditorInteractionEntry
+{
+  /**
+   * DLL entry.
+   */
+  struct TEAH_AuditorInteractionEntry *next;
+
+  /**
+   * DLL entry.
+   */
+  struct TEAH_AuditorInteractionEntry *prev;
+
+  /**
+   * URL of our auditor. For logging.
+   */
+  const char *auditor_url;
+
+  /**
+   * Interaction state.
+   */
+  struct TALER_AUDITOR_DepositConfirmationHandle *dch;
+
+  /**
+   * Batch deposit this is for.
+   */
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
+};
+
+
+/**
+ * @brief A Deposit Handle
+ */
+struct TALER_EXCHANGE_BatchDepositHandle
+{
+
+  /**
+   * The keys of the exchange.
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * Context for our curl request(s).
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_BatchDepositResultCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Details about the contract.
+   */
+  struct TALER_EXCHANGE_DepositContractDetail dcd;
+
+  /**
+   * Array with details about the coins.
+   */
+  struct TALER_EXCHANGE_CoinDepositDetail *cdds;
+
+  /**
+   * Hash of the merchant's wire details.
+   */
+  struct TALER_MerchantWireHashP h_wire;
+
+  /**
+   * Hash over the extensions, or all zero.
+   */
+  struct TALER_ExtensionPolicyHashP h_policy;
+
+  /**
+   * Time when this confirmation was generated / when the exchange received
+   * the deposit request.
+   */
+  struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+  /**
+   * Exchange signatures, set for #auditor_cb.
+   */
+  struct TALER_ExchangeSignatureP *exchange_sigs;
+
+  /**
+   * Head of DLL of interactions with this auditor.
+   */
+  struct TEAH_AuditorInteractionEntry *ai_head;
+
+  /**
+   * Tail of DLL of interactions with this auditor.
+   */
+  struct TEAH_AuditorInteractionEntry *ai_tail;
+
+  /**
+   * Result to return to the application once @e ai_head is empty.
+   */
+  struct TALER_EXCHANGE_BatchDepositResult dr;
+
+  /**
+   * Exchange signing public key, set for #auditor_cb.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Response object to free at the end.
+   */
+  json_t *response;
+
+  /**
+   * Chance that we will inform the auditor about the deposit
+   * is 1:n, where the value of this field is "n".
+   */
+  unsigned int auditor_chance;
+
+  /**
+   * Length of the @e cdds array.
+   */
+  unsigned int num_cdds;
+
+};
+
+
+/**
+ * Finish batch deposit operation by calling the callback.
+ *
+ * @param[in] dh handle to finished batch deposit operation
+ */
+static void
+finish_dh (struct TALER_EXCHANGE_BatchDepositHandle *dh)
+{
+  dh->cb (dh->cb_cls,
+          &dh->dr);
+  TALER_EXCHANGE_batch_deposit_cancel (dh);
+}
+
+
+/**
+ * Function called with the result from our call to the
+ * auditor's /deposit-confirmation handler.
+ *
+ * @param cls closure of type `struct TEAH_AuditorInteractionEntry *`
+ * @param dcr response
+ */
+static void
+acc_confirmation_cb (
+  void *cls,
+  const struct TALER_AUDITOR_DepositConfirmationResponse *dcr)
+{
+  struct TEAH_AuditorInteractionEntry *aie = cls;
+  struct TALER_EXCHANGE_BatchDepositHandle *dh = aie->dh;
+
+  if (MHD_HTTP_OK != dcr->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to submit deposit confirmation to auditor `%s' with 
HTTP status %d (EC: %d). This is acceptable if it does not happen often.\n",
+                aie->auditor_url,
+                dcr->hr.http_status,
+                dcr->hr.ec);
+  }
+  GNUNET_CONTAINER_DLL_remove (dh->ai_head,
+                               dh->ai_tail,
+                               aie);
+  GNUNET_free (aie);
+  if (NULL == dh->ai_head)
+    finish_dh (dh);
+}
+
+
+/**
+ * Function called for each auditor to give us a chance to possibly
+ * launch a deposit confirmation interaction.
+ *
+ * @param cls closure
+ * @param auditor_url base URL of the auditor
+ * @param auditor_pub public key of the auditor
+ */
+static void
+auditor_cb (void *cls,
+            const char *auditor_url,
+            const struct TALER_AuditorPublicKeyP *auditor_pub)
+{
+  struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+  const struct TALER_EXCHANGE_SigningPublicKey *spk;
+  struct TEAH_AuditorInteractionEntry *aie;
+  struct TALER_Amount amount_without_fee;
+  const struct TALER_EXCHANGE_DenomPublicKey *dki;
+  unsigned int coin;
+
+  if (0 !=
+      GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                dh->auditor_chance))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Not providing deposit confirmation to auditor\n");
+    return;
+  }
+  coin = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK,
+                                   dh->num_cdds);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Will provide deposit confirmation to auditor `%s'\n",
+              TALER_B2S (auditor_pub));
+  dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
+                                                     
&dh->cdds[coin].h_denom_pub);
+  GNUNET_assert (NULL != dki);
+  spk = TALER_EXCHANGE_get_signing_key_info (dh->keys,
+                                             &dh->exchange_pub);
+  if (NULL == spk)
+  {
+    GNUNET_break_op (0);
+    return;
+  }
+  GNUNET_assert (0 <=
+                 TALER_amount_subtract (&amount_without_fee,
+                                        &dh->cdds[coin].amount,
+                                        &dki->fees.deposit));
+  aie = GNUNET_new (struct TEAH_AuditorInteractionEntry);
+  aie->dh = dh;
+  aie->auditor_url = auditor_url;
+  aie->dch = TALER_AUDITOR_deposit_confirmation (
+    dh->ctx,
+    auditor_url,
+    &dh->h_wire,
+    &dh->h_policy,
+    &dh->dcd.h_contract_terms,
+    dh->exchange_timestamp,
+    dh->dcd.wire_deadline,
+    dh->dcd.refund_deadline,
+    &amount_without_fee,
+    &dh->cdds[coin].coin_pub,
+    &dh->dcd.merchant_pub,
+    &dh->exchange_pub,
+    &dh->exchange_sigs[coin],
+    &dh->keys->master_pub,
+    spk->valid_from,
+    spk->valid_until,
+    spk->valid_legal,
+    &spk->master_sig,
+    &acc_confirmation_cb,
+    aie);
+  GNUNET_CONTAINER_DLL_insert (dh->ai_head,
+                               dh->ai_tail,
+                               aie);
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /deposit request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchDepositHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_finished (void *cls,
+                         long response_code,
+                         const void *response)
+{
+  struct TALER_EXCHANGE_BatchDepositHandle *dh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_BatchDepositResult *dr = &dh->dr;
+
+  dh->job = NULL;
+  dh->response = json_incref ((json_t*) j);
+  dr->hr.reply = dh->response;
+  dr->hr.http_status = (unsigned int) response_code;
+  switch (response_code)
+  {
+  case 0:
+    dr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      const json_t *sigs;
+      json_t *sig;
+      unsigned int idx;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_array_const ("exchange_sigs",
+                                      &sigs),
+        GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                     &dh->exchange_pub),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_string ("transaction_base_url",
+                                   &dr->details.ok.transaction_base_url),
+          NULL),
+        GNUNET_JSON_spec_timestamp ("exchange_timestamp",
+                                    &dh->exchange_timestamp),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      if (json_array_size (sigs) != dh->num_cdds)
+      {
+        GNUNET_break_op (0);
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      dh->exchange_sigs = GNUNET_new_array (dh->num_cdds,
+                                            struct TALER_ExchangeSignatureP);
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_test_signing_key (dh->keys,
+                                           &dh->exchange_pub))
+      {
+        GNUNET_break_op (0);
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+        break;
+      }
+      json_array_foreach (sigs, idx, sig)
+      {
+        struct GNUNET_JSON_Specification ispec[] = {
+          GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                       &dh->exchange_sigs[idx]),
+          GNUNET_JSON_spec_end ()
+        };
+        struct TALER_Amount amount_without_fee;
+        const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (sig,
+                               ispec,
+                               NULL, NULL))
+        {
+          GNUNET_break_op (0);
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+          break;
+        }
+        dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
+                                                           &dh->cdds[idx].
+                                                           h_denom_pub);
+        GNUNET_assert (NULL != dki);
+        GNUNET_assert (0 <=
+                       TALER_amount_subtract (&amount_without_fee,
+                                              &dh->cdds[idx].amount,
+                                              &dki->fees.deposit));
+
+        if (GNUNET_OK !=
+            TALER_exchange_online_deposit_confirmation_verify (
+              &dh->dcd.h_contract_terms,
+              &dh->h_wire,
+              &dh->h_policy,
+              dh->exchange_timestamp,
+              dh->dcd.wire_deadline,
+              dh->dcd.refund_deadline,
+              &amount_without_fee,
+              &dh->cdds[idx].coin_pub,
+              &dh->dcd.merchant_pub,
+              &dh->exchange_pub,
+              &dh->exchange_sigs[idx]))
+        {
+          GNUNET_break_op (0);
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_EXCHANGE_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE;
+          break;
+        }
+      }
+      TEAH_get_auditors_for_dc (dh->keys,
+                                &auditor_cb,
+                                dh);
+    }
+    dr->details.ok.exchange_sigs = dh->exchange_sigs;
+    dr->details.ok.exchange_pub = &dh->exchange_pub;
+    dr->details.ok.deposit_timestamp = dh->exchange_timestamp;
+    dr->details.ok.num_signatures = dh->num_cdds;
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    break;
+  case MHD_HTTP_CONFLICT:
+    {
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &coin_pub),
+        GNUNET_JSON_spec_end ()
+      };
+      const struct TALER_EXCHANGE_DenomPublicKey *dki;
+      bool found = false;
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      for (unsigned int i = 0; i<dh->num_cdds; i++)
+      {
+        if (0 !=
+            GNUNET_memcmp (&coin_pub,
+                           &dh->cdds[i].coin_pub))
+          continue;
+        dki = TALER_EXCHANGE_get_denomination_key_by_hash (dh->keys,
+                                                           &dh->cdds[i].
+                                                           h_denom_pub);
+        GNUNET_assert (NULL != dki);
+        if (GNUNET_OK !=
+            TALER_EXCHANGE_check_coin_conflict_ (
+              dh->keys,
+              j,
+              dki,
+              &dh->cdds[i].coin_pub,
+              &dh->cdds[i].coin_sig,
+              &dh->cdds[i].amount))
+        {
+          GNUNET_break_op (0);
+          dr->hr.http_status = 0;
+          dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+          break;
+        }
+        found = true;
+        break;
+      }
+      if (! found)
+      {
+        GNUNET_break_op (0);
+        dr->hr.http_status = 0;
+        dr->hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      dr->hr.ec = TALER_JSON_get_error_code (j);
+      dr->hr.hint = TALER_JSON_get_error_hint (j);
+    }
+    break;
+  case MHD_HTTP_GONE:
+    /* could happen if denomination was revoked */
+    /* Note: one might want to check /keys for revocation
+       signature here, alas tricky in case our /keys
+       is outdated => left to clients */
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    break;
+  default:
+    /* unexpected response code */
+    dr->hr.ec = TALER_JSON_get_error_code (j);
+    dr->hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for exchange deposit\n",
+                (unsigned int) response_code,
+                dr->hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  if (NULL != dh->ai_head)
+    return;
+  finish_dh (dh);
+}
+
+
+struct TALER_EXCHANGE_BatchDepositHandle *
+TALER_EXCHANGE_batch_deposit (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+  unsigned int num_cdds,
+  const struct TALER_EXCHANGE_CoinDepositDetail cdds[static num_cdds],
+  TALER_EXCHANGE_BatchDepositResultCallback cb,
+  void *cb_cls,
+  enum TALER_ErrorCode *ec)
+{
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
+  json_t *deposit_obj;
+  json_t *deposits;
+  CURL *eh;
+  struct TALER_Amount amount_without_fee;
+  const struct GNUNET_HashCode *wallet_data_hashp;
+
+  if (GNUNET_TIME_timestamp_cmp (dcd->refund_deadline,
+                                 >,
+                                 dcd->wire_deadline))
+  {
+    GNUNET_break_op (0);
+    *ec = TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE;
+    return NULL;
+  }
+  dh = GNUNET_new (struct TALER_EXCHANGE_BatchDepositHandle);
+  dh->auditor_chance = AUDITOR_CHANCE;
+  dh->cb = cb;
+  dh->cb_cls = cb_cls;
+  dh->cdds = GNUNET_memdup (cdds,
+                            num_cdds
+                            * sizeof (*cdds));
+  dh->num_cdds = num_cdds;
+  dh->dcd = *dcd;
+  if (NULL != dcd->policy_details)
+    TALER_deposit_policy_hash (dcd->policy_details,
+                               &dh->h_policy);
+  TALER_merchant_wire_signature_hash (dcd->merchant_payto_uri,
+                                      &dcd->wire_salt,
+                                      &dh->h_wire);
+  deposits = json_array ();
+  GNUNET_assert (NULL != deposits);
+  for (unsigned int i = 0; i<num_cdds; i++)
+  {
+    const struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
+    const struct TALER_EXCHANGE_DenomPublicKey *dki;
+    const struct TALER_AgeCommitmentHash *h_age_commitmentp;
+
+    dki = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                       &cdd->h_denom_pub);
+    if (NULL == dki)
+    {
+      *ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+      GNUNET_break_op (0);
+      return NULL;
+    }
+    if (0 >
+        TALER_amount_subtract (&amount_without_fee,
+                               &cdd->amount,
+                               &dki->fees.deposit))
+    {
+      *ec = TALER_EC_EXCHANGE_DEPOSIT_FEE_ABOVE_AMOUNT;
+      GNUNET_break_op (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Amount: %s\n",
+                  TALER_amount2s (&cdd->amount));
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Fee: %s\n",
+                  TALER_amount2s (&dki->fees.deposit));
+      GNUNET_free (dh->cdds);
+      GNUNET_free (dh);
+      return NULL;
+    }
+
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_verify_deposit_signature_ (dcd,
+                                                  &dh->h_policy,
+                                                  &dh->h_wire,
+                                                  cdd,
+                                                  dki))
+    {
+      *ec = TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID;
+      GNUNET_break_op (0);
+      GNUNET_free (dh->cdds);
+      GNUNET_free (dh);
+      return NULL;
+    }
+    if (GNUNET_is_zero (&cdd->h_age_commitment))
+      h_age_commitmentp = NULL;
+    else
+      h_age_commitmentp = &cdd->h_age_commitment;
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        deposits,
+        GNUNET_JSON_PACK (
+          TALER_JSON_pack_amount ("contribution",
+                                  &cdd->amount),
+          GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                      &cdd->h_denom_pub),
+          TALER_JSON_pack_denom_sig ("ub_sig",
+                                     &cdd->denom_sig),
+          GNUNET_JSON_pack_data_auto ("coin_pub",
+                                      &cdd->coin_pub),
+          GNUNET_JSON_pack_allow_null (
+            GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                        h_age_commitmentp)),
+          GNUNET_JSON_pack_data_auto ("coin_sig",
+                                      &cdd->coin_sig)
+          )));
+  }
+  dh->url = TALER_url_join (url,
+                            "batch-deposit",
+                            NULL);
+  if (NULL == dh->url)
+  {
+    GNUNET_break (0);
+    *ec = TALER_EC_GENERIC_ALLOCATION_FAILURE;
+    GNUNET_free (dh->url);
+    GNUNET_free (dh->cdds);
+    GNUNET_free (dh);
+    return NULL;
+  }
+
+  if (GNUNET_is_zero (&dcd->wallet_data_hash))
+    wallet_data_hashp = NULL;
+  else
+    wallet_data_hashp = &dcd->wallet_data_hash;
+
+  deposit_obj = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("merchant_payto_uri",
+                             dcd->merchant_payto_uri),
+    GNUNET_JSON_pack_data_auto ("wire_salt",
+                                &dcd->wire_salt),
+    GNUNET_JSON_pack_data_auto ("h_contract_terms",
+                                &dcd->h_contract_terms),
+    GNUNET_JSON_pack_array_steal ("coins",
+                                  deposits),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_data_auto ("wallet_data_hash",
+                                  wallet_data_hashp)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_steal ("policy_details",
+                                     (json_t *) dcd->policy_details)),
+    GNUNET_JSON_pack_timestamp ("timestamp",
+                                dcd->wallet_timestamp),
+    GNUNET_JSON_pack_data_auto ("merchant_pub",
+                                &dcd->merchant_pub),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_timestamp ("refund_deadline",
+                                  dcd->refund_deadline)),
+    GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+                                dcd->wire_deadline));
+  GNUNET_assert (NULL != deposit_obj);
+  eh = TALER_EXCHANGE_curl_easy_get_ (dh->url);
+  if ( (NULL == eh) ||
+       (GNUNET_OK !=
+        TALER_curl_easy_post (&dh->post_ctx,
+                              eh,
+                              deposit_obj)) )
+  {
+    *ec = TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE;
+    GNUNET_break (0);
+    if (NULL != eh)
+      curl_easy_cleanup (eh);
+    json_decref (deposit_obj);
+    GNUNET_free (dh->cdds);
+    GNUNET_free (dh->url);
+    GNUNET_free (dh);
+    return NULL;
+  }
+  json_decref (deposit_obj);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "URL for deposit: `%s'\n",
+              dh->url);
+  dh->ctx = ctx;
+  dh->keys = TALER_EXCHANGE_keys_incref (keys);
+  dh->job = GNUNET_CURL_job_add2 (ctx,
+                                  eh,
+                                  dh->post_ctx.headers,
+                                  &handle_deposit_finished,
+                                  dh);
+  return dh;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_force_dc (
+  struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+  deposit->auditor_chance = 1;
+}
+
+
+void
+TALER_EXCHANGE_batch_deposit_cancel (
+  struct TALER_EXCHANGE_BatchDepositHandle *deposit)
+{
+  struct TEAH_AuditorInteractionEntry *aie;
+
+  while (NULL != (aie = deposit->ai_head))
+  {
+    GNUNET_assert (aie->dh == deposit);
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Not sending deposit confirmation to auditor `%s' due to 
cancellation\n",
+                aie->auditor_url);
+    TALER_AUDITOR_deposit_confirmation_cancel (aie->dch);
+    GNUNET_CONTAINER_DLL_remove (deposit->ai_head,
+                                 deposit->ai_tail,
+                                 aie);
+    GNUNET_free (aie);
+  }
+  if (NULL != deposit->job)
+  {
+    GNUNET_CURL_job_cancel (deposit->job);
+    deposit->job = NULL;
+  }
+  TALER_EXCHANGE_keys_decref (deposit->keys);
+  GNUNET_free (deposit->url);
+  GNUNET_free (deposit->cdds);
+  GNUNET_free (deposit->exchange_sigs);
+  TALER_curl_easy_post_finished (&deposit->post_ctx);
+  json_decref (deposit->response);
+  GNUNET_free (deposit);
+}
+
+
+/* end of exchange_api_batch_deposit.c */
diff --git a/src/lib/exchange_api_batch_withdraw.c 
b/src/lib/exchange_api_batch_withdraw.c
new file mode 100644
index 0000000..a1fcccc
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw.c
@@ -0,0 +1,456 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_batch_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests 
with blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Data we keep per coin in the batch.
+ */
+struct CoinData
+{
+
+  /**
+   * Denomination key we are withdrawing.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey pk;
+
+  /**
+   * Master key material for the coin.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * Age commitment for the coin.
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
+  /**
+   *  blinding secret
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Private key of the coin we are withdrawing.
+   */
+  struct TALER_CoinSpendPrivateKeyP priv;
+
+  /**
+   * Details of the planchet.
+   */
+  struct TALER_PlanchetDetail pd;
+
+  /**
+   * Values of the @cipher selected
+   */
+  struct TALER_ExchangeWithdrawValues alg_values;
+
+  /**
+   * Hash of the public key of the coin we are signing.
+   */
+  struct TALER_CoinPubHashP c_hash;
+
+  /**
+   * Handler for the CS R request (only used for TALER_DENOMINATION_CS 
denominations)
+   */
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+  /**
+   * Batch withdraw this coin is part of.
+   */
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+};
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdrawHandle
+{
+
+  /**
+   * The curl context to use
+   */
+  struct GNUNET_CURL_Context *curl_ctx;
+
+  /**
+   * The base URL to the exchange
+   */
+  const char *exchange_url;
+
+  /**
+   * The /keys information from the exchange
+   */
+  const struct TALER_EXCHANGE_Keys *keys;
+
+
+  /**
+   * Handle for the actual (internal) batch withdraw operation.
+   */
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh2;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_BatchWithdrawCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reserve private key.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Array of per-coin data.
+   */
+  struct CoinData *coins;
+
+  /**
+   * Length of the @e coins array.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Number of CS requests still pending.
+   */
+  unsigned int cs_pending;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdrawHandle`
+ * @param bw2r response data
+ */
+static void
+handle_reserve_batch_withdraw_finished (
+  void *cls,
+  const struct TALER_EXCHANGE_BatchWithdraw2Response *bw2r)
+{
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cls;
+  struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+    .hr = bw2r->hr
+  };
+  struct TALER_EXCHANGE_PrivateCoinDetails coins[GNUNET_NZL (wh->num_coins)];
+
+  wh->wh2 = NULL;
+  memset (coins,
+          0,
+          sizeof (coins));
+  switch (bw2r->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      if (bw2r->details.ok.blind_sigs_length != wh->num_coins)
+      {
+        GNUNET_break_op (0);
+        wr.hr.http_status = 0;
+        wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      for (unsigned int i = 0; i<wh->num_coins; i++)
+      {
+        struct CoinData *cd = &wh->coins[i];
+        struct TALER_EXCHANGE_PrivateCoinDetails *coin = &coins[i];
+        struct TALER_FreshCoin fc;
+
+        if (GNUNET_OK !=
+            TALER_planchet_to_coin (&cd->pk.key,
+                                    &bw2r->details.ok.blind_sigs[i],
+                                    &cd->bks,
+                                    &cd->priv,
+                                    cd->ach,
+                                    &cd->c_hash,
+                                    &cd->alg_values,
+                                    &fc))
+        {
+          wr.hr.http_status = 0;
+          wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
+          break;
+        }
+        coin->coin_priv = cd->priv;
+        coin->bks = cd->bks;
+        coin->sig = fc.sig;
+        coin->exchange_vals = cd->alg_values;
+      }
+      wr.details.ok.coins = coins;
+      wr.details.ok.num_coins = wh->num_coins;
+      break;
+    }
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto (
+          "h_payto",
+          &wr.details.unavailable_for_legal_reasons.h_payto),
+        GNUNET_JSON_spec_uint64 (
+          "requirement_row",
+          &wr.details.unavailable_for_legal_reasons.requirement_row),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (bw2r->hr.reply,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        wr.hr.http_status = 0;
+        wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+    TALER_denom_sig_free (&coins[i].sig);
+  TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+/**
+ * Runs phase two, the actual withdraw operation.
+ * Started once the preparation for CS-denominations is
+ * done.
+ *
+ * @param[in,out] wh batch withdraw to start phase 2 for
+ */
+static void
+phase_two (struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+  struct TALER_PlanchetDetail pds[wh->num_coins];
+
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+
+    pds[i] = cd->pd;
+  }
+  wh->wh2 = TALER_EXCHANGE_batch_withdraw2 (
+    wh->curl_ctx,
+    wh->exchange_url,
+    wh->keys,
+    wh->reserve_priv,
+    wh->num_coins,
+    pds,
+    &handle_reserve_batch_withdraw_finished,
+    wh);
+}
+
+
+/**
+ * Function called when stage 1 of CS withdraw is finished (request r_pub's)
+ *
+ * @param cls the `struct CoinData *`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+withdraw_cs_stage_two_callback (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+  struct CoinData *cd = cls;
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh = cd->wh;
+  struct TALER_EXCHANGE_BatchWithdrawResponse wr = {
+    .hr = csrr->hr
+  };
+
+  cd->csrh = NULL;
+  GNUNET_assert (TALER_DENOMINATION_CS == cd->pk.key.cipher);
+  switch (csrr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    cd->alg_values = csrr->details.ok.alg_values;
+    TALER_planchet_setup_coin_priv (&cd->ps,
+                                    &cd->alg_values,
+                                    &cd->priv);
+    TALER_planchet_blinding_secret_create (&cd->ps,
+                                           &cd->alg_values,
+                                           &cd->bks);
+    /* This initializes the 2nd half of the
+       wh->pd.blinded_planchet! */
+    if (GNUNET_OK !=
+        TALER_planchet_prepare (&cd->pk.key,
+                                &cd->alg_values,
+                                &cd->bks,
+                                &cd->priv,
+                                cd->ach,
+                                &cd->c_hash,
+                                &cd->pd))
+    {
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw_cancel (wh);
+    }
+    wh->cs_pending--;
+    if (0 == wh->cs_pending)
+      phase_two (wh);
+    return;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  TALER_EXCHANGE_batch_withdraw_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdrawHandle *
+TALER_EXCHANGE_batch_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  unsigned int wci_length,
+  const struct TALER_EXCHANGE_WithdrawCoinInput wcis[static wci_length],
+  TALER_EXCHANGE_BatchWithdrawCallback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh;
+
+  wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdrawHandle);
+  wh->curl_ctx = curl_ctx;
+  wh->exchange_url = exchange_url;
+  wh->keys = keys;
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  wh->reserve_priv = reserve_priv;
+  wh->num_coins = wci_length;
+  wh->coins = GNUNET_new_array (wh->num_coins,
+                                struct CoinData);
+  for (unsigned int i = 0; i<wci_length; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+    const struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
+
+    cd->wh = wh;
+    cd->ps = *wci->ps;
+    cd->ach = wci->ach;
+    cd->pk = *wci->pk;
+    TALER_denom_pub_deep_copy (&cd->pk.key,
+                               &wci->pk->key);
+    switch (wci->pk->key.cipher)
+    {
+    case TALER_DENOMINATION_RSA:
+      {
+        cd->alg_values.cipher = TALER_DENOMINATION_RSA;
+        TALER_planchet_setup_coin_priv (&cd->ps,
+                                        &cd->alg_values,
+                                        &cd->priv);
+        TALER_planchet_blinding_secret_create (&cd->ps,
+                                               &cd->alg_values,
+                                               &cd->bks);
+        if (GNUNET_OK !=
+            TALER_planchet_prepare (&cd->pk.key,
+                                    &cd->alg_values,
+                                    &cd->bks,
+                                    &cd->priv,
+                                    cd->ach,
+                                    &cd->c_hash,
+                                    &cd->pd))
+        {
+          GNUNET_break (0);
+          TALER_EXCHANGE_batch_withdraw_cancel (wh);
+          return NULL;
+        }
+        break;
+      }
+    case TALER_DENOMINATION_CS:
+      {
+        TALER_cs_withdraw_nonce_derive (
+          &cd->ps,
+          &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
+        /* Note that we only initialize the first half
+           of the blinded_planchet here; the other part
+           will be done after the /csr-withdraw request! */
+        cd->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
+        cd->csrh = TALER_EXCHANGE_csr_withdraw (
+          curl_ctx,
+          exchange_url,
+          &cd->pk,
+          &cd->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
+          &withdraw_cs_stage_two_callback,
+          cd);
+        if (NULL == cd->csrh)
+        {
+          GNUNET_break (0);
+          TALER_EXCHANGE_batch_withdraw_cancel (wh);
+          return NULL;
+        }
+        wh->cs_pending++;
+        break;
+      }
+    default:
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw_cancel (wh);
+      return NULL;
+    }
+  }
+  if (0 == wh->cs_pending)
+    phase_two (wh);
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw_cancel (
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wh)
+{
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+  {
+    struct CoinData *cd = &wh->coins[i];
+
+    if (NULL != cd->csrh)
+    {
+      TALER_EXCHANGE_csr_withdraw_cancel (cd->csrh);
+      cd->csrh = NULL;
+    }
+    TALER_blinded_planchet_free (&cd->pd.blinded_planchet);
+    TALER_denom_pub_free (&cd->pk.key);
+  }
+  GNUNET_free (wh->coins);
+  if (NULL != wh->wh2)
+  {
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh->wh2);
+    wh->wh2 = NULL;
+  }
+  GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_batch_withdraw2.c 
b/src/lib/exchange_api_batch_withdraw2.c
new file mode 100644
index 0000000..1f59a69
--- /dev/null
+++ b/src/lib/exchange_api_batch_withdraw2.c
@@ -0,0 +1,555 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_batch_withdraw2.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/batch-withdraw requests 
without blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A batch withdraw handle
+ */
+struct TALER_EXCHANGE_BatchWithdraw2Handle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * The /keys material from the exchange
+   */
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_BatchWithdraw2Callback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Total amount requested (value plus withdraw fee).
+   */
+  struct TALER_Amount requested_amount;
+
+  /**
+   * Public key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Number of coins expected.
+   */
+  unsigned int num_coins;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/batch-withdraw 
operation.
+ * Extract the coin's signature and return it to the caller.  The signature we
+ * get from the exchange is for the blinded value.  Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_batch_withdraw_ok (struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
+                           const json_t *json)
+{
+  struct TALER_BlindedDenominationSignature blind_sigs[wh->num_coins];
+  const json_t *ja = json_object_get (json,
+                                      "ev_sigs");
+  const json_t *j;
+  unsigned int index;
+  struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+    .hr.reply = json,
+    .hr.http_status = MHD_HTTP_OK
+  };
+
+  if ( (NULL == ja) ||
+       (! json_is_array (ja)) ||
+       (wh->num_coins != json_array_size (ja)) )
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_array_foreach (ja, index, j)
+  {
+    struct GNUNET_JSON_Specification spec[] = {
+      TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+                                         &blind_sigs[index]),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (j,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      for (unsigned int i = 0; i<index; i++)
+        TALER_blinded_denom_sig_free (&blind_sigs[i]);
+      return GNUNET_SYSERR;
+    }
+  }
+
+  /* signature is valid, return it to the application */
+  bwr.details.ok.blind_sigs = blind_sigs;
+  bwr.details.ok.blind_sigs_length = wh->num_coins;
+  wh->cb (wh->cb_cls,
+          &bwr);
+  /* make sure callback isn't called again after return */
+  wh->cb = NULL;
+  for (unsigned int i = 0; i<wh->num_coins; i++)
+    TALER_blinded_denom_sig_free (&blind_sigs[i]);
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * We got a 409 CONFLICT response for the 
/reserves/$RESERVE_PUB/batch-withdraw operation.
+ * Check the signatures on the batch withdraw transactions in the provided
+ * history and that the balances add up.  We don't do anything directly
+ * with the information, as the JSON will be returned to the application.
+ * However, our job is ensuring that the exchange followed the protocol, and
+ * this in particular means checking all of the signatures in the history.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_batch_withdraw_payment_required (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh,
+  const json_t *json)
+{
+  struct TALER_Amount balance;
+  struct TALER_Amount total_in_from_history;
+  struct TALER_Amount total_out_from_history;
+  json_t *history;
+  size_t len;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &balance),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  history = json_object_get (json,
+                             "history");
+  if (NULL == history)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* go over transaction history and compute
+     total incoming and outgoing amounts */
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+    /* Use heap allocation as "len" may be very big and thus this may
+       not fit on the stack. Use "GNUNET_malloc_large" as a malicious
+       exchange may theoretically try to crash us by giving a history
+       that does not fit into our memory. */
+    rhistory = GNUNET_malloc_large (
+      sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
+      * len);
+    if (NULL == rhistory)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_parse_reserve_history (
+          wh->keys,
+          history,
+          &wh->reserve_pub,
+          balance.currency,
+          &total_in_from_history,
+          &total_out_from_history,
+          len,
+          rhistory))
+    {
+      GNUNET_break_op (0);
+      TALER_EXCHANGE_free_reserve_history (len,
+                                           rhistory);
+      return GNUNET_SYSERR;
+    }
+    TALER_EXCHANGE_free_reserve_history (len,
+                                         rhistory);
+  }
+
+  /* Check that funds were really insufficient */
+  if (0 >= TALER_amount_cmp (&wh->requested_amount,
+                             &balance))
+  {
+    /* Requested amount is smaller or equal to reported balance,
+       so this should not have failed. */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/batch-withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_BatchWithdraw2Handle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_batch_withdraw_finished (void *cls,
+                                        long response_code,
+                                        const void *response)
+{
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_BatchWithdraw2Response bwr = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  wh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    bwr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        reserve_batch_withdraw_ok (wh,
+                                   j))
+    {
+      GNUNET_break_op (0);
+      bwr.hr.http_status = 0;
+      bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+      break;
+    }
+    GNUNET_assert (NULL == wh->cb);
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+    return;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* only validate reply is well-formed */
+    {
+      uint64_t ptu;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_uint64 ("legitimization_uuid",
+                                 &ptu),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        bwr.hr.http_status = 0;
+        bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    GNUNET_break_op (0);
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, the exchange basically just says
+       that it doesn't know this reserve.  Can happen if we
+       query before the wire transfer went through.
+       We should simply pass the JSON reply to the application. */
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* The exchange says that the reserve has insufficient funds;
+       check the signatures in the history... */
+    if (GNUNET_OK !=
+        reserve_batch_withdraw_payment_required (wh,
+                                                 j))
+    {
+      GNUNET_break_op (0);
+      bwr.hr.http_status = 0;
+      bwr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    else
+    {
+      bwr.hr.ec = TALER_JSON_get_error_code (j);
+      bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    }
+    break;
+  case MHD_HTTP_GONE:
+    /* could happen if denomination was revoked */
+    /* Note: one might want to check /keys for revocation
+       signature here, alas tricky in case our /keys
+       is outdated => left to clients */
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    bwr.hr.ec = TALER_JSON_get_error_code (j);
+    bwr.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for exchange batch withdraw\n",
+                (unsigned int) response_code,
+                (int) bwr.hr.ec);
+    break;
+  }
+  if (NULL != wh->cb)
+  {
+    wh->cb (wh->cb_cls,
+            &bwr);
+    wh->cb = NULL;
+  }
+  TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_BatchWithdraw2Handle *
+TALER_EXCHANGE_batch_withdraw2 (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  unsigned int pds_length,
+  const struct TALER_PlanchetDetail pds[static pds_length],
+  TALER_EXCHANGE_BatchWithdraw2Callback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh;
+  const struct TALER_EXCHANGE_DenomPublicKey *dk;
+  struct TALER_ReserveSignatureP reserve_sig;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  struct TALER_BlindedCoinHashP bch;
+  json_t *jc;
+
+  GNUNET_assert (NULL != keys);
+  wh = GNUNET_new (struct TALER_EXCHANGE_BatchWithdraw2Handle);
+  wh->keys = keys;
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  wh->num_coins = pds_length;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (keys->currency,
+                                        &wh->requested_amount));
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &wh->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &wh->reserve_pub,
+      sizeof (struct TALER_ReservePublicKeyP),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/batch-withdraw",
+                     pub_str);
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Attempting to batch-withdraw from reserve %s\n",
+              TALER_B2S (&wh->reserve_pub));
+  wh->url = TALER_url_join (exchange_url,
+                            arg_str,
+                            NULL);
+  if (NULL == wh->url)
+  {
+    GNUNET_break (0);
+    TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+    return NULL;
+  }
+  jc = json_array ();
+  GNUNET_assert (NULL != jc);
+  for (unsigned int i = 0; i<pds_length; i++)
+  {
+    const struct TALER_PlanchetDetail *pd = &pds[i];
+    struct TALER_Amount coin_total;
+    json_t *withdraw_obj;
+
+    dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                      &pd->denom_pub_hash);
+    if (NULL == dk)
+    {
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      GNUNET_break (0);
+      return NULL;
+    }
+    /* Compute how much we expected to charge to the reserve */
+    if (0 >
+        TALER_amount_add (&coin_total,
+                          &dk->fees.withdraw,
+                          &dk->value))
+    {
+      /* Overflow here? Very strange, our CPU must be fried... */
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    if (0 >
+        TALER_amount_add (&wh->requested_amount,
+                          &wh->requested_amount,
+                          &coin_total))
+    {
+      /* Overflow here? Very strange, our CPU must be fried... */
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    if (GNUNET_OK !=
+        TALER_coin_ev_hash (&pd->blinded_planchet,
+                            &pd->denom_pub_hash,
+                            &bch))
+    {
+      GNUNET_break (0);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      json_decref (jc);
+      return NULL;
+    }
+    TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
+                                &coin_total,
+                                &bch,
+                                reserve_priv,
+                                &reserve_sig);
+    withdraw_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                  &pd->denom_pub_hash),
+      TALER_JSON_pack_blinded_planchet ("coin_ev",
+                                        &pd->blinded_planchet),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &reserve_sig));
+    GNUNET_assert (NULL != withdraw_obj);
+    GNUNET_assert (0 ==
+                   json_array_append_new (jc,
+                                          withdraw_obj));
+  }
+  {
+    CURL *eh;
+    json_t *req;
+
+    req = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_array_steal ("planchets",
+                                    jc));
+    eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+    if ( (NULL == eh) ||
+         (GNUNET_OK !=
+          TALER_curl_easy_post (&wh->post_ctx,
+                                eh,
+                                req)) )
+    {
+      GNUNET_break (0);
+      if (NULL != eh)
+        curl_easy_cleanup (eh);
+      json_decref (req);
+      TALER_EXCHANGE_batch_withdraw2_cancel (wh);
+      return NULL;
+    }
+    json_decref (req);
+    wh->job = GNUNET_CURL_job_add2 (curl_ctx,
+                                    eh,
+                                    wh->post_ctx.headers,
+                                    &handle_reserve_batch_withdraw_finished,
+                                    wh);
+  }
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_batch_withdraw2_cancel (
+  struct TALER_EXCHANGE_BatchWithdraw2Handle *wh)
+{
+  if (NULL != wh->job)
+  {
+    GNUNET_CURL_job_cancel (wh->job);
+    wh->job = NULL;
+  }
+  GNUNET_free (wh->url);
+  TALER_curl_easy_post_finished (&wh->post_ctx);
+  GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c
new file mode 100644
index 0000000..609a2f7
--- /dev/null
+++ b/src/lib/exchange_api_common.c
@@ -0,0 +1,2448 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_common.c
+ * @brief common functions for the exchange API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+
+
+/**
+ * Context for history entry helpers.
+ */
+struct HistoryParseContext
+{
+
+  /**
+   * Keys of the exchange we use.
+   */
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * Our reserve public key.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Array of UUIDs.
+   */
+  struct GNUNET_HashCode *uuids;
+
+  /**
+   * Where to sum up total inbound amounts.
+   */
+  struct TALER_Amount *total_in;
+
+  /**
+   * Where to sum up total outbound amounts.
+   */
+  struct TALER_Amount *total_out;
+
+  /**
+   * Number of entries already used in @e uuids.
+   */
+  unsigned int uuid_off;
+};
+
+
+/**
+ * Type of a function called to parse a reserve history
+ * entry @a rh.
+ *
+ * @param[in,out] rh where to write the result
+ * @param[in,out] uc UUID context for duplicate detection
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+typedef enum GNUNET_GenericReturnValue
+(*ParseHelper)(struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+               struct HistoryParseContext *uc,
+               const json_t *transaction);
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_credit (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+              struct HistoryParseContext *uc,
+              const json_t *transaction)
+{
+  const char *wire_url;
+  uint64_t wire_reference;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct GNUNET_JSON_Specification withdraw_spec[] = {
+    GNUNET_JSON_spec_uint64 ("wire_reference",
+                             &wire_reference),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_string ("sender_account_url",
+                             &wire_url),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CREDIT;
+  if (0 >
+      TALER_amount_add (uc->total_in,
+                        uc->total_in,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         withdraw_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rh->details.in_details.sender_url = GNUNET_strdup (wire_url);
+  rh->details.in_details.wire_reference = wire_reference;
+  rh->details.in_details.timestamp = timestamp;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "credit" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_withdraw (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+                struct HistoryParseContext *uc,
+                const json_t *transaction)
+{
+  struct TALER_ReserveSignatureP sig;
+  struct TALER_DenominationHashP h_denom_pub;
+  struct TALER_BlindedCoinHashP bch;
+  struct TALER_Amount withdraw_fee;
+  struct GNUNET_JSON_Specification withdraw_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &sig),
+    TALER_JSON_spec_amount_any ("withdraw_fee",
+                                &withdraw_fee),
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 &h_denom_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_coin_envelope",
+                                 &bch),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         withdraw_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* Check that the signature is a valid withdraw request */
+  if (GNUNET_OK !=
+      TALER_wallet_withdraw_verify (&h_denom_pub,
+                                    &rh->amount,
+                                    &bch,
+                                    uc->reserve_pub,
+                                    &sig))
+  {
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (withdraw_spec);
+    return GNUNET_SYSERR;
+  }
+  /* check that withdraw fee matches expectations! */
+  {
+    const struct TALER_EXCHANGE_Keys *key_state;
+    const struct TALER_EXCHANGE_DenomPublicKey *dki;
+
+    key_state = uc->keys;
+    dki = TALER_EXCHANGE_get_denomination_key_by_hash (key_state,
+                                                       &h_denom_pub);
+    if ( (GNUNET_YES !=
+          TALER_amount_cmp_currency (&withdraw_fee,
+                                     &dki->fees.withdraw)) ||
+         (0 !=
+          TALER_amount_cmp (&withdraw_fee,
+                            &dki->fees.withdraw)) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (withdraw_spec);
+      return GNUNET_SYSERR;
+    }
+    rh->details.withdraw.fee = withdraw_fee;
+  }
+  rh->details.withdraw.out_authorization_sig
+    = json_object_get (transaction,
+                       "signature");
+  /* Check check that the same withdraw transaction
+       isn't listed twice by the exchange. We use the
+       "uuid" array to remember the hashes of all
+       signatures, and compare the hashes to find
+       duplicates. */
+  GNUNET_CRYPTO_hash (&sig,
+                      sizeof (sig),
+                      &uc->uuids[uc->uuid_off]);
+  for (unsigned int i = 0; i<uc->uuid_off; i++)
+  {
+    if (0 == GNUNET_memcmp (&uc->uuids[uc->uuid_off],
+                            &uc->uuids[i]))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (withdraw_spec);
+      return GNUNET_SYSERR;
+    }
+  }
+  uc->uuid_off++;
+
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    GNUNET_JSON_parse_free (withdraw_spec);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "recoup" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_recoup (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+              struct HistoryParseContext *uc,
+              const json_t *transaction)
+{
+  const struct TALER_EXCHANGE_Keys *key_state;
+  struct GNUNET_JSON_Specification recoup_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &rh->details.recoup_details.coin_pub),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &rh->details.recoup_details.exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &rh->details.recoup_details.exchange_pub),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &rh->details.recoup_details.timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_RECOUP;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         recoup_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  key_state = uc->keys;
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_test_signing_key (key_state,
+                                       &rh->details.
+                                       recoup_details.exchange_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_verify (
+        rh->details.recoup_details.timestamp,
+        &rh->amount,
+        &rh->details.recoup_details.coin_pub,
+        uc->reserve_pub,
+        &rh->details.recoup_details.exchange_pub,
+        &rh->details.recoup_details.exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_in,
+                        uc->total_in,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "closing" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_closing (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+               struct HistoryParseContext *uc,
+               const json_t *transaction)
+{
+  const struct TALER_EXCHANGE_Keys *key_state;
+  struct GNUNET_JSON_Specification closing_spec[] = {
+    GNUNET_JSON_spec_string (
+      "receiver_account_details",
+      &rh->details.close_details.receiver_account_details),
+    GNUNET_JSON_spec_fixed_auto ("wtid",
+                                 &rh->details.close_details.wtid),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &rh->details.close_details.exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &rh->details.close_details.exchange_pub),
+    TALER_JSON_spec_amount_any ("closing_fee",
+                                &rh->details.close_details.fee),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &rh->details.close_details.timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CLOSING;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         closing_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  key_state = uc->keys;
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_test_signing_key (
+        key_state,
+        &rh->details.close_details.exchange_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_reserve_closed_verify (
+        rh->details.close_details.timestamp,
+        &rh->amount,
+        &rh->details.close_details.fee,
+        rh->details.close_details.receiver_account_details,
+        &rh->details.close_details.wtid,
+        uc->reserve_pub,
+        &rh->details.close_details.exchange_pub,
+        &rh->details.close_details.exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "merge" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_merge (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+             struct HistoryParseContext *uc,
+             const json_t *transaction)
+{
+  uint32_t flags32;
+  struct GNUNET_JSON_Specification merge_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &rh->details.merge_details.h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merge_pub",
+                                 &rh->details.merge_details.merge_pub),
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &rh->details.merge_details.purse_pub),
+    GNUNET_JSON_spec_uint32 ("min_age",
+                             &rh->details.merge_details.min_age),
+    GNUNET_JSON_spec_uint32 ("flags",
+                             &flags32),
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.merge_details.reserve_sig),
+    TALER_JSON_spec_amount_any ("purse_fee",
+                                &rh->details.merge_details.purse_fee),
+    GNUNET_JSON_spec_timestamp ("merge_timestamp",
+                                &rh->details.merge_details.merge_timestamp),
+    GNUNET_JSON_spec_timestamp ("purse_expiration",
+                                &rh->details.merge_details.purse_expiration),
+    GNUNET_JSON_spec_bool ("merged",
+                           &rh->details.merge_details.merged),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_MERGE;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         merge_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rh->details.merge_details.flags =
+    (enum TALER_WalletAccountMergeFlags) flags32;
+  if (GNUNET_OK !=
+      TALER_wallet_account_merge_verify (
+        rh->details.merge_details.merge_timestamp,
+        &rh->details.merge_details.purse_pub,
+        rh->details.merge_details.purse_expiration,
+        &rh->details.merge_details.h_contract_terms,
+        &rh->amount,
+        &rh->details.merge_details.purse_fee,
+        rh->details.merge_details.min_age,
+        rh->details.merge_details.flags,
+        uc->reserve_pub,
+        &rh->details.merge_details.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (rh->details.merge_details.merged)
+  {
+    if (0 >
+        TALER_amount_add (uc->total_in,
+                          uc->total_in,
+                          &rh->amount))
+    {
+      /* overflow in history already!? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  else
+  {
+    if (0 >
+        TALER_amount_add (uc->total_out,
+                          uc->total_out,
+                          &rh->details.merge_details.purse_fee))
+    {
+      /* overflow in history already!? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "history" reserve history entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_history (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+               struct HistoryParseContext *uc,
+               const json_t *transaction)
+{
+  struct GNUNET_JSON_Specification history_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.history_details.reserve_sig),
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                
&rh->details.history_details.request_timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_HISTORY;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         history_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_history_verify (
+        rh->details.history_details.request_timestamp,
+        &rh->amount,
+        uc->reserve_pub,
+        &rh->details.history_details.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "open" reserve open entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_open (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+            struct HistoryParseContext *uc,
+            const json_t *transaction)
+{
+  struct GNUNET_JSON_Specification open_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.open_request.reserve_sig),
+    TALER_JSON_spec_amount_any ("open_payment",
+                                &rh->details.open_request.reserve_payment),
+    GNUNET_JSON_spec_uint32 ("requested_min_purses",
+                             &rh->details.open_request.purse_limit),
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rh->details.open_request.request_timestamp),
+    GNUNET_JSON_spec_timestamp ("requested_expiration",
+                                &rh->details.open_request.reserve_expiration),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_OPEN;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         open_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_open_verify (
+        &rh->amount,
+        rh->details.open_request.request_timestamp,
+        rh->details.open_request.reserve_expiration,
+        rh->details.open_request.purse_limit,
+        uc->reserve_pub,
+        &rh->details.open_request.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (uc->total_out,
+                        uc->total_out,
+                        &rh->amount))
+  {
+    /* overflow in history already!? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse "close" reserve close entry.
+ *
+ * @param[in,out] rh entry to parse
+ * @param uc our context
+ * @param transaction the transaction to parse
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_close (struct TALER_EXCHANGE_ReserveHistoryEntry *rh,
+             struct HistoryParseContext *uc,
+             const json_t *transaction)
+{
+  struct GNUNET_JSON_Specification close_spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &rh->details.close_request.reserve_sig),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_payto",
+                                   &rh->details.close_request.
+                                   target_account_h_payto),
+      NULL),
+    GNUNET_JSON_spec_timestamp ("request_timestamp",
+                                &rh->details.close_request.request_timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  rh->type = TALER_EXCHANGE_RTT_CLOSE;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         close_spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* force amount to invalid */
+  memset (&rh->amount,
+          0,
+          sizeof (rh->amount));
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_close_verify (
+        rh->details.close_request.request_timestamp,
+        &rh->details.close_request.target_account_h_payto,
+        uc->reserve_pub,
+        &rh->details.close_request.reserve_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_reserve_history (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *history,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *currency,
+  struct TALER_Amount *total_in,
+  struct TALER_Amount *total_out,
+  unsigned int history_length,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static history_length])
+{
+  const struct
+  {
+    const char *type;
+    ParseHelper helper;
+  } map[] = {
+    { "CREDIT", &parse_credit },
+    { "WITHDRAW", &parse_withdraw },
+    { "RECOUP", &parse_recoup },
+    { "MERGE", &parse_merge },
+    { "CLOSING", &parse_closing },
+    { "HISTORY", &parse_history },
+    { "OPEN", &parse_open },
+    { "CLOSE", &parse_close },
+    { NULL, NULL }
+  };
+  struct GNUNET_HashCode uuid[history_length];
+  struct HistoryParseContext uc = {
+    .keys = keys,
+    .reserve_pub = reserve_pub,
+    .uuids = uuid,
+    .total_in = total_in,
+    .total_out = total_out
+  };
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total_in));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total_out));
+  for (unsigned int off = 0; off<history_length; off++)
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rh = &rhistory[off];
+    json_t *transaction;
+    struct TALER_Amount amount;
+    const char *type;
+    struct GNUNET_JSON_Specification hist_spec[] = {
+      GNUNET_JSON_spec_string ("type",
+                               &type),
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
+      /* 'wire' and 'signature' are optional depending on 'type'! */
+      GNUNET_JSON_spec_end ()
+    };
+    bool found = false;
+
+    transaction = json_array_get (history,
+                                  off);
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (transaction,
+                           hist_spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      json_dumpf (transaction,
+                  stderr,
+                  JSON_INDENT (2));
+      return GNUNET_SYSERR;
+    }
+    rh->amount = amount;
+    if (GNUNET_YES !=
+        TALER_amount_cmp_currency (&amount,
+                                   total_in))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    for (unsigned int i = 0; NULL != map[i].type; i++)
+    {
+      if (0 == strcasecmp (map[i].type,
+                           type))
+      {
+        found = true;
+        if (GNUNET_OK !=
+            map[i].helper (rh,
+                           &uc,
+                           transaction))
+        {
+          GNUNET_break_op (0);
+          return GNUNET_SYSERR;
+        }
+        break;
+      }
+    }
+    if (! found)
+    {
+      /* unexpected 'type', protocol incompatibility, complain! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+void
+TALER_EXCHANGE_free_reserve_history (
+  unsigned int len,
+  struct TALER_EXCHANGE_ReserveHistoryEntry rhistory[static len])
+{
+  for (unsigned int i = 0; i<len; i++)
+  {
+    switch (rhistory[i].type)
+    {
+    case TALER_EXCHANGE_RTT_CREDIT:
+      GNUNET_free (rhistory[i].details.in_details.sender_url);
+      break;
+    case TALER_EXCHANGE_RTT_WITHDRAWAL:
+      break;
+    case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+      break;
+    case TALER_EXCHANGE_RTT_RECOUP:
+      break;
+    case TALER_EXCHANGE_RTT_CLOSING:
+      break;
+    case TALER_EXCHANGE_RTT_HISTORY:
+      break;
+    case TALER_EXCHANGE_RTT_MERGE:
+      break;
+    case TALER_EXCHANGE_RTT_OPEN:
+      break;
+    case TALER_EXCHANGE_RTT_CLOSE:
+      break;
+    }
+  }
+  GNUNET_free (rhistory);
+}
+
+
+/**
+ * Context for coin helpers.
+ */
+struct CoinHistoryParseContext
+{
+
+  /**
+   * Denomination of the coin.
+   */
+  const struct TALER_EXCHANGE_DenomPublicKey *dk;
+
+  /**
+   * Our coin public key.
+   */
+  const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+  /**
+   * Where to sum up total refunds.
+   */
+  struct TALER_Amount rtotal;
+
+  /**
+   * Total amount encountered.
+   */
+  struct TALER_Amount *total;
+
+};
+
+
+/**
+ * Signature of functions that operate on one of
+ * the coin's history entries.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+typedef enum GNUNET_GenericReturnValue
+(*CoinCheckHelper)(struct CoinHistoryParseContext *pc,
+                   const struct TALER_Amount *amount,
+                   json_t *transaction);
+
+
+/**
+ * Handle deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_deposit (struct CoinHistoryParseContext *pc,
+              const struct TALER_Amount *amount,
+              json_t *transaction)
+{
+  struct TALER_MerchantWireHashP h_wire;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  struct TALER_ExtensionPolicyHashP h_policy;
+  bool no_h_policy;
+  struct GNUNET_HashCode wallet_data_hash;
+  bool no_wallet_data_hash;
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct GNUNET_TIME_Timestamp refund_deadline = {0};
+  struct TALER_CoinSpendSignatureP sig;
+  struct TALER_AgeCommitmentHash hac;
+  bool no_hac;
+  struct TALER_Amount deposit_fee;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &h_contract_terms),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+                                   &wallet_data_hash),
+      &no_wallet_data_hash),
+    GNUNET_JSON_spec_fixed_auto ("h_wire",
+                                 &h_wire),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &hac),
+      &no_hac),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_policy",
+                                   &h_policy),
+      &no_h_policy),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &wallet_timestamp),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_timestamp ("refund_deadline",
+                                  &refund_deadline),
+      NULL),
+    TALER_JSON_spec_amount_any ("deposit_fee",
+                                &deposit_fee),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_deposit_verify (
+        amount,
+        &deposit_fee,
+        &h_wire,
+        &h_contract_terms,
+        no_wallet_data_hash ? NULL : &wallet_data_hash,
+        no_hac ? NULL : &hac,
+        no_h_policy ? NULL : &h_policy,
+        &pc->dk->h_key,
+        wallet_timestamp,
+        &merchant_pub,
+        refund_deadline,
+        pc->coin_pub,
+        &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* check that deposit fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&deposit_fee,
+                                   &pc->dk->fees.deposit)) ||
+       (0 !=
+        TALER_amount_cmp (&deposit_fee,
+                          &pc->dk->fees.deposit)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle melt entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_melt (struct CoinHistoryParseContext *pc,
+           const struct TALER_Amount *amount,
+           json_t *transaction)
+{
+  struct TALER_CoinSpendSignatureP sig;
+  struct TALER_RefreshCommitmentP rc;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+  bool no_hac;
+  struct TALER_Amount melt_fee;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("rc",
+                                 &rc),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &h_age_commitment),
+      &no_hac),
+    TALER_JSON_spec_amount_any ("melt_fee",
+                                &melt_fee),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* check that melt fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&melt_fee,
+                                   &pc->dk->fees.refresh)) ||
+       (0 !=
+        TALER_amount_cmp (&melt_fee,
+                          &pc->dk->fees.refresh)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_melt_verify (
+        amount,
+        &melt_fee,
+        &rc,
+        &pc->dk->h_key,
+        no_hac
+        ? NULL
+        : &h_age_commitment,
+        pc->coin_pub,
+        &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_refund (struct CoinHistoryParseContext *pc,
+             const struct TALER_Amount *amount,
+             json_t *transaction)
+{
+  struct TALER_PrivateContractHashP h_contract_terms;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_MerchantSignatureP sig;
+  struct TALER_Amount refund_fee;
+  struct TALER_Amount sig_amount;
+  uint64_t rtransaction_id;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("refund_fee",
+                                &refund_fee),
+    GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+                                 &sig),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_uint64 ("rtransaction_id",
+                             &rtransaction_id),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_add (&sig_amount,
+                        &refund_fee,
+                        amount))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_merchant_refund_verify (pc->coin_pub,
+                                    &h_contract_terms,
+                                    rtransaction_id,
+                                    &sig_amount,
+                                    &merchant_pub,
+                                    &sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* NOTE: theoretically, we could also check that the given
+     merchant_pub and h_contract_terms appear in the
+     history under deposits.  However, there is really no benefit
+     for the exchange to lie here, so not checking is probably OK
+     (an auditor ought to check, though). Then again, we similarly
+     had no reason to check the merchant's signature (other than a
+     well-formendess check). */
+
+  /* check that refund fee matches our expectations from /keys! */
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&refund_fee,
+                                   &pc->dk->fees.refund)) ||
+       (0 !=
+        TALER_amount_cmp (&refund_fee,
+                          &pc->dk->fees.refund)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup (struct CoinHistoryParseContext *pc,
+             const struct TALER_Amount *amount,
+             json_t *transaction)
+{
+  struct TALER_ReservePublicKeyP reserve_pub;
+  struct GNUNET_TIME_Timestamp timestamp;
+  union TALER_DenominationBlindingKeyP coin_bks;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                 &reserve_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_blind",
+                                 &coin_bks),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_verify (
+        timestamp,
+        amount,
+        pc->coin_pub,
+        &reserve_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_recoup_verify (&pc->dk->h_key,
+                                  &coin_bks,
+                                  pc->coin_pub,
+                                  &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle recoup-refresh entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_recoup_refresh (struct CoinHistoryParseContext *pc,
+                     const struct TALER_Amount *amount,
+                     json_t *transaction)
+{
+  /* This is the coin that was subjected to a recoup,
+       the value being credited to the old coin. */
+  struct TALER_CoinSpendPublicKeyP old_coin_pub;
+  union TALER_DenominationBlindingKeyP coin_bks;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+                                 &old_coin_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_blind",
+                                 &coin_bks),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_refresh_verify (
+        timestamp,
+        amount,
+        pc->coin_pub,
+        &old_coin_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_recoup_verify (&pc->dk->h_key,
+                                  &coin_bks,
+                                  pc->coin_pub,
+                                  &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle old coin recoup entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_old_coin_recoup (struct CoinHistoryParseContext *pc,
+                      const struct TALER_Amount *amount,
+                      json_t *transaction)
+{
+  /* This is the coin that was credited in a recoup,
+       the value being credited to the this coin. */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct TALER_CoinSpendPublicKeyP new_coin_pub;
+  struct GNUNET_TIME_Timestamp timestamp;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 &new_coin_pub),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &timestamp),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_confirm_recoup_refresh_verify (
+        timestamp,
+        amount,
+        &new_coin_pub,
+        pc->coin_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle purse deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_deposit (struct CoinHistoryParseContext *pc,
+                    const struct TALER_Amount *amount,
+                    json_t *transaction)
+{
+  struct TALER_PurseContractPublicKeyP purse_pub;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  const char *exchange_base_url;
+  bool refunded;
+  struct TALER_AgeCommitmentHash phac = { 0 };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &purse_pub),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &coin_sig),
+      NULL),
+    GNUNET_JSON_spec_string ("exchange_base_url",
+                             &exchange_base_url),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                   &phac),
+      NULL),
+    GNUNET_JSON_spec_bool ("refunded",
+                           &refunded),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_purse_deposit_verify (
+        exchange_base_url,
+        &purse_pub,
+        amount,
+        &pc->dk->h_key,
+        &phac,
+        pc->coin_pub,
+        &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (refunded)
+  {
+    /* We wave the deposit fee. */
+    if (0 >
+        TALER_amount_add (&pc->rtotal,
+                          &pc->rtotal,
+                          &pc->dk->fees.deposit))
+    {
+      /* overflow in refund history? inconceivable! Bad exchange! */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_YES;
+}
+
+
+/**
+ * Handle purse refund entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_purse_refund (struct CoinHistoryParseContext *pc,
+                   const struct TALER_Amount *amount,
+                   json_t *transaction)
+{
+  struct TALER_PurseContractPublicKeyP purse_pub;
+  struct TALER_Amount refund_fee;
+  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_ExchangeSignatureP exchange_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("refund_fee",
+                                &refund_fee),
+    GNUNET_JSON_spec_fixed_auto ("purse_pub",
+                                 &purse_pub),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &exchange_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_exchange_online_purse_refund_verify (
+        amount,
+        &refund_fee,
+        pc->coin_pub,
+        &purse_pub,
+        &exchange_pub,
+        &exchange_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (GNUNET_YES !=
+        TALER_amount_cmp_currency (&refund_fee,
+                                   &pc->dk->fees.refund)) ||
+       (0 !=
+        TALER_amount_cmp (&refund_fee,
+                          &pc->dk->fees.refund)) )
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_NO;
+}
+
+
+/**
+ * Handle reserve deposit entry in the coin's history.
+ *
+ * @param[in,out] pc overall context
+ * @param amount main amount of this operation
+ * @param transaction JSON details for the operation
+ * @return #GNUNET_SYSERR on error,
+ *         #GNUNET_OK to add, #GNUNET_NO to subtract
+ */
+static enum GNUNET_GenericReturnValue
+help_reserve_open_deposit (struct CoinHistoryParseContext *pc,
+                           const struct TALER_Amount *amount,
+                           json_t *transaction)
+{
+  struct TALER_ReserveSignatureP reserve_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+                                 &reserve_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 &coin_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (transaction,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_reserve_open_deposit_verify (
+        amount,
+        &reserve_sig,
+        pc->coin_pub,
+        &coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_YES;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_coin_history (
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const json_t *history,
+  struct TALER_Amount *total)
+{
+  const char *currency = dk->value.currency;
+  const struct
+  {
+    const char *type;
+    CoinCheckHelper helper;
+  } map[] = {
+    { "DEPOSIT", &help_deposit },
+    { "MELT", &help_melt },
+    { "REFUND", &help_refund },
+    { "RECOUP", &help_recoup },
+    { "RECOUP-REFRESH", &help_recoup_refresh },
+    { "OLD-COIN-RECOUP", &help_old_coin_recoup },
+    { "PURSE-DEPOSIT", &help_purse_deposit },
+    { "PURSE-REFUND", &help_purse_refund },
+    { "RESERVE-OPEN-DEPOSIT", &help_reserve_open_deposit },
+    { NULL, NULL }
+  };
+  struct CoinHistoryParseContext pc = {
+    .dk = dk,
+    .coin_pub = coin_pub,
+    .total = total
+  };
+  size_t len;
+
+  if (NULL == history)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len = json_array_size (history);
+  if (0 == len)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        total));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (currency,
+                                        &pc.rtotal));
+  for (size_t off = 0; off<len; off++)
+  {
+    enum GNUNET_GenericReturnValue add;
+    json_t *transaction;
+    struct TALER_Amount amount;
+    const char *type;
+    struct GNUNET_JSON_Specification spec_glob[] = {
+      TALER_JSON_spec_amount_any ("amount",
+                                  &amount),
+      GNUNET_JSON_spec_string ("type",
+                               &type),
+      GNUNET_JSON_spec_end ()
+    };
+
+    transaction = json_array_get (history,
+                                  off);
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (transaction,
+                           spec_glob,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    if (GNUNET_YES !=
+        TALER_amount_cmp_currency (&amount,
+                                   &pc.rtotal))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Operation of type %s with amount %s\n",
+                type,
+                TALER_amount2s (&amount));
+    add = GNUNET_SYSERR;
+    for (unsigned int i = 0; NULL != map[i].type; i++)
+    {
+      if (0 == strcasecmp (type,
+                           map[i].type))
+      {
+        add = map[i].helper (&pc,
+                             &amount,
+                             transaction);
+        break;
+      }
+    }
+    switch (add)
+    {
+    case GNUNET_SYSERR:
+      /* entry type not supported, new version on server? */
+      GNUNET_break_op (0);
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected type `%s' in response\n",
+                  type);
+      return GNUNET_SYSERR;
+    case GNUNET_YES:
+      /* This amount should be added to the total */
+      if (0 >
+          TALER_amount_add (total,
+                            total,
+                            &amount))
+      {
+        /* overflow in history already!? inconceivable! Bad exchange! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    case GNUNET_NO:
+      /* This amount should be subtracted from the total.
+
+         However, for the implementation, we first *add* up all of
+         these negative amounts, as we might get refunds before
+         deposits from a semi-evil exchange.  Then, at the end, we do
+         the subtraction by calculating "total = total - rtotal" */
+      if (0 >
+          TALER_amount_add (&pc.rtotal,
+                            &pc.rtotal,
+                            &amount))
+      {
+        /* overflow in refund history? inconceivable! Bad exchange! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    } /* end of switch(add) */
+  }
+  /* Finally, subtract 'rtotal' from total to handle the subtractions */
+  if (0 >
+      TALER_amount_subtract (total,
+                             total,
+                             &pc.rtotal))
+  {
+    /* underflow in history? inconceivable! Bad exchange! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+const struct TALER_EXCHANGE_SigningPublicKey *
+TALER_EXCHANGE_get_signing_key_info (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  for (unsigned int i = 0; i<keys->num_sign_keys; i++)
+  {
+    const struct TALER_EXCHANGE_SigningPublicKey *spk
+      = &keys->sign_keys[i];
+
+    if (0 == GNUNET_memcmp (exchange_pub,
+                            &spk->key))
+      return spk;
+  }
+  return NULL;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_create_conflict_ (
+  const struct TALER_PurseContractSignatureP *cpurse_sig,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const json_t *proof)
+{
+  struct TALER_Amount amount;
+  uint32_t min_age;
+  struct GNUNET_TIME_Timestamp purse_expiration;
+  struct TALER_PurseContractSignatureP purse_sig;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  struct TALER_PurseMergePublicKeyP merge_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("amount",
+                                &amount),
+    GNUNET_JSON_spec_uint32 ("min_age",
+                             &min_age),
+    GNUNET_JSON_spec_timestamp ("purse_expiration",
+                                &purse_expiration),
+    GNUNET_JSON_spec_fixed_auto ("purse_sig",
+                                 &purse_sig),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                 &h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merge_pub",
+                                 &merge_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_purse_create_verify (purse_expiration,
+                                        &h_contract_terms,
+                                        &merge_pub,
+                                        min_age,
+                                        &amount,
+                                        purse_pub,
+                                        &purse_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 ==
+      GNUNET_memcmp (&purse_sig,
+                     cpurse_sig))
+  {
+    /* Must be the SAME data, not a conflict! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_merge_conflict_ (
+  const struct TALER_PurseMergeSignatureP *cmerge_sig,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const char *exchange_url,
+  const json_t *proof)
+{
+  struct TALER_PurseMergeSignatureP merge_sig;
+  struct GNUNET_TIME_Timestamp merge_timestamp;
+  const char *partner_url = NULL;
+  struct TALER_ReservePublicKeyP reserve_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("partner_url",
+                               &partner_url),
+      NULL),
+    GNUNET_JSON_spec_timestamp ("merge_timestamp",
+                                &merge_timestamp),
+    GNUNET_JSON_spec_fixed_auto ("merge_sig",
+                                 &merge_sig),
+    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+                                 &reserve_pub),
+    GNUNET_JSON_spec_end ()
+  };
+  char *payto_uri;
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == partner_url)
+    partner_url = exchange_url;
+  payto_uri = TALER_reserve_make_payto (partner_url,
+                                        &reserve_pub);
+  if (GNUNET_OK !=
+      TALER_wallet_purse_merge_verify (
+        payto_uri,
+        merge_timestamp,
+        purse_pub,
+        merge_pub,
+        &merge_sig))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (payto_uri);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (payto_uri);
+  if (0 ==
+      GNUNET_memcmp (&merge_sig,
+                     cmerge_sig))
+  {
+    /* Must be the SAME data, not a conflict! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_coin_conflict_ (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const char *exchange_url,
+  const json_t *proof,
+  struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_AgeCommitmentHash *phac,
+  struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  const char *partner_url = NULL;
+  struct TALER_Amount amount;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 h_denom_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                 phac),
+    GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                 coin_sig),
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 coin_pub),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("partner_url",
+                               &partner_url),
+      NULL),
+    TALER_JSON_spec_amount_any ("amount",
+                                &amount),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (NULL == partner_url)
+    partner_url = exchange_url;
+  if (GNUNET_OK !=
+      TALER_wallet_purse_deposit_verify (
+        partner_url,
+        purse_pub,
+        &amount,
+        h_denom_pub,
+        phac,
+        coin_pub,
+        coin_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_econtract_conflict_ (
+  const struct TALER_PurseContractSignatureP *ccontract_sig,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const json_t *proof)
+{
+  struct TALER_ContractDiffiePublicP contract_pub;
+  struct TALER_PurseContractSignatureP contract_sig;
+  struct GNUNET_HashCode h_econtract;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_econtract",
+                                 &h_econtract),
+    GNUNET_JSON_spec_fixed_auto ("econtract_sig",
+                                 &contract_sig),
+    GNUNET_JSON_spec_fixed_auto ("contract_pub",
+                                 &contract_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_wallet_econtract_upload_verify2 (
+        &h_econtract,
+        &contract_pub,
+        purse_pub,
+        &contract_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 ==
+      GNUNET_memcmp (&contract_sig,
+                     ccontract_sig))
+  {
+    /* Must be the SAME data, not a conflict! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_amount_conflict_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *proof,
+  struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_Amount *remaining)
+{
+  const json_t *history;
+  struct TALER_Amount total;
+  struct TALER_DenominationHashP h_denom_pub;
+  const struct TALER_EXCHANGE_DenomPublicKey *dki;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                 coin_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 &h_denom_pub),
+    GNUNET_JSON_spec_array_const ("history",
+                                  &history),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  dki = TALER_EXCHANGE_get_denomination_key_by_hash (
+    keys,
+    &h_denom_pub);
+  if (NULL == dki)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_verify_coin_history (dki,
+                                          coin_pub,
+                                          history,
+                                          &total))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 >
+      TALER_amount_subtract (remaining,
+                             &dki->value,
+                             &total))
+  {
+    /* Strange 'proof': coin was double-spent
+       before our transaction?! */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in
+ * the history of @a proof and thus whatever transaction
+ * is authorized by @a coin_sig is a conflict with
+ * @a proof.
+ *
+ * @param proof a proof to check
+ * @param coin_sig signature that must not be in @a proof
+ * @return #GNUNET_OK if @a coin_sig is not in @a proof
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict_ (
+  const json_t *proof,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  json_t *history;
+  size_t off;
+  json_t *entry;
+
+  history = json_object_get (proof,
+                             "history");
+  if (NULL == history)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  json_array_foreach (history, off, entry)
+  {
+    struct TALER_CoinSpendSignatureP cs;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                   &cs),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (entry,
+                           spec,
+                           NULL, NULL))
+      continue; /* entry without coin signature */
+    if (0 ==
+        GNUNET_memcmp (&cs,
+                       coin_sig))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_denomination_conflict_ (
+  const json_t *proof,
+  const struct TALER_DenominationHashP *ch_denom_pub)
+{
+  struct TALER_DenominationHashP h_denom_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                 &h_denom_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (proof,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (0 ==
+      GNUNET_memcmp (ch_denom_pub,
+                     &h_denom_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_OK;
+  }
+  /* indeed, proof with different denomination key provided */
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_conflict_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *proof,
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const struct TALER_Amount *required)
+{
+  enum TALER_ErrorCode ec;
+
+  ec = TALER_JSON_get_error_code (proof);
+  switch (ec)
+  {
+  case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS:
+    {
+      struct TALER_Amount left;
+      struct TALER_CoinSpendPublicKeyP pcoin_pub;
+
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_check_coin_amount_conflict_ (
+            keys,
+            proof,
+            &pcoin_pub,
+            &left))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      if (0 !=
+          GNUNET_memcmp (&pcoin_pub,
+                         coin_pub))
+      {
+        /* conflict is for a different coin! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      if (-1 !=
+          TALER_amount_cmp (&left,
+                            required))
+      {
+        /* Balance was sufficient after all; recoup MAY have still been 
possible */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_check_coin_signature_conflict_ (
+            proof,
+            coin_sig))
+      {
+        /* Not a conflicting transaction: ours is included! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    }
+  case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:
+    {
+      struct TALER_Amount left;
+      struct TALER_CoinSpendPublicKeyP pcoin_pub;
+
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_check_coin_amount_conflict_ (
+            keys,
+            proof,
+            &pcoin_pub,
+            &left))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      if (0 !=
+          GNUNET_memcmp (&pcoin_pub,
+                         coin_pub))
+      {
+        /* conflict is for a different coin! */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_check_coin_denomination_conflict_ (
+            proof,
+            &dk->h_key))
+      {
+        /* Eh, same denomination, hence no conflict */
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+      break;
+    }
+  default:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_min_denomination_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  struct TALER_Amount *min)
+{
+  bool have_min = false;
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+  {
+    const struct TALER_EXCHANGE_DenomPublicKey *dk = &keys->denom_keys[i];
+
+    if (! have_min)
+    {
+      *min = dk->value;
+      have_min = true;
+      continue;
+    }
+    if (1 != TALER_amount_cmp (min,
+                               &dk->value))
+      continue;
+    *min = dk->value;
+  }
+  if (! have_min)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_deposit_signature_ (
+  const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+  const struct TALER_ExtensionPolicyHashP *ech,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
+  const struct TALER_EXCHANGE_DenomPublicKey *dki)
+{
+  if (GNUNET_OK !=
+      TALER_wallet_deposit_verify (&cdd->amount,
+                                   &dki->fees.deposit,
+                                   h_wire,
+                                   &dcd->h_contract_terms,
+                                   &dcd->wallet_data_hash,
+                                   &cdd->h_age_commitment,
+                                   ech,
+                                   &cdd->h_denom_pub,
+                                   dcd->wallet_timestamp,
+                                   &dcd->merchant_pub,
+                                   dcd->refund_deadline,
+                                   &cdd->coin_pub,
+                                   &cdd->coin_sig))
+  {
+    GNUNET_break_op (0);
+    TALER_LOG_WARNING ("Invalid coin signature on /deposit request!\n");
+    TALER_LOG_DEBUG ("... amount_with_fee was %s\n",
+                     TALER_amount2s (&cdd->amount));
+    TALER_LOG_DEBUG ("... deposit_fee was %s\n",
+                     TALER_amount2s (&dki->fees.deposit));
+    return GNUNET_SYSERR;
+  }
+
+  /* check coin signature */
+  {
+    struct TALER_CoinPublicInfo coin_info = {
+      .coin_pub = cdd->coin_pub,
+      .denom_pub_hash = cdd->h_denom_pub,
+      .denom_sig = cdd->denom_sig,
+      .h_age_commitment = cdd->h_age_commitment,
+    };
+
+    if (GNUNET_YES !=
+        TALER_test_coin_valid (&coin_info,
+                               &dki->key))
+    {
+      GNUNET_break_op (0);
+      TALER_LOG_WARNING ("Invalid coin passed for /deposit\n");
+      return GNUNET_SYSERR;
+    }
+  }
+
+  /* Check coin does make a contribution */
+  if (0 < TALER_amount_cmp (&dki->fees.deposit,
+                            &cdd->amount))
+  {
+    GNUNET_break_op (0);
+    TALER_LOG_WARNING ("Deposit amount smaller than fee\n");
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse account restriction in @a jrest into @a rest.
+ *
+ * @param jresta array of account restrictions in JSON
+ * @param[out] resta_len set to length of @a resta
+ * @param[out] resta account restriction array to set
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_restrictions (const json_t *jresta,
+                    unsigned int *resta_len,
+                    struct TALER_EXCHANGE_AccountRestriction **resta)
+{
+  if (! json_is_array (jresta))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  *resta_len = json_array_size (jresta);
+  if (0 == *resta_len)
+  {
+    /* no restrictions, perfectly OK */
+    *resta = NULL;
+    return GNUNET_OK;
+  }
+  *resta = GNUNET_new_array (*resta_len,
+                             struct TALER_EXCHANGE_AccountRestriction);
+  for (unsigned int i = 0; i<*resta_len; i++)
+  {
+    const json_t *jr = json_array_get (jresta,
+                                       i);
+    struct TALER_EXCHANGE_AccountRestriction *ar = &(*resta)[i];
+    const char *type = json_string_value (json_object_get (jr,
+                                                           "type"));
+
+    if (NULL == type)
+    {
+      GNUNET_break (0);
+      goto fail;
+    }
+    if (0 == strcmp (type,
+                     "deny"))
+    {
+      ar->type = TALER_EXCHANGE_AR_DENY;
+      continue;
+    }
+    if (0 == strcmp (type,
+                     "regex"))
+    {
+      const char *regex;
+      const char *hint;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_string (
+          "payto_regex",
+          &regex),
+        GNUNET_JSON_spec_string (
+          "human_hint",
+          &hint),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_json (
+            "human_hint_i18n",
+            &ar->details.regex.human_hint_i18n),
+          NULL),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (jr,
+                             spec,
+                             NULL, NULL))
+      {
+        /* bogus reply */
+        GNUNET_break_op (0);
+        goto fail;
+      }
+      ar->type = TALER_EXCHANGE_AR_REGEX;
+      ar->details.regex.posix_egrep = GNUNET_strdup (regex);
+      ar->details.regex.human_hint = GNUNET_strdup (hint);
+      continue;
+    }
+    /* unsupported type */
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+fail:
+  GNUNET_free (*resta);
+  *resta_len = 0;
+  return GNUNET_SYSERR;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_parse_accounts (
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const json_t *accounts,
+  unsigned int was_length,
+  struct TALER_EXCHANGE_WireAccount was[static was_length])
+{
+  memset (was,
+          0,
+          sizeof (struct TALER_EXCHANGE_WireAccount) * was_length);
+  GNUNET_assert (was_length ==
+                 json_array_size (accounts));
+  for (unsigned int i = 0;
+       i<was_length;
+       i++)
+  {
+    struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+    const char *payto_uri;
+    const char *conversion_url;
+    const json_t *credit_restrictions;
+    const json_t *debit_restrictions;
+    struct GNUNET_JSON_Specification spec_account[] = {
+      GNUNET_JSON_spec_string ("payto_uri",
+                               &payto_uri),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_string ("conversion_url",
+                                 &conversion_url),
+        NULL),
+      GNUNET_JSON_spec_array_const ("credit_restrictions",
+                                    &credit_restrictions),
+      GNUNET_JSON_spec_array_const ("debit_restrictions",
+                                    &debit_restrictions),
+      GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                   &wa->master_sig),
+      GNUNET_JSON_spec_end ()
+    };
+    json_t *account;
+
+    account = json_array_get (accounts,
+                              i);
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (account,
+                           spec_account,
+                           NULL, NULL))
+    {
+      /* bogus reply */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    {
+      char *err;
+
+      err = TALER_payto_validate (payto_uri);
+      if (NULL != err)
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (err);
+        return GNUNET_SYSERR;
+      }
+    }
+
+    if ( (NULL != master_pub) &&
+         (GNUNET_OK !=
+          TALER_exchange_wire_signature_check (
+            payto_uri,
+            conversion_url,
+            debit_restrictions,
+            credit_restrictions,
+            master_pub,
+            &wa->master_sig)) )
+    {
+      /* bogus reply */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    if ( (GNUNET_OK !=
+          parse_restrictions (credit_restrictions,
+                              &wa->credit_restrictions_length,
+                              &wa->credit_restrictions)) ||
+         (GNUNET_OK !=
+          parse_restrictions (debit_restrictions,
+                              &wa->debit_restrictions_length,
+                              &wa->debit_restrictions)) )
+    {
+      /* bogus reply */
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    wa->payto_uri = GNUNET_strdup (payto_uri);
+    if (NULL != conversion_url)
+      wa->conversion_url = GNUNET_strdup (conversion_url);
+  }       /* end 'for all accounts */
+  return GNUNET_OK;
+}
+
+
+/**
+ * Free array of account restrictions.
+ *
+ * @param ar_len length of @a ar
+ * @param[in] ar array to free contents of (but not @a ar itself)
+ */
+static void
+free_restrictions (unsigned int ar_len,
+                   struct TALER_EXCHANGE_AccountRestriction ar[static ar_len])
+{
+  for (unsigned int i = 0; i<ar_len; i++)
+  {
+    struct TALER_EXCHANGE_AccountRestriction *a = &ar[i];
+    switch (a->type)
+    {
+    case TALER_EXCHANGE_AR_INVALID:
+      GNUNET_break (0);
+      break;
+    case TALER_EXCHANGE_AR_DENY:
+      break;
+    case TALER_EXCHANGE_AR_REGEX:
+      GNUNET_free (ar->details.regex.posix_egrep);
+      GNUNET_free (ar->details.regex.human_hint);
+      json_decref (ar->details.regex.human_hint_i18n);
+      break;
+    }
+  }
+}
+
+
+void
+TALER_EXCHANGE_free_accounts (
+  unsigned int was_len,
+  struct TALER_EXCHANGE_WireAccount was[static was_len])
+{
+  for (unsigned int i = 0; i<was_len; i++)
+  {
+    struct TALER_EXCHANGE_WireAccount *wa = &was[i];
+
+    GNUNET_free (wa->payto_uri);
+    GNUNET_free (wa->conversion_url);
+    free_restrictions (wa->credit_restrictions_length,
+                       wa->credit_restrictions);
+    GNUNET_array_grow (wa->credit_restrictions,
+                       wa->credit_restrictions_length,
+                       0);
+    free_restrictions (wa->debit_restrictions_length,
+                       wa->debit_restrictions);
+    GNUNET_array_grow (wa->debit_restrictions,
+                       wa->debit_restrictions_length,
+                       0);
+  }
+}
+
+
+/* end of exchange_api_common.c */
diff --git a/src/lib/exchange_api_common.h b/src/lib/exchange_api_common.h
new file mode 100644
index 0000000..1b9ddce
--- /dev/null
+++ b/src/lib/exchange_api_common.h
@@ -0,0 +1,219 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2015-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_common.h
+ * @brief common functions for the exchange API
+ * @author Christian Grothoff
+ */
+#ifndef EXCHANGE_API_COMMON_H
+#define EXCHANGE_API_COMMON_H
+
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+
+
+/**
+ * Check proof of a purse creation conflict.
+ *
+ * @param cpurse_sig conflicting signature (must
+ *        not match the signature from the proof)
+ * @param purse_pub the public key (must match
+ *        the signature from the proof)
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts 
with @a cpurse_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_create_conflict_ (
+  const struct TALER_PurseContractSignatureP *cpurse_sig,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const json_t *proof);
+
+
+/**
+ * Check proof of a purse merge conflict.
+ *
+ * @param cmerge_sig conflicting signature (must
+ *        not match the signature from the proof)
+ * @param merge_pub the public key (must match
+ *        the signature from the proof)
+ * @param purse_pub the public key of the purse
+ * @param exchange_url the base URL of this exchange
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and @a merge_pub 
and conflicts with @a cmerge_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_merge_conflict_ (
+  const struct TALER_PurseMergeSignatureP *cmerge_sig,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const char *exchange_url,
+  const json_t *proof);
+
+
+/**
+ * Check @a proof that claims this coin was spend
+ * differently on the same purse already. Note that
+ * the caller must still check that @a coin_pub is
+ * in the list of coins that were used, and that
+ * @a coin_sig is different from the signature the
+ * caller used.
+ *
+ * @param purse_pub the public key of the purse
+ * @param exchange_url base URL of our exchange
+ * @param proof the proof to check
+ * @param[out] h_denom_pub hash of the coin's denomination
+ * @param[out] phac age commitment hash of the coin
+ * @param[out] coin_pub set to the conflicting coin
+ * @param[out] coin_sig set to the conflicting signature
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and showing that 
@a coin_pub was spent using @a coin_sig.
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_coin_conflict_ (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const char *exchange_url,
+  const json_t *proof,
+  struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_AgeCommitmentHash *phac,
+  struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Check proof of a contract conflict.
+ *
+ * @param ccontract_sig conflicting signature (must
+ *        not match the signature from the proof)
+ * @param purse_pub public key of the purse
+ * @param proof the proof to check
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub and conflicts 
with @a ccontract_sig
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_purse_econtract_conflict_ (
+  const struct TALER_PurseContractSignatureP *ccontract_sig,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const json_t *proof);
+
+
+/**
+ * Check proof of a coin spend value conflict.
+ *
+ * @param keys exchange /keys structure
+ * @param proof the proof to check
+ * @param[out] coin_pub set to the public key of the
+ *        coin that is claimed to have an insufficient
+ *        balance
+ * @param[out] remaining set to the remaining balance
+ *        of the coin as provided by the proof
+ * @return #GNUNET_OK if the @a proof is OK for @a purse_pub demonstrating 
that @a coin_pub has only @a remaining balance.
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_amount_conflict_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *proof,
+  struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct TALER_Amount *remaining);
+
+
+/**
+ * Verify that @a proof contains a coin history that demonstrates that @a
+ * coin_pub was previously used with a denomination key that is different from
+ * @a ch_denom_pub.  Note that the coin history MUST have been checked before
+ * using #TALER_EXCHANGE_check_coin_amount_conflict_().
+ *
+ * @param proof a proof to check
+ * @param ch_denom_pub hash of the conflicting denomination
+ * @return #GNUNET_OK if @a ch_denom_pub differs from the
+ *         denomination hash given by the history of the coin
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_denomination_conflict_ (
+  const json_t *proof,
+  const struct TALER_DenominationHashP *ch_denom_pub);
+
+
+/**
+ * Verify that @a coin_sig does NOT appear in
+ * the history of @a proof and thus whatever transaction
+ * is authorized by @a coin_sig is a conflict with
+ * @a proof.
+ *
+ * @param proof a proof to check
+ * @param coin_sig signature that must not be in @a proof
+ * @return #GNUNET_OK if @a coin_sig is not in @a proof
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_signature_conflict_ (
+  const json_t *proof,
+  const struct TALER_CoinSpendSignatureP *coin_sig);
+
+
+/**
+ * Check that the provided @a proof indeeds indicates
+ * a conflict for @a coin_pub.
+ *
+ * @param keys exchange keys
+ * @param proof provided conflict proof
+ * @param dk denomination of @a coin_pub that the client
+ *           used
+ * @param coin_pub public key of the coin
+ * @param coin_sig signature over operation that conflicted
+ * @param required balance required on the coin for the operation
+ * @return #GNUNET_OK if @a proof holds
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_check_coin_conflict_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const json_t *proof,
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig,
+  const struct TALER_Amount *required);
+
+
+/**
+ * Find the smallest denomination amount in @e keys.
+ *
+ * @param keys keys to search
+ * @param[out] min set to the smallest amount
+ * @return #GNUNET_SYSERR if there are no denominations in @a keys
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_get_min_denomination_ (
+  const struct TALER_EXCHANGE_Keys *keys,
+  struct TALER_Amount *min);
+
+
+/**
+ * Verify signature information about the deposit.
+ *
+ * @param dcd contract details
+ * @param ech hashed policy (passed to avoid recomputation)
+ * @param h_wire hashed wire details (passed to avoid recomputation)
+ * @param cdd coin-specific details
+ * @param dki denomination of the coin
+ * @return #GNUNET_OK if signatures are OK, #GNUNET_SYSERR if not
+ */
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_verify_deposit_signature_ (
+  const struct TALER_EXCHANGE_DepositContractDetail *dcd,
+  const struct TALER_ExtensionPolicyHashP *ech,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_EXCHANGE_CoinDepositDetail *cdd,
+  const struct TALER_EXCHANGE_DenomPublicKey *dki);
+
+
+#endif
diff --git a/src/lib/exchange_api_csr_withdraw.c 
b/src/lib/exchange_api_csr_withdraw.c
new file mode 100644
index 0000000..4c1d83a
--- /dev/null
+++ b/src/lib/exchange_api_csr_withdraw.c
@@ -0,0 +1,279 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_csr_withdraw.c
+ * @brief Implementation of /csr-withdraw requests (get R in exchange used for 
Clause Schnorr withdraw and refresh)
+ * @author Lucien Heuzeveldt
+ * @author Gian Demarmels
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Clause Schnorr R Handle
+ */
+struct TALER_EXCHANGE_CsRWithdrawHandle
+{
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_CsRWithdrawCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller.  The signature we
+ * get from the exchange is for the blinded value.  Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param csrh operation handle
+ * @param av reply from the exchange
+ * @param hr http response details
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+csr_ok (struct TALER_EXCHANGE_CsRWithdrawHandle *csrh,
+        const json_t *av,
+        struct TALER_EXCHANGE_HttpResponse *hr)
+{
+  struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
+    .hr = *hr,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_exchange_withdraw_values (
+      "ewv",
+      &csrr.details.ok.alg_values),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (av,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  csrh->cb (csrh->cb_cls,
+            &csrr);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the HTTP /csr request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_CsRWithdrawHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_csr_finished (void *cls,
+                     long response_code,
+                     const void *response)
+{
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_HttpResponse hr = {
+    .reply = j,
+    .http_status = (unsigned int) response_code
+  };
+  struct TALER_EXCHANGE_CsRWithdrawResponse csrr = {
+    .hr = hr
+  };
+
+  csrh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    csrr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      if (GNUNET_OK !=
+          csr_ok (csrh,
+                  response,
+                  &hr))
+      {
+        GNUNET_break_op (0);
+        csrr.hr.http_status = 0;
+        csrr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    TALER_EXCHANGE_csr_withdraw_cancel (csrh);
+    return;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    csrr.hr.ec = TALER_JSON_get_error_code (j);
+    csrr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, the exchange basically just says
+       that it doesn't know the /csr endpoint or denomination.
+       Can happen if the exchange doesn't support Clause Schnorr.
+       We should simply pass the JSON reply to the application. */
+    csrr.hr.ec = TALER_JSON_get_error_code (j);
+    csrr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_GONE:
+    /* could happen if denomination was revoked */
+    /* Note: one might want to check /keys for revocation
+       signature here, alas tricky in case our /keys
+       is outdated => left to clients */
+    csrr.hr.ec = TALER_JSON_get_error_code (j);
+    csrr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    csrr.hr.ec = TALER_JSON_get_error_code (j);
+    csrr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    csrr.hr.ec = TALER_JSON_get_error_code (j);
+    csrr.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for CS R request\n",
+                (unsigned int) response_code,
+                (int) hr.ec);
+    break;
+  }
+  csrh->cb (csrh->cb_cls,
+            &csrr);
+  csrh->cb = NULL;
+  TALER_EXCHANGE_csr_withdraw_cancel (csrh);
+}
+
+
+struct TALER_EXCHANGE_CsRWithdrawHandle *
+TALER_EXCHANGE_csr_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  const struct TALER_EXCHANGE_DenomPublicKey *pk,
+  const struct TALER_CsNonce *nonce,
+  TALER_EXCHANGE_CsRWithdrawCallback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+  if (TALER_DENOMINATION_CS != pk->key.cipher)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  csrh = GNUNET_new (struct TALER_EXCHANGE_CsRWithdrawHandle);
+  csrh->cb = res_cb;
+  csrh->cb_cls = res_cb_cls;
+  csrh->url = TALER_url_join (exchange_url,
+                              "csr-withdraw",
+                              NULL);
+  if (NULL == csrh->url)
+  {
+    GNUNET_free (csrh);
+    return NULL;
+  }
+
+  {
+    CURL *eh;
+    json_t *req;
+
+    req = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_varsize ("nonce",
+                                     nonce,
+                                     sizeof(struct TALER_CsNonce)),
+      GNUNET_JSON_pack_data_varsize ("denom_pub_hash",
+                                     &pk->h_key,
+                                     sizeof(struct TALER_DenominationHashP)));
+    GNUNET_assert (NULL != req);
+    eh = TALER_EXCHANGE_curl_easy_get_ (csrh->url);
+    if ( (NULL == eh) ||
+         (GNUNET_OK !=
+          TALER_curl_easy_post (&csrh->post_ctx,
+                                eh,
+                                req)) )
+    {
+      GNUNET_break (0);
+      if (NULL != eh)
+        curl_easy_cleanup (eh);
+      json_decref (req);
+      GNUNET_free (csrh->url);
+      GNUNET_free (csrh);
+      return NULL;
+    }
+    json_decref (req);
+    csrh->job = GNUNET_CURL_job_add2 (curl_ctx,
+                                      eh,
+                                      csrh->post_ctx.headers,
+                                      &handle_csr_finished,
+                                      csrh);
+  }
+  return csrh;
+}
+
+
+void
+TALER_EXCHANGE_csr_withdraw_cancel (struct
+                                    TALER_EXCHANGE_CsRWithdrawHandle *csrh)
+{
+  if (NULL != csrh->job)
+  {
+    GNUNET_CURL_job_cancel (csrh->job);
+    csrh->job = NULL;
+  }
+  GNUNET_free (csrh->url);
+  TALER_curl_easy_post_finished (&csrh->post_ctx);
+  GNUNET_free (csrh);
+}
diff --git a/src/lib/exchange_api_curl_defaults.c 
b/src/lib/exchange_api_curl_defaults.c
new file mode 100644
index 0000000..9627db9
--- /dev/null
+++ b/src/lib/exchange_api_curl_defaults.c
@@ -0,0 +1,62 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018, 2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_curl_defaults.c
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#include "exchange_api_curl_defaults.h"
+
+
+CURL *
+TALER_EXCHANGE_curl_easy_get_ (const char *url)
+{
+  CURL *eh;
+
+  eh = curl_easy_init ();
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_URL,
+                                   url));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_FOLLOWLOCATION,
+                                   1L));
+  /* Enable compression (using whatever curl likes), see
+     https://curl.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html  */
+  GNUNET_break (CURLE_OK ==
+                curl_easy_setopt (eh,
+                                  CURLOPT_ACCEPT_ENCODING,
+                                  ""));
+  /* limit MAXREDIRS to 5 as a simple security measure against
+     a potential infinite loop caused by a malicious target */
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_MAXREDIRS,
+                                   5L));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_TCP_FASTOPEN,
+                                   1L));
+  return eh;
+}
diff --git a/src/lib/exchange_api_curl_defaults.h 
b/src/lib/exchange_api_curl_defaults.h
new file mode 100644
index 0000000..c4ba04f
--- /dev/null
+++ b/src/lib/exchange_api_curl_defaults.h
@@ -0,0 +1,40 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/exchange_api_curl_defaults.h
+ * @brief curl easy handle defaults
+ * @author Florian Dold
+ */
+
+#ifndef _TALER_CURL_DEFAULTS_H
+#define _TALER_CURL_DEFAULTS_H
+
+
+#include <gnunet/gnunet_curl_lib.h>
+
+
+/**
+ * Get a curl handle with the right defaults
+ * for the exchange lib.  In the future, we might manage a pool of connections 
here.
+ *
+ * @param url URL to query
+ */
+CURL *
+TALER_EXCHANGE_curl_easy_get_ (const char *url);
+
+#endif /* _TALER_CURL_DEFAULTS_H */
diff --git a/src/lib/exchange_api_deposits_get.c 
b/src/lib/exchange_api_deposits_get.c
new file mode 100644
index 0000000..8b145da
--- /dev/null
+++ b/src/lib/exchange_api_deposits_get.c
@@ -0,0 +1,395 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_deposits_get.c
+ * @brief Implementation of the /deposits/ GET request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Deposit Get Handle
+ */
+struct TALER_EXCHANGE_DepositGetHandle
+{
+
+  /**
+   * The keys of the this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext ctx;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_DepositGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire;
+
+  /**
+   * Hash over the contract for which this deposit is made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /track/transaction request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_DepositGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_deposit_wtid_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_EXCHANGE_DepositGetHandle *dwh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_GetDepositResponse dr = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  dwh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("wtid",
+                                     &dr.details.ok.wtid),
+        GNUNET_JSON_spec_timestamp ("execution_time",
+                                    &dr.details.ok.execution_time),
+        TALER_JSON_spec_amount_any ("coin_contribution",
+                                    &dr.details.ok.coin_contribution),
+        GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                     &dr.details.ok.exchange_sig),
+        GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                     &dr.details.ok.exchange_pub),
+        GNUNET_JSON_spec_end ()
+      };
+      const struct TALER_EXCHANGE_Keys *key_state;
+
+      key_state = dwh->keys;
+      GNUNET_assert (NULL != key_state);
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        dr.hr.http_status = 0;
+        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_test_signing_key (key_state,
+                                           &dr.details.ok.exchange_pub))
+      {
+        GNUNET_break_op (0);
+        dr.hr.http_status = 0;
+        dr.hr.ec = 
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+        break;
+      }
+      if (GNUNET_OK !=
+          TALER_exchange_online_confirm_wire_verify (
+            &dwh->h_wire,
+            &dwh->h_contract_terms,
+            &dr.details.ok.wtid,
+            &dwh->coin_pub,
+            dr.details.ok.execution_time,
+            &dr.details.ok.coin_contribution,
+            &dr.details.ok.exchange_pub,
+            &dr.details.ok.exchange_sig))
+      {
+        GNUNET_break_op (0);
+        dr.hr.http_status = 0;
+        dr.hr.ec = 
TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
+        break;
+      }
+      dwh->cb (dwh->cb_cls,
+               &dr);
+      TALER_EXCHANGE_deposits_get_cancel (dwh);
+      return;
+    }
+  case MHD_HTTP_ACCEPTED:
+    {
+      /* Transaction known, but not executed yet */
+      bool no_legi = false;
+      uint32_t state32;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_timestamp ("execution_time",
+                                    &dr.details.accepted.execution_time),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_uint64 ("requirement_row",
+                                   &dr.details.accepted.requirement_row),
+          &no_legi),
+        GNUNET_JSON_spec_uint32 ("aml_decision",
+                                 &state32),
+        GNUNET_JSON_spec_bool ("kyc_ok",
+                               &dr.details.accepted.kyc_ok),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        dr.hr.http_status = 0;
+        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      dr.details.accepted.aml_decision
+        = (enum TALER_AmlDecisionState) state32;
+      if (no_legi)
+        dr.details.accepted.requirement_row = 0;
+      dwh->cb (dwh->cb_cls,
+               &dr);
+      TALER_EXCHANGE_deposits_get_cancel (dwh);
+      return;
+    }
+  case MHD_HTTP_BAD_REQUEST:
+    dr.hr.ec = TALER_JSON_get_error_code (j);
+    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    dr.hr.ec = TALER_JSON_get_error_code (j);
+    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    dr.hr.ec = TALER_JSON_get_error_code (j);
+    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    /* Exchange does not know about transaction;
+       we should pass the reply to the application */
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    dr.hr.ec = TALER_JSON_get_error_code (j);
+    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    break;
+  default:
+    /* unexpected response code */
+    dr.hr.ec = TALER_JSON_get_error_code (j);
+    dr.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for exchange GET deposits\n",
+                (unsigned int) response_code,
+                (int) dr.hr.ec);
+    GNUNET_break_op (0);
+    break;
+  }
+  dwh->cb (dwh->cb_cls,
+           &dr);
+  TALER_EXCHANGE_deposits_get_cancel (dwh);
+}
+
+
+struct TALER_EXCHANGE_DepositGetHandle *
+TALER_EXCHANGE_deposits_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_EXCHANGE_DepositGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_MerchantPublicKeyP merchant;
+  struct TALER_MerchantSignatureP merchant_sig;
+  struct TALER_EXCHANGE_DepositGetHandle *dwh;
+  CURL *eh;
+  char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
+                + sizeof (struct TALER_MerchantWireHashP)
+                + sizeof (struct TALER_MerchantPublicKeyP)
+                + sizeof (struct TALER_PrivateContractHashP)
+                + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
+
+  GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
+                                      &merchant.eddsa_pub);
+  TALER_merchant_deposit_sign (h_contract_terms,
+                               h_wire,
+                               coin_pub,
+                               merchant_priv,
+                               &merchant_sig);
+  {
+    char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
+    char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
+    char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2];
+    char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2];
+    char timeout_str[24];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (h_wire,
+                                         sizeof (*h_wire),
+                                         whash_str,
+                                         sizeof (whash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&merchant,
+                                         sizeof (merchant),
+                                         mpub_str,
+                                         sizeof (mpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (h_contract_terms,
+                                         sizeof (*h_contract_terms),
+                                         chash_str,
+                                         sizeof (chash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (*coin_pub),
+                                         cpub_str,
+                                         sizeof (cpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&merchant_sig,
+                                         sizeof (merchant_sig),
+                                         msig_str,
+                                         sizeof (msig_str));
+    *end = '\0';
+    if (GNUNET_TIME_relative_is_zero (timeout))
+    {
+      timeout_str[0] = '\0';
+    }
+    else
+    {
+      GNUNET_snprintf (
+        timeout_str,
+        sizeof (timeout_str),
+        "%llu",
+        (unsigned long long) (
+          timeout.rel_value_us
+          / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
+    }
+
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "deposits/%s/%s/%s/%s?merchant_sig=%s%s%s",
+                     whash_str,
+                     mpub_str,
+                     chash_str,
+                     cpub_str,
+                     msig_str,
+                     GNUNET_TIME_relative_is_zero (timeout)
+                     ? ""
+                     : "&timeout_ms=",
+                     timeout_str);
+  }
+
+  dwh = GNUNET_new (struct TALER_EXCHANGE_DepositGetHandle);
+  dwh->cb = cb;
+  dwh->cb_cls = cb_cls;
+  dwh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == dwh->url)
+  {
+    GNUNET_free (dwh);
+    return NULL;
+  }
+  dwh->h_wire = *h_wire;
+  dwh->h_contract_terms = *h_contract_terms;
+  dwh->coin_pub = *coin_pub;
+  eh = TALER_EXCHANGE_curl_easy_get_ (dwh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (dwh->url);
+    GNUNET_free (dwh);
+    return NULL;
+  }
+  dwh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  &handle_deposit_wtid_finished,
+                                  dwh);
+  dwh->keys = TALER_EXCHANGE_keys_incref (keys);
+  return dwh;
+}
+
+
+void
+TALER_EXCHANGE_deposits_get_cancel (struct TALER_EXCHANGE_DepositGetHandle 
*dwh)
+{
+  if (NULL != dwh->job)
+  {
+    GNUNET_CURL_job_cancel (dwh->job);
+    dwh->job = NULL;
+  }
+  GNUNET_free (dwh->url);
+  TALER_curl_easy_post_finished (&dwh->ctx);
+  TALER_EXCHANGE_keys_decref (dwh->keys);
+  GNUNET_free (dwh);
+}
+
+
+/* end of exchange_api_deposits_get.c */
diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c
new file mode 100644
index 0000000..5562936
--- /dev/null
+++ b/src/lib/exchange_api_handle.c
@@ -0,0 +1,2316 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published
+  by the Free Software Foundation; either version 3, or (at your
+  option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file lib/exchange_api_handle.c
+ * @brief Implementation of the "handle" component of the exchange's HTTP API
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_json_lib.h"
+#include "taler_exchange_service.h"
+#include "taler_auditor_service.h"
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "exchange_api_handle.h"
+#include "exchange_api_curl_defaults.h"
+#include "backoff.h"
+#include "taler_curl_lib.h"
+
+/**
+ * Which version of the Taler protocol is implemented
+ * by this library?  Used to determine compatibility.
+ */
+#define EXCHANGE_PROTOCOL_CURRENT 17
+
+/**
+ * How many versions are we backwards compatible with?
+ */
+#define EXCHANGE_PROTOCOL_AGE 0
+
+/**
+ * Set to 1 for extra debug logging.
+ */
+#define DEBUG 0
+
+/**
+ * Current version for (local) JSON serialization of persisted
+ * /keys data.
+ */
+#define EXCHANGE_SERIALIZATION_FORMAT_VERSION 0
+
+/**
+ * How far off do we allow key liftimes to be?
+ */
+#define LIFETIME_TOLERANCE GNUNET_TIME_UNIT_HOURS
+
+/**
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
+ */
+#define DEFAULT_EXPIRATION GNUNET_TIME_UNIT_HOURS
+
+/**
+ * If the "Expire" cache control header is missing, for
+ * how long do we assume the reply to be valid at least?
+ */
+#define MINIMUM_EXPIRATION GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MINUTES, 2)
+
+
+/**
+ * Handle for a GET /keys request.
+ */
+struct TALER_EXCHANGE_GetKeysHandle
+{
+
+  /**
+   * The exchange base URL (i.e. "http://exchange.taler.net/";)
+   */
+  char *exchange_url;
+
+  /**
+   * The url for the /keys request.
+   */
+  char *url;
+
+  /**
+   * Previous /keys response, NULL for none.
+   */
+  struct TALER_EXCHANGE_Keys *prev_keys;
+
+  /**
+   * Entry for this request with the `struct GNUNET_CURL_Context`.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Expiration time according to "Expire:" header.
+   * 0 if not provided by the server.
+   */
+  struct GNUNET_TIME_Timestamp expire;
+
+  /**
+   * Function to call with the exchange's certification data,
+   * NULL if this has already been done.
+   */
+  TALER_EXCHANGE_GetKeysCallback cert_cb;
+
+  /**
+   * Closure to pass to @e cert_cb.
+   */
+  void *cert_cb_cls;
+
+};
+
+
+/**
+ * Frees @a wfm array.
+ *
+ * @param wfm fee array to release
+ * @param wfm_len length of the @a wfm array
+ */
+static void
+free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm,
+           unsigned int wfm_len)
+{
+  for (unsigned int i = 0; i<wfm_len; i++)
+  {
+    struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i];
+
+    while (NULL != wfmi->fees_head)
+    {
+      struct TALER_EXCHANGE_WireAggregateFees *fe
+        = wfmi->fees_head;
+
+      wfmi->fees_head = fe->next;
+      GNUNET_free (fe);
+    }
+    GNUNET_free (wfmi->method);
+  }
+  GNUNET_free (wfm);
+}
+
+
+/**
+ * Parse wire @a fees and return array.
+ *
+ * @param master_pub master public key to use to check signatures
+ * @param currency currency amounts are expected in
+ * @param fees json AggregateTransferFee to parse
+ * @param[out] fees_len set to length of returned array
+ * @return NULL on error
+ */
+static struct TALER_EXCHANGE_WireFeesByMethod *
+parse_fees (const struct TALER_MasterPublicKeyP *master_pub,
+            const char *currency,
+            const json_t *fees,
+            unsigned int *fees_len)
+{
+  struct TALER_EXCHANGE_WireFeesByMethod *fbm;
+  unsigned int fbml = json_object_size (fees);
+  unsigned int i = 0;
+  const char *key;
+  const json_t *fee_array;
+
+  fbm = GNUNET_new_array (fbml,
+                          struct TALER_EXCHANGE_WireFeesByMethod);
+  *fees_len = fbml;
+  json_object_foreach ((json_t *) fees, key, fee_array) {
+    struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++];
+    unsigned int idx;
+    json_t *fee;
+
+    fe->method = GNUNET_strdup (key);
+    fe->fees_head = NULL;
+    json_array_foreach (fee_array, idx, fee)
+    {
+      struct TALER_EXCHANGE_WireAggregateFees *wa
+        = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("sig",
+                                     &wa->master_sig),
+        TALER_JSON_spec_amount ("wire_fee",
+                                currency,
+                                &wa->fees.wire),
+        TALER_JSON_spec_amount ("closing_fee",
+                                currency,
+                                &wa->fees.closing),
+        GNUNET_JSON_spec_timestamp ("start_date",
+                                    &wa->start_date),
+        GNUNET_JSON_spec_timestamp ("end_date",
+                                    &wa->end_date),
+        GNUNET_JSON_spec_end ()
+      };
+
+      wa->next = fe->fees_head;
+      fe->fees_head = wa;
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (fee,
+                             spec,
+                             NULL,
+                             NULL))
+      {
+        GNUNET_break_op (0);
+        free_fees (fbm,
+                   i);
+        return NULL;
+      }
+      if (GNUNET_OK !=
+          TALER_exchange_offline_wire_fee_verify (
+            key,
+            wa->start_date,
+            wa->end_date,
+            &wa->fees,
+            master_pub,
+            &wa->master_sig))
+      {
+        GNUNET_break_op (0);
+        free_fees (fbm,
+                   i);
+        return NULL;
+      }
+    } /* for all fees over time */
+  } /* for all methods */
+  GNUNET_assert (i == fbml);
+  return fbm;
+}
+
+
+void
+TEAH_get_auditors_for_dc (
+  struct TALER_EXCHANGE_Keys *keys,
+  TEAH_AuditorCallback ac,
+  void *ac_cls)
+{
+  if (0 == keys->num_auditors)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "No auditor available. Not submitting deposit 
confirmations.\n");
+    return;
+  }
+  for (unsigned int i = 0; i<keys->num_auditors; i++)
+  {
+    const struct TALER_EXCHANGE_AuditorInformation *auditor
+      = &keys->auditors[i];
+
+    ac (ac_cls,
+        auditor->auditor_url,
+        &auditor->auditor_pub);
+  }
+}
+
+
+#define EXITIF(cond)                                              \
+  do {                                                            \
+    if (cond) { GNUNET_break (0); goto EXITIF_exit; }             \
+  } while (0)
+
+
+/**
+ * Parse a exchange's signing key encoded in JSON.
+ *
+ * @param[out] sign_key where to return the result
+ * @param check_sigs should we check signatures?
+ * @param sign_key_obj json to parse
+ * @param master_key master key to use to verify signature
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ *        invalid or the @a sign_key_obj is malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_json_signkey (struct TALER_EXCHANGE_SigningPublicKey *sign_key,
+                    bool check_sigs,
+                    const json_t *sign_key_obj,
+                    const struct TALER_MasterPublicKeyP *master_key)
+{
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &sign_key->master_sig),
+    GNUNET_JSON_spec_fixed_auto ("key",
+                                 &sign_key->key),
+    GNUNET_JSON_spec_timestamp ("stamp_start",
+                                &sign_key->valid_from),
+    GNUNET_JSON_spec_timestamp ("stamp_expire",
+                                &sign_key->valid_until),
+    GNUNET_JSON_spec_timestamp ("stamp_end",
+                                &sign_key->valid_legal),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (sign_key_obj,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (! check_sigs)
+    return GNUNET_OK;
+  if (GNUNET_OK !=
+      TALER_exchange_offline_signkey_validity_verify (
+        &sign_key->key,
+        sign_key->valid_from,
+        sign_key->valid_until,
+        sign_key->valid_legal,
+        master_key,
+        &sign_key->master_sig))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse a exchange's denomination key encoded in JSON partially.
+ *
+ * Only the values for master_sig, timestamps and the cipher-specific public
+ * key are parsed.  All other fields (fees, age_mask, value) MUST have been set
+ * prior to calling this function, otherwise the signature verification
+ * performed within this function will fail.
+ *
+ * @param[out] denom_key where to return the result
+ * @param cipher cipher type to parse
+ * @param check_sigs should we check signatures?
+ * @param denom_key_obj json to parse
+ * @param master_key master key to use to verify signature
+ * @param[in,out] hash_xor where to accumulate data for signature verification 
via XOR
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ *        invalid or the json malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_json_denomkey_partially (
+  struct TALER_EXCHANGE_DenomPublicKey *denom_key,
+  enum TALER_DenominationCipher cipher,
+  bool check_sigs,
+  const json_t *denom_key_obj,
+  struct TALER_MasterPublicKeyP *master_key,
+  struct GNUNET_HashCode *hash_xor)
+{
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &denom_key->master_sig),
+    GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
+                                &denom_key->expire_deposit),
+    GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
+                                &denom_key->withdraw_valid_until),
+    GNUNET_JSON_spec_timestamp ("stamp_start",
+                                &denom_key->valid_from),
+    GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
+                                &denom_key->expire_legal),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_bool ("lost",
+                             &denom_key->lost),
+      NULL),
+    TALER_JSON_spec_denom_pub_cipher (NULL,
+                                      cipher,
+                                      &denom_key->key),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (denom_key_obj,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  TALER_denom_pub_hash (&denom_key->key,
+                        &denom_key->h_key);
+  if (NULL != hash_xor)
+    GNUNET_CRYPTO_hash_xor (&denom_key->h_key.hash,
+                            hash_xor,
+                            hash_xor);
+  if (! check_sigs)
+    return GNUNET_OK;
+  EXITIF (GNUNET_SYSERR ==
+          TALER_exchange_offline_denom_validity_verify (
+            &denom_key->h_key,
+            denom_key->valid_from,
+            denom_key->withdraw_valid_until,
+            denom_key->expire_deposit,
+            denom_key->expire_legal,
+            &denom_key->value,
+            &denom_key->fees,
+            master_key,
+            &denom_key->master_sig));
+  return GNUNET_OK;
+EXITIF_exit:
+  /* invalidate denom_key, just to be sure */
+  memset (denom_key,
+          0,
+          sizeof (*denom_key));
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Parse a exchange's auditor information encoded in JSON.
+ *
+ * @param[out] auditor where to return the result
+ * @param check_sigs should we check signatures
+ * @param auditor_obj json to parse
+ * @param key_data information about denomination keys
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ *        invalid or the json malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_json_auditor (struct TALER_EXCHANGE_AuditorInformation *auditor,
+                    bool check_sigs,
+                    const json_t *auditor_obj,
+                    const struct TALER_EXCHANGE_Keys *key_data)
+{
+  const json_t *keys;
+  json_t *key;
+  unsigned int off;
+  unsigned int pos;
+  const char *auditor_url;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+                                 &auditor->auditor_pub),
+    GNUNET_JSON_spec_string ("auditor_url",
+                             &auditor_url),
+    GNUNET_JSON_spec_array_const ("denomination_keys",
+                                  &keys),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (auditor_obj,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+#if DEBUG
+    json_dumpf (auditor_obj,
+                stderr,
+                JSON_INDENT (2));
+#endif
+    return GNUNET_SYSERR;
+  }
+  auditor->auditor_url = GNUNET_strdup (auditor_url);
+  auditor->denom_keys
+    = GNUNET_new_array (json_array_size (keys),
+                        struct TALER_EXCHANGE_AuditorDenominationInfo);
+  pos = 0;
+  json_array_foreach (keys, off, key) {
+    struct TALER_AuditorSignatureP auditor_sig;
+    struct TALER_DenominationHashP denom_h;
+    const struct TALER_EXCHANGE_DenomPublicKey *dk = NULL;
+    unsigned int dk_off = UINT_MAX;
+    struct GNUNET_JSON_Specification kspec[] = {
+      GNUNET_JSON_spec_fixed_auto ("auditor_sig",
+                                   &auditor_sig),
+      GNUNET_JSON_spec_fixed_auto ("denom_pub_h",
+                                   &denom_h),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (key,
+                           kspec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      continue;
+    }
+    for (unsigned int j = 0; j<key_data->num_denom_keys; j++)
+    {
+      if (0 == GNUNET_memcmp (&denom_h,
+                              &key_data->denom_keys[j].h_key))
+      {
+        dk = &key_data->denom_keys[j];
+        dk_off = j;
+        break;
+      }
+    }
+    if (NULL == dk)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Auditor signed denomination %s, which we do not know. 
Ignoring signature.\n",
+                  GNUNET_h2s (&denom_h.hash));
+      continue;
+    }
+    if (check_sigs)
+    {
+      if (GNUNET_OK !=
+          TALER_auditor_denom_validity_verify (
+            auditor_url,
+            &dk->h_key,
+            &key_data->master_pub,
+            dk->valid_from,
+            dk->withdraw_valid_until,
+            dk->expire_deposit,
+            dk->expire_legal,
+            &dk->value,
+            &dk->fees,
+            &auditor->auditor_pub,
+            &auditor_sig))
+      {
+        GNUNET_break_op (0);
+        return GNUNET_SYSERR;
+      }
+    }
+    auditor->denom_keys[pos].denom_key_offset = dk_off;
+    auditor->denom_keys[pos].auditor_sig = auditor_sig;
+    pos++;
+  }
+  auditor->num_denom_keys = pos;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Parse a exchange's global fee information encoded in JSON.
+ *
+ * @param[out] gf where to return the result
+ * @param check_sigs should we check signatures
+ * @param fee_obj json to parse
+ * @param key_data already parsed information about the exchange
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is
+ *        invalid or the json malformed.
+ */
+static enum GNUNET_GenericReturnValue
+parse_global_fee (struct TALER_EXCHANGE_GlobalFee *gf,
+                  bool check_sigs,
+                  const json_t *fee_obj,
+                  const struct TALER_EXCHANGE_Keys *key_data)
+{
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_timestamp ("start_date",
+                                &gf->start_date),
+    GNUNET_JSON_spec_timestamp ("end_date",
+                                &gf->end_date),
+    GNUNET_JSON_spec_relative_time ("purse_timeout",
+                                    &gf->purse_timeout),
+    GNUNET_JSON_spec_relative_time ("history_expiration",
+                                    &gf->history_expiration),
+    GNUNET_JSON_spec_uint32 ("purse_account_limit",
+                             &gf->purse_account_limit),
+    TALER_JSON_SPEC_GLOBAL_FEES (key_data->currency,
+                                 &gf->fees),
+    GNUNET_JSON_spec_fixed_auto ("master_sig",
+                                 &gf->master_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (fee_obj,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+#if DEBUG
+    json_dumpf (fee_obj,
+                stderr,
+                JSON_INDENT (2));
+#endif
+    return GNUNET_SYSERR;
+  }
+  if (check_sigs)
+  {
+    if (GNUNET_OK !=
+        TALER_exchange_offline_global_fee_verify (
+          gf->start_date,
+          gf->end_date,
+          &gf->fees,
+          gf->purse_timeout,
+          gf->history_expiration,
+          gf->purse_account_limit,
+          &key_data->master_pub,
+          &gf->master_sig))
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+  }
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Compare two denomination keys.  Ignores revocation data.
+ *
+ * @param denom1 first denomination key
+ * @param denom2 second denomination key
+ * @return 0 if the two keys are equal (not necessarily
+ *  the same object), 1 otherwise.
+ */
+static unsigned int
+denoms_cmp (const struct TALER_EXCHANGE_DenomPublicKey *denom1,
+            const struct TALER_EXCHANGE_DenomPublicKey *denom2)
+{
+  struct TALER_EXCHANGE_DenomPublicKey tmp1;
+  struct TALER_EXCHANGE_DenomPublicKey tmp2;
+
+  if (0 !=
+      TALER_denom_pub_cmp (&denom1->key,
+                           &denom2->key))
+    return 1;
+  tmp1 = *denom1;
+  tmp2 = *denom2;
+  tmp1.revoked = false;
+  tmp2.revoked = false;
+  memset (&tmp1.key,
+          0,
+          sizeof (tmp1.key));
+  memset (&tmp2.key,
+          0,
+          sizeof (tmp2.key));
+  return GNUNET_memcmp (&tmp1,
+                        &tmp2);
+}
+
+
+/**
+ * Decode the JSON in @a resp_obj from the /keys response
+ * and store the data in the @a key_data.
+ *
+ * @param[in] resp_obj JSON object to parse
+ * @param check_sig true if we should check the signature
+ * @param[out] key_data where to store the results we decoded
+ * @param[out] vc where to store version compatibility data
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
+ * (malformed JSON)
+ */
+static enum GNUNET_GenericReturnValue
+decode_keys_json (const json_t *resp_obj,
+                  bool check_sig,
+                  struct TALER_EXCHANGE_Keys *key_data,
+                  enum TALER_EXCHANGE_VersionCompatibility *vc)
+{
+  struct TALER_ExchangeSignatureP denominations_sig;
+  struct GNUNET_HashCode hash_xor = {0};
+  struct TALER_ExchangePublicKeyP pub;
+  const json_t *wblwk = NULL;
+  const json_t *global_fees;
+  const json_t *sign_keys_array;
+  const json_t *denominations_by_group;
+  const json_t *auditors_array;
+  const json_t *recoup_array = NULL;
+  const json_t *manifests = NULL;
+  bool no_extensions = false;
+  bool no_signature = false;
+  const json_t *accounts;
+  const json_t *fees;
+  const json_t *wads;
+
+  if (JSON_OBJECT != json_typeof (resp_obj))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+#if DEBUG
+  json_dumpf (resp_obj,
+              stderr,
+              JSON_INDENT (2));
+#endif
+  /* check the version first */
+  {
+    const char *ver;
+    unsigned int age;
+    unsigned int revision;
+    unsigned int current;
+    char dummy;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_string ("version",
+                               &ver),
+      GNUNET_JSON_spec_end ()
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (resp_obj,
+                           spec,
+                           NULL, NULL))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    if (3 != sscanf (ver,
+                     "%u:%u:%u%c",
+                     &current,
+                     &revision,
+                     &age,
+                     &dummy))
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    *vc = TALER_EXCHANGE_VC_MATCH;
+    if (EXCHANGE_PROTOCOL_CURRENT < current)
+    {
+      *vc |= TALER_EXCHANGE_VC_NEWER;
+      if (EXCHANGE_PROTOCOL_CURRENT < current - age)
+        *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
+    }
+    if (EXCHANGE_PROTOCOL_CURRENT > current)
+    {
+      *vc |= TALER_EXCHANGE_VC_OLDER;
+      if (EXCHANGE_PROTOCOL_CURRENT - EXCHANGE_PROTOCOL_AGE > current)
+        *vc |= TALER_EXCHANGE_VC_INCOMPATIBLE;
+    }
+    key_data->version = GNUNET_strdup (ver);
+  }
+
+  {
+    const char *currency;
+    const char *asset_type;
+    struct GNUNET_JSON_Specification mspec[] = {
+      GNUNET_JSON_spec_fixed_auto (
+        "denominations_sig",
+        &denominations_sig),
+      GNUNET_JSON_spec_fixed_auto (
+        "eddsa_pub",
+        &pub),
+      GNUNET_JSON_spec_fixed_auto (
+        "master_public_key",
+        &key_data->master_pub),
+      GNUNET_JSON_spec_array_const ("accounts",
+                                    &accounts),
+      GNUNET_JSON_spec_object_const ("wire_fees",
+                                     &fees),
+      GNUNET_JSON_spec_array_const ("wads",
+                                    &wads),
+      GNUNET_JSON_spec_timestamp (
+        "list_issue_date",
+        &key_data->list_issue_date),
+      GNUNET_JSON_spec_relative_time (
+        "reserve_closing_delay",
+        &key_data->reserve_closing_delay),
+      GNUNET_JSON_spec_string (
+        "currency",
+        &currency),
+      GNUNET_JSON_spec_uint32 (
+        "currency_fraction_digits",
+        &key_data->currency_fraction_digits),
+      GNUNET_JSON_spec_string (
+        "asset_type",
+        &asset_type),
+      GNUNET_JSON_spec_array_const (
+        "global_fees",
+        &global_fees),
+      GNUNET_JSON_spec_array_const (
+        "signkeys",
+        &sign_keys_array),
+      GNUNET_JSON_spec_array_const (
+        "denominations",
+        &denominations_by_group),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_array_const (
+          "recoup",
+          &recoup_array),
+        NULL),
+      GNUNET_JSON_spec_array_const (
+        "auditors",
+        &auditors_array),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_bool (
+          "rewards_allowed",
+          &key_data->rewards_allowed),
+        NULL),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_object_const ("extensions",
+                                       &manifests),
+        &no_extensions),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_fixed_auto (
+          "extensions_sig",
+          &key_data->extensions_sig),
+        &no_signature),
+      GNUNET_JSON_spec_mark_optional (
+        GNUNET_JSON_spec_array_const (
+          "wallet_balance_limit_without_kyc",
+          &wblwk),
+        NULL),
+      GNUNET_JSON_spec_end ()
+    };
+    const char *emsg;
+    unsigned int eline;
+
+    if (GNUNET_OK !=
+        GNUNET_JSON_parse (resp_obj,
+                           (check_sig) ? mspec : &mspec[2],
+                           &emsg,
+                           &eline))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Parsing /keys failed for `%s' (%u)\n",
+                  emsg,
+                  eline);
+      EXITIF (1);
+    }
+    {
+      struct GNUNET_JSON_Specification sspec[] = {
+        TALER_JSON_spec_amount (
+          "stefan_abs",
+          currency,
+          &key_data->stefan_abs),
+        TALER_JSON_spec_amount (
+          "stefan_log",
+          currency,
+          &key_data->stefan_log),
+        TALER_JSON_spec_amount (
+          "stefan_lin",
+          currency,
+          &key_data->stefan_lin),
+        GNUNET_JSON_spec_end ()
+      };
+
+      EXITIF (GNUNET_OK !=
+              GNUNET_JSON_parse (resp_obj,
+                                 sspec,
+                                 NULL, NULL));
+    }
+
+    key_data->currency = GNUNET_strdup (currency);
+    key_data->asset_type = GNUNET_strdup (asset_type);
+    if (! no_extensions)
+      key_data->extensions = json_incref ((json_t *) manifests);
+  }
+
+  /* parse the global fees */
+  key_data->num_global_fees
+    = json_array_size (global_fees);
+  if (0 != key_data->num_global_fees)
+  {
+    json_t *global_fee;
+    unsigned int index;
+
+    key_data->global_fees
+      = GNUNET_new_array (key_data->num_global_fees,
+                          struct TALER_EXCHANGE_GlobalFee);
+    json_array_foreach (global_fees, index, global_fee)
+    {
+      EXITIF (GNUNET_SYSERR ==
+              parse_global_fee (&key_data->global_fees[index],
+                                check_sig,
+                                global_fee,
+                                key_data));
+    }
+  }
+
+  /* parse the signing keys */
+  key_data->num_sign_keys
+    = json_array_size (sign_keys_array);
+  if (0 != key_data->num_sign_keys)
+  {
+    json_t *sign_key_obj;
+    unsigned int index;
+
+    key_data->sign_keys
+      = GNUNET_new_array (key_data->num_sign_keys,
+                          struct TALER_EXCHANGE_SigningPublicKey);
+    json_array_foreach (sign_keys_array, index, sign_key_obj) {
+      EXITIF (GNUNET_SYSERR ==
+              parse_json_signkey (&key_data->sign_keys[index],
+                                  check_sig,
+                                  sign_key_obj,
+                                  &key_data->master_pub));
+    }
+  }
+
+  /* Parse balance limits */
+  if (NULL != wblwk)
+  {
+    key_data->wblwk_length = json_array_size (wblwk);
+    key_data->wallet_balance_limit_without_kyc
+      = GNUNET_new_array (key_data->wblwk_length,
+                          struct TALER_Amount);
+    for (unsigned int i = 0; i<key_data->wblwk_length; i++)
+    {
+      struct TALER_Amount *a = &key_data->wallet_balance_limit_without_kyc[i];
+      const json_t *aj = json_array_get (wblwk,
+                                         i);
+      struct GNUNET_JSON_Specification spec[] = {
+        TALER_JSON_spec_amount (NULL,
+                                key_data->currency,
+                                a),
+        GNUNET_JSON_spec_end ()
+      };
+
+      EXITIF (GNUNET_OK !=
+              GNUNET_JSON_parse (aj,
+                                 spec,
+                                 NULL, NULL));
+    }
+  }
+
+  /* Parse wire accounts */
+  key_data->fees = parse_fees (&key_data->master_pub,
+                               key_data->currency,
+                               fees,
+                               &key_data->fees_len);
+  EXITIF (NULL == key_data->fees);
+  /* parse accounts */
+  GNUNET_array_grow (key_data->accounts,
+                     key_data->accounts_len,
+                     json_array_size (accounts));
+  EXITIF (GNUNET_OK !=
+          TALER_EXCHANGE_parse_accounts (&key_data->master_pub,
+                                         accounts,
+                                         key_data->accounts_len,
+                                         key_data->accounts));
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Parsed %u wire accounts from JSON\n",
+              (unsigned int) json_array_size (accounts));
+
+
+  /* Parse the supported extension(s): age-restriction. */
+  /* TODO: maybe lift all this into a FP in TALER_Extension ? */
+  if (! no_extensions)
+  {
+    if (no_signature)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "found extensions without signature\n");
+    }
+    else
+    {
+      /* We have an extensions object. Verify its signature. */
+      EXITIF (GNUNET_OK !=
+              TALER_extensions_verify_manifests_signature (
+                manifests,
+                &key_data->extensions_sig,
+                &key_data->master_pub));
+
+      /* Parse and set the the configuration of the extensions accordingly */
+      EXITIF (GNUNET_OK !=
+              TALER_extensions_load_manifests (manifests));
+    }
+
+    /* Assuming we might have now a new value for age_mask, set it in key_data 
*/
+    key_data->age_mask = TALER_extensions_get_age_restriction_mask ();
+  }
+
+  /*
+   * Parse the denomination keys, merging with the
+   * possibly EXISTING array as required (/keys cherry picking).
+   *
+   * The denominations are grouped by common values of
+   *    {cipher, value, fee, age_mask}.
+   */
+  {
+    json_t *group_obj;
+    unsigned int group_idx;
+
+    json_array_foreach (denominations_by_group, group_idx, group_obj)
+    {
+      /* Running XOR of each SHA512 hash of the denominations' public key in
+         this group.  Used to compare against group.hash after all keys have
+         been parsed. */
+      struct GNUNET_HashCode group_hash_xor = {0};
+
+      /* First, parse { cipher, fees, value, age_mask, hash } of the current
+         group. */
+      struct TALER_DenominationGroup group = {0};
+      const json_t *denom_keys_array;
+      struct GNUNET_JSON_Specification group_spec[] = {
+        TALER_JSON_spec_denomination_group (NULL,
+                                            key_data->currency,
+                                            &group),
+        GNUNET_JSON_spec_array_const ("denoms",
+                                      &denom_keys_array),
+        GNUNET_JSON_spec_end ()
+      };
+      json_t *denom_key_obj;
+      unsigned int index;
+
+      EXITIF (GNUNET_SYSERR ==
+              GNUNET_JSON_parse (group_obj,
+                                 group_spec,
+                                 NULL,
+                                 NULL));
+
+      /* Now, parse the individual denominations */
+      json_array_foreach (denom_keys_array, index, denom_key_obj)
+      {
+        /* Set the common fields from the group for this particular
+           denomination.  Required to make the validity check inside
+           parse_json_denomkey_partially pass */
+        struct TALER_EXCHANGE_DenomPublicKey dk = {
+          .key.cipher = group.cipher,
+          .value = group.value,
+          .fees = group.fees,
+          .key.age_mask = group.age_mask
+        };
+        bool found = false;
+
+        EXITIF (GNUNET_SYSERR ==
+                parse_json_denomkey_partially (&dk,
+                                               group.cipher,
+                                               check_sig,
+                                               denom_key_obj,
+                                               &key_data->master_pub,
+                                               check_sig ? &hash_xor : NULL));
+
+        /* Build the running xor of the SHA512-hash of the public keys for the 
group */
+        GNUNET_CRYPTO_hash_xor (&dk.h_key.hash,
+                                &group_hash_xor,
+                                &group_hash_xor);
+        for (unsigned int j = 0;
+             j<key_data->num_denom_keys;
+             j++)
+        {
+          if (0 == denoms_cmp (&dk,
+                               &key_data->denom_keys[j]))
+          {
+            found = true;
+            break;
+          }
+        }
+
+        if (found)
+        {
+          /* 0:0:0 did not support /keys cherry picking */
+          TALER_LOG_DEBUG ("Skipping denomination key: already know it\n");
+          TALER_denom_pub_free (&dk.key);
+          continue;
+        }
+
+        if (key_data->denom_keys_size == key_data->num_denom_keys)
+          GNUNET_array_grow (key_data->denom_keys,
+                             key_data->denom_keys_size,
+                             key_data->denom_keys_size * 2 + 2);
+        key_data->denom_keys[key_data->num_denom_keys++] = dk;
+
+        /* Update "last_denom_issue_date" */
+        TALER_LOG_DEBUG ("Adding denomination key that is valid_until %s\n",
+                         GNUNET_TIME_timestamp2s (dk.valid_from));
+        key_data->last_denom_issue_date
+          = GNUNET_TIME_timestamp_max (key_data->last_denom_issue_date,
+                                       dk.valid_from);
+      };   /* end of json_array_foreach over denominations */
+
+      /* The calculated group_hash_xor must be the same as group.hash from
+         the JSON. */
+      EXITIF (0 !=
+              GNUNET_CRYPTO_hash_cmp (&group_hash_xor,
+                                      &group.hash));
+
+    } /* end of json_array_foreach over groups of denominations */
+  } /* end of scope for group_ojb/group_idx */
+
+  /* parse the auditor information */
+  {
+    json_t *auditor_info;
+    unsigned int index;
+
+    /* Merge with the existing auditor information we have (/keys cherry 
picking) */
+    json_array_foreach (auditors_array, index, auditor_info)
+    {
+      struct TALER_EXCHANGE_AuditorInformation ai;
+      bool found = false;
+
+      memset (&ai,
+              0,
+              sizeof (ai));
+      EXITIF (GNUNET_SYSERR ==
+              parse_json_auditor (&ai,
+                                  check_sig,
+                                  auditor_info,
+                                  key_data));
+      for (unsigned int j = 0; j<key_data->num_auditors; j++)
+      {
+        struct TALER_EXCHANGE_AuditorInformation *aix = &key_data->auditors[j];
+
+        if (0 == GNUNET_memcmp (&ai.auditor_pub,
+                                &aix->auditor_pub))
+        {
+          found = true;
+          /* Merge denomination key signatures of downloaded /keys into 
existing
+             auditor information 'aix'. */
+          TALER_LOG_DEBUG (
+            "Merging %u new audited keys with %u known audited keys\n",
+            aix->num_denom_keys,
+            ai.num_denom_keys);
+          for (unsigned int i = 0; i<ai.num_denom_keys; i++)
+          {
+            bool kfound = false;
+
+            for (unsigned int k = 0; k<aix->num_denom_keys; k++)
+            {
+              if (aix->denom_keys[k].denom_key_offset ==
+                  ai.denom_keys[i].denom_key_offset)
+              {
+                kfound = true;
+                break;
+              }
+            }
+            if (! kfound)
+              GNUNET_array_append (aix->denom_keys,
+                                   aix->num_denom_keys,
+                                   ai.denom_keys[i]);
+          }
+          break;
+        }
+      }
+      if (found)
+      {
+        GNUNET_array_grow (ai.denom_keys,
+                           ai.num_denom_keys,
+                           0);
+        GNUNET_free (ai.auditor_url);
+        continue; /* we are done */
+      }
+      if (key_data->auditors_size == key_data->num_auditors)
+        GNUNET_array_grow (key_data->auditors,
+                           key_data->auditors_size,
+                           key_data->auditors_size * 2 + 2);
+      GNUNET_assert (NULL != ai.auditor_url);
+      key_data->auditors[key_data->num_auditors++] = ai;
+    };
+  }
+
+  /* parse the revocation/recoup information */
+  if (NULL != recoup_array)
+  {
+    json_t *recoup_info;
+    unsigned int index;
+
+    json_array_foreach (recoup_array, index, recoup_info)
+    {
+      struct TALER_DenominationHashP h_denom_pub;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+                                     &h_denom_pub),
+        GNUNET_JSON_spec_end ()
+      };
+
+      EXITIF (GNUNET_OK !=
+              GNUNET_JSON_parse (recoup_info,
+                                 spec,
+                                 NULL, NULL));
+      for (unsigned int j = 0;
+           j<key_data->num_denom_keys;
+           j++)
+      {
+        if (0 == GNUNET_memcmp (&h_denom_pub,
+                                &key_data->denom_keys[j].h_key))
+        {
+          key_data->denom_keys[j].revoked = true;
+          break;
+        }
+      }
+    }
+  }
+
+  if (check_sig)
+  {
+    EXITIF (GNUNET_OK !=
+            TALER_EXCHANGE_test_signing_key (key_data,
+                                             &pub));
+    EXITIF (GNUNET_OK !=
+            TALER_exchange_online_key_set_verify (
+              key_data->list_issue_date,
+              &hash_xor,
+              &pub,
+              &denominations_sig));
+  }
+  return GNUNET_OK;
+
+EXITIF_exit:
+  *vc = TALER_EXCHANGE_VC_PROTOCOL_ERROR;
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Callback used when downloading the reply to a /keys request
+ * is complete.
+ *
+ * @param cls the `struct KeysRequest`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp_obj parsed JSON result, NULL on error
+ */
+static void
+keys_completed_cb (void *cls,
+                   long response_code,
+                   const void *resp_obj)
+{
+  struct TALER_EXCHANGE_GetKeysHandle *gkh = cls;
+  const json_t *j = resp_obj;
+  struct TALER_EXCHANGE_Keys *kd = NULL;
+  struct TALER_EXCHANGE_KeysResponse kresp = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code,
+    .details.ok.compat = TALER_EXCHANGE_VC_PROTOCOL_ERROR,
+  };
+
+  gkh->job = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Received keys from URL `%s' with status %ld and expiration 
%s.\n",
+              gkh->url,
+              response_code,
+              GNUNET_TIME_timestamp2s (gkh->expire));
+  if (GNUNET_TIME_absolute_is_past (gkh->expire.abs_time))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Exchange failed to give expiration time, assuming in %s\n",
+                GNUNET_TIME_relative2s (DEFAULT_EXPIRATION,
+                                        true));
+    gkh->expire
+      = GNUNET_TIME_absolute_to_timestamp (
+          GNUNET_TIME_relative_to_absolute (DEFAULT_EXPIRATION));
+  }
+  switch (response_code)
+  {
+  case 0:
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to receive /keys response from exchange %s\n",
+                gkh->exchange_url);
+    break;
+  case MHD_HTTP_OK:
+    if (NULL == j)
+    {
+      GNUNET_break (0);
+      response_code = 0;
+      break;
+    }
+    kd = GNUNET_new (struct TALER_EXCHANGE_Keys);
+    kd->exchange_url = GNUNET_strdup (gkh->exchange_url);
+    if (NULL != gkh->prev_keys)
+    {
+      const struct TALER_EXCHANGE_Keys *kd_old = gkh->prev_keys;
+
+      /* We keep the denomination keys and auditor signatures from the
+         previous iteration (/keys cherry picking) */
+      kd->num_denom_keys
+        = kd_old->num_denom_keys;
+      kd->last_denom_issue_date
+        = kd_old->last_denom_issue_date;
+      GNUNET_array_grow (kd->denom_keys,
+                         kd->denom_keys_size,
+                         kd->num_denom_keys);
+      /* First make a shallow copy, we then need another pass for the RSA 
key... */
+      GNUNET_memcpy (kd->denom_keys,
+                     kd_old->denom_keys,
+                     kd_old->num_denom_keys
+                     * sizeof (struct TALER_EXCHANGE_DenomPublicKey));
+      for (unsigned int i = 0; i<kd_old->num_denom_keys; i++)
+        TALER_denom_pub_deep_copy (&kd->denom_keys[i].key,
+                                   &kd_old->denom_keys[i].key);
+      kd->num_auditors = kd_old->num_auditors;
+      kd->auditors = GNUNET_new_array (kd->num_auditors,
+                                       struct 
TALER_EXCHANGE_AuditorInformation);
+      /* Now the necessary deep copy... */
+      for (unsigned int i = 0; i<kd_old->num_auditors; i++)
+      {
+        const struct TALER_EXCHANGE_AuditorInformation *aold =
+          &kd_old->auditors[i];
+        struct TALER_EXCHANGE_AuditorInformation *anew = &kd->auditors[i];
+
+        anew->auditor_pub = aold->auditor_pub;
+        anew->auditor_url = GNUNET_strdup (aold->auditor_url);
+        GNUNET_array_grow (anew->denom_keys,
+                           anew->num_denom_keys,
+                           aold->num_denom_keys);
+        GNUNET_memcpy (
+          anew->denom_keys,
+          aold->denom_keys,
+          aold->num_denom_keys
+          * sizeof (struct TALER_EXCHANGE_AuditorDenominationInfo));
+      }
+    }
+    /* Now decode fresh /keys response */
+    if (GNUNET_OK !=
+        decode_keys_json (j,
+                          true,
+                          kd,
+                          &kresp.details.ok.compat))
+    {
+      TALER_LOG_ERROR ("Could not decode /keys response\n");
+      kd->rc = 1;
+      TALER_EXCHANGE_keys_decref (kd);
+      kd = NULL;
+      kresp.hr.http_status = 0;
+      kresp.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+      break;
+    }
+    kd->rc = 1;
+    kd->key_data_expiration = gkh->expire;
+    if (GNUNET_TIME_relative_cmp (
+          GNUNET_TIME_absolute_get_remaining (gkh->expire.abs_time),
+          <,
+          MINIMUM_EXPIRATION))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Exchange returned keys with expiration time below %s. 
Compensating.\n",
+                  GNUNET_TIME_relative2s (MINIMUM_EXPIRATION,
+                                          true));
+      kd->key_data_expiration
+        = GNUNET_TIME_relative_to_timestamp (MINIMUM_EXPIRATION);
+    }
+
+    kresp.details.ok.keys = kd;
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+  case MHD_HTTP_UNAUTHORIZED:
+  case MHD_HTTP_FORBIDDEN:
+  case MHD_HTTP_NOT_FOUND:
+    if (NULL == j)
+    {
+      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
+    }
+    else
+    {
+      kresp.hr.ec = TALER_JSON_get_error_code (j);
+      kresp.hr.hint = TALER_JSON_get_error_hint (j);
+    }
+    break;
+  default:
+    if (NULL == j)
+    {
+      kresp.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+      kresp.hr.hint = TALER_ErrorCode_get_hint (kresp.hr.ec);
+    }
+    else
+    {
+      kresp.hr.ec = TALER_JSON_get_error_code (j);
+      kresp.hr.hint = TALER_JSON_get_error_hint (j);
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d\n",
+                (unsigned int) response_code,
+                (int) kresp.hr.ec);
+    break;
+  }
+  gkh->cert_cb (gkh->cert_cb_cls,
+                &kresp,
+                kd);
+  TALER_EXCHANGE_get_keys_cancel (gkh);
+}
+
+
+/**
+ * Define a max length for the HTTP "Expire:" header
+ */
+#define MAX_DATE_LINE_LEN 32
+
+
+/**
+ * Parse HTTP timestamp.
+ *
+ * @param dateline header to parse header
+ * @param[out] at where to write the result
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_date_string (const char *dateline,
+                   struct GNUNET_TIME_Timestamp *at)
+{
+  static const char *MONTHS[] =
+  { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL };
+  int year;
+  int mon;
+  int day;
+  int hour;
+  int min;
+  int sec;
+  char month[4];
+  struct tm tm;
+  time_t t;
+
+  /* We recognize the three formats in RFC2616, section 3.3.1.  Month
+     names are always in English.  The formats are:
+      Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
+      Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+      Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
+     Note that the first is preferred.
+   */
+
+  if (strlen (dateline) > MAX_DATE_LINE_LEN)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  while (*dateline == ' ')
+    ++dateline;
+  while (*dateline && *dateline != ' ')
+    ++dateline;
+  while (*dateline == ' ')
+    ++dateline;
+  /* We just skipped over the day of the week. Now we have:*/
+  if ( (sscanf (dateline,
+                "%d %3s %d %d:%d:%d",
+                &day, month, &year, &hour, &min, &sec) != 6) &&
+       (sscanf (dateline,
+                "%d-%3s-%d %d:%d:%d",
+                &day, month, &year, &hour, &min, &sec) != 6) &&
+       (sscanf (dateline,
+                "%3s %d %d:%d:%d %d",
+                month, &day, &hour, &min, &sec, &year) != 6) )
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  /* Two digit dates are defined to be relative to 1900; all other dates
+   * are supposed to be represented as four digits. */
+  if (year < 100)
+    year += 1900;
+
+  for (mon = 0; ; mon++)
+  {
+    if (! MONTHS[mon])
+    {
+      GNUNET_break_op (0);
+      return GNUNET_SYSERR;
+    }
+    if (0 == strcasecmp (month,
+                         MONTHS[mon]))
+      break;
+  }
+
+  memset (&tm, 0, sizeof(tm));
+  tm.tm_year = year - 1900;
+  tm.tm_mon = mon;
+  tm.tm_mday = day;
+  tm.tm_hour = hour;
+  tm.tm_min = min;
+  tm.tm_sec = sec;
+
+  t = mktime (&tm);
+  if (((time_t) -1) == t)
+  {
+    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                         "mktime");
+    return GNUNET_SYSERR;
+  }
+  if (t < 0)
+    t = 0; /* can happen due to timezone issues if date was 1.1.1970 */
+  *at = GNUNET_TIME_timestamp_from_s (t);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called for each header in the HTTP /keys response.
+ * Finds the "Expire:" header and parses it, storing the result
+ * in the "expire" field of the keys request.
+ *
+ * @param buffer header data received
+ * @param size size of an item in @a buffer
+ * @param nitems number of items in @a buffer
+ * @param userdata the `struct TALER_EXCHANGE_GetKeysHandle`
+ * @return `size * nitems` on success (everything else aborts)
+ */
+static size_t
+header_cb (char *buffer,
+           size_t size,
+           size_t nitems,
+           void *userdata)
+{
+  struct TALER_EXCHANGE_GetKeysHandle *kr = userdata;
+  size_t total = size * nitems;
+  char *val;
+
+  if (total < strlen (MHD_HTTP_HEADER_EXPIRES ": "))
+    return total;
+  if (0 != strncasecmp (MHD_HTTP_HEADER_EXPIRES ": ",
+                        buffer,
+                        strlen (MHD_HTTP_HEADER_EXPIRES ": ")))
+    return total;
+  val = GNUNET_strndup (&buffer[strlen (MHD_HTTP_HEADER_EXPIRES ": ")],
+                        total - strlen (MHD_HTTP_HEADER_EXPIRES ": "));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Found %s header `%s'\n",
+              MHD_HTTP_HEADER_EXPIRES,
+              val);
+  if (GNUNET_OK !=
+      parse_date_string (val,
+                         &kr->expire))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to parse %s-header `%s'\n",
+                MHD_HTTP_HEADER_EXPIRES,
+                val);
+    kr->expire = GNUNET_TIME_UNIT_ZERO_TS;
+  }
+  GNUNET_free (val);
+  return total;
+}
+
+
+struct TALER_EXCHANGE_GetKeysHandle *
+TALER_EXCHANGE_get_keys (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *last_keys,
+  TALER_EXCHANGE_GetKeysCallback cert_cb,
+  void *cert_cb_cls)
+{
+  struct TALER_EXCHANGE_GetKeysHandle *gkh;
+  CURL *eh;
+  char last_date[80] = { 0 };
+
+  TALER_LOG_DEBUG ("Connecting to the exchange (%s)\n",
+                   url);
+  gkh = GNUNET_new (struct TALER_EXCHANGE_GetKeysHandle);
+  gkh->exchange_url = GNUNET_strdup (url);
+  gkh->cert_cb = cert_cb;
+  gkh->cert_cb_cls = cert_cb_cls;
+  if (NULL != last_keys)
+  {
+    gkh->prev_keys = TALER_EXCHANGE_keys_incref (last_keys);
+    TALER_LOG_DEBUG ("Last DK issue date (before GETting /keys): %s\n",
+                     GNUNET_TIME_timestamp2s (
+                       last_keys->last_denom_issue_date));
+    GNUNET_snprintf (last_date,
+                     sizeof (last_date),
+                     "%llu",
+                     (unsigned long long)
+                     last_keys->last_denom_issue_date.abs_time.abs_value_us
+                     / 1000000LLU);
+  }
+  gkh->url = TALER_url_join (url,
+                             "keys",
+                             (NULL != last_keys)
+                             ? "last_issue_date"
+                             : NULL,
+                             (NULL != last_keys)
+                             ? last_date
+                             : NULL,
+                             NULL);
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Requesting keys with URL `%s'.\n",
+              gkh->url);
+  eh = TALER_EXCHANGE_curl_easy_get_ (gkh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (gkh->exchange_url);
+    GNUNET_free (gkh->url);
+    GNUNET_free (gkh);
+    return NULL;
+  }
+  GNUNET_break (CURLE_OK ==
+                curl_easy_setopt (eh,
+                                  CURLOPT_VERBOSE,
+                                  0));
+  GNUNET_break (CURLE_OK ==
+                curl_easy_setopt (eh,
+                                  CURLOPT_TIMEOUT,
+                                  120 /* seconds */));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_HEADERFUNCTION,
+                                   &header_cb));
+  GNUNET_assert (CURLE_OK ==
+                 curl_easy_setopt (eh,
+                                   CURLOPT_HEADERDATA,
+                                   gkh));
+  gkh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+                                               eh,
+                                               &keys_completed_cb,
+                                               gkh);
+  return gkh;
+}
+
+
+void
+TALER_EXCHANGE_get_keys_cancel (
+  struct TALER_EXCHANGE_GetKeysHandle *gkh)
+{
+  if (NULL != gkh->job)
+  {
+    GNUNET_CURL_job_cancel (gkh->job);
+    gkh->job = NULL;
+  }
+  TALER_EXCHANGE_keys_decref (gkh->prev_keys);
+  GNUNET_free (gkh->exchange_url);
+  GNUNET_free (gkh->url);
+  GNUNET_free (gkh);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_EXCHANGE_test_signing_key (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ExchangePublicKeyP *pub)
+{
+  struct GNUNET_TIME_Absolute now;
+
+  /* we will check using a tolerance of 1h for the time */
+  now = GNUNET_TIME_absolute_get ();
+  for (unsigned int i = 0; i<keys->num_sign_keys; i++)
+    if ( (GNUNET_TIME_absolute_cmp (
+            keys->sign_keys[i].valid_from.abs_time,
+            <=,
+            GNUNET_TIME_absolute_add (now,
+                                      LIFETIME_TOLERANCE))) &&
+         (GNUNET_TIME_absolute_cmp (
+            keys->sign_keys[i].valid_until.abs_time,
+            >,
+            GNUNET_TIME_absolute_subtract (now,
+                                           LIFETIME_TOLERANCE))) &&
+         (0 == GNUNET_memcmp (pub,
+                              &keys->sign_keys[i].key)) )
+      return GNUNET_OK;
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "Signing key not valid at time %s\n",
+              GNUNET_TIME_absolute2s (now));
+  return GNUNET_SYSERR;
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_DenominationPublicKey *pk)
+{
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+    if (0 ==
+        TALER_denom_pub_cmp (pk,
+                             &keys->denom_keys[i].key))
+      return &keys->denom_keys[i];
+  return NULL;
+}
+
+
+const struct TALER_EXCHANGE_GlobalFee *
+TALER_EXCHANGE_get_global_fee (
+  const struct TALER_EXCHANGE_Keys *keys,
+  struct GNUNET_TIME_Timestamp ts)
+{
+  for (unsigned int i = 0; i<keys->num_global_fees; i++)
+  {
+    const struct TALER_EXCHANGE_GlobalFee *gf = &keys->global_fees[i];
+
+    if (GNUNET_TIME_timestamp_cmp (ts,
+                                   >=,
+                                   gf->start_date) &&
+        GNUNET_TIME_timestamp_cmp (ts,
+                                   <,
+                                   gf->end_date))
+      return gf;
+  }
+  return NULL;
+}
+
+
+struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_copy_denomination_key (
+  const struct TALER_EXCHANGE_DenomPublicKey *key)
+{
+  struct TALER_EXCHANGE_DenomPublicKey *copy;
+
+  copy = GNUNET_new (struct TALER_EXCHANGE_DenomPublicKey);
+  *copy = *key;
+  TALER_denom_pub_deep_copy (&copy->key,
+                             &key->key);
+  return copy;
+}
+
+
+void
+TALER_EXCHANGE_destroy_denomination_key (
+  struct TALER_EXCHANGE_DenomPublicKey *key)
+{
+  TALER_denom_pub_free (&key->key);
+  GNUNET_free (key);
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_EXCHANGE_get_denomination_key_by_hash (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_DenominationHashP *hc)
+{
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+    if (0 == GNUNET_memcmp (hc,
+                            &keys->denom_keys[i].h_key))
+      return &keys->denom_keys[i];
+  return NULL;
+}
+
+
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_incref (struct TALER_EXCHANGE_Keys *keys)
+{
+  GNUNET_assert (keys->rc < UINT_MAX);
+  keys->rc++;
+  return keys;
+}
+
+
+void
+TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys)
+{
+  if (NULL == keys)
+    return;
+  GNUNET_assert (0 < keys->rc);
+  keys->rc--;
+  if (0 != keys->rc)
+    return;
+  GNUNET_array_grow (keys->sign_keys,
+                     keys->num_sign_keys,
+                     0);
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+    TALER_denom_pub_free (&keys->denom_keys[i].key);
+
+  GNUNET_array_grow (keys->denom_keys,
+                     keys->denom_keys_size,
+                     0);
+  for (unsigned int i = 0; i<keys->num_auditors; i++)
+  {
+    GNUNET_array_grow (keys->auditors[i].denom_keys,
+                       keys->auditors[i].num_denom_keys,
+                       0);
+    GNUNET_free (keys->auditors[i].auditor_url);
+  }
+  GNUNET_array_grow (keys->auditors,
+                     keys->auditors_size,
+                     0);
+  TALER_EXCHANGE_free_accounts (keys->accounts_len,
+                                keys->accounts);
+  GNUNET_array_grow (keys->accounts,
+                     keys->accounts_len,
+                     0);
+  free_fees (keys->fees,
+             keys->fees_len);
+  json_decref (keys->extensions);
+  GNUNET_free (keys->wallet_balance_limit_without_kyc);
+  GNUNET_free (keys->version);
+  GNUNET_free (keys->currency);
+  GNUNET_free (keys->asset_type);
+  GNUNET_free (keys->global_fees);
+  GNUNET_free (keys->exchange_url);
+  GNUNET_free (keys);
+}
+
+
+struct TALER_EXCHANGE_Keys *
+TALER_EXCHANGE_keys_from_json (const json_t *j)
+{
+  const json_t *jkeys;
+  const char *url;
+  uint32_t version;
+  struct GNUNET_TIME_Timestamp expire
+    = GNUNET_TIME_UNIT_ZERO_TS;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_uint32 ("version",
+                             &version),
+    GNUNET_JSON_spec_object_const ("keys",
+                                   &jkeys),
+    GNUNET_JSON_spec_string ("exchange_url",
+                             &url),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_timestamp ("expire",
+                                  &expire),
+      NULL),
+    GNUNET_JSON_spec_end ()
+  };
+  struct TALER_EXCHANGE_Keys *keys;
+  enum TALER_EXCHANGE_VersionCompatibility compat;
+
+  if (NULL == j)
+    return NULL;
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  if (0 != version)
+  {
+    return NULL; /* unsupported version */
+  }
+  keys = GNUNET_new (struct TALER_EXCHANGE_Keys);
+  if (GNUNET_OK !=
+      decode_keys_json (jkeys,
+                        false,
+                        keys,
+                        &compat))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  keys->rc = 1;
+  keys->key_data_expiration = expire;
+  keys->exchange_url = GNUNET_strdup (url);
+  return keys;
+}
+
+
+/**
+ * Data we track per denomination group.
+ */
+struct GroupData
+{
+  /**
+   * The json blob with the group meta-data and list of denominations
+   */
+  json_t *json;
+
+  /**
+   * Meta data for this group.
+   */
+  struct TALER_DenominationGroup meta;
+};
+
+
+/**
+ * Add denomination group represented by @a value
+ * to list of denominations in @a cls. Also frees
+ * the @a value.
+ *
+ * @param[in,out] cls a `json_t *` with an array to build
+ * @param key unused
+ * @param value a `struct GroupData *`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_grp (void *cls,
+         const struct GNUNET_HashCode *key,
+         void *value)
+{
+  json_t *denominations_by_group = cls;
+  struct GroupData *gd = value;
+  const char *cipher;
+  json_t *ge;
+  bool age_restricted = gd->meta.age_mask.bits != 0;
+
+  (void) key;
+  switch (gd->meta.cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+    break;
+  case TALER_DENOMINATION_CS:
+    cipher = age_restricted ? "CS+age_restricted" : "CS";
+    break;
+  default:
+    GNUNET_assert (false);
+  }
+
+  ge = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_data_auto ("hash",
+                                &gd->meta.hash),
+    GNUNET_JSON_pack_string ("cipher",
+                             cipher),
+    GNUNET_JSON_pack_array_steal ("denoms",
+                                  gd->json),
+    TALER_JSON_PACK_DENOM_FEES ("fee",
+                                &gd->meta.fees),
+    GNUNET_JSON_pack_allow_null (
+      age_restricted
+          ? GNUNET_JSON_pack_uint64 ("age_mask",
+                                     gd->meta.age_mask.bits)
+          : GNUNET_JSON_pack_string ("dummy",
+                                     NULL)),
+    TALER_JSON_pack_amount ("value",
+                            &gd->meta.value));
+  GNUNET_assert (0 ==
+                 json_array_append_new (denominations_by_group,
+                                        ge));
+  GNUNET_free (gd);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Convert array of account restrictions @a ars to JSON.
+ *
+ * @param ar_len length of @a ars
+ * @param ars account restrictions to convert
+ * @return JSON representation
+ */
+static json_t *
+ar_to_json (unsigned int ar_len,
+            const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len])
+{
+  json_t *rval;
+
+  rval = json_array ();
+  GNUNET_assert (NULL != rval);
+  for (unsigned int i = 0; i<ar_len; i++)
+  {
+    const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i];
+
+    switch (ar->type)
+    {
+    case TALER_EXCHANGE_AR_INVALID:
+      GNUNET_break (0);
+      json_decref (rval);
+      return NULL;
+    case TALER_EXCHANGE_AR_DENY:
+      GNUNET_assert (
+        0 ==
+        json_array_append_new (
+          rval,
+          GNUNET_JSON_PACK (
+            GNUNET_JSON_pack_string ("type",
+                                     "deny"))));
+      break;
+    case TALER_EXCHANGE_AR_REGEX:
+      GNUNET_assert (
+        0 ==
+        json_array_append_new (
+          rval,
+          GNUNET_JSON_PACK (
+            GNUNET_JSON_pack_string (
+              "type",
+              "regex"),
+            GNUNET_JSON_pack_string (
+              "regex",
+              ar->details.regex.posix_egrep),
+            GNUNET_JSON_pack_string (
+              "human_hint",
+              ar->details.regex.human_hint),
+            GNUNET_JSON_pack_object_incref (
+              "human_hint_i18n",
+              (json_t *) ar->details.regex.human_hint_i18n)
+            )));
+      break;
+    }
+  }
+  return rval;
+}
+
+
+json_t *
+TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd)
+{
+  struct GNUNET_TIME_Timestamp now;
+  json_t *keys;
+  json_t *signkeys;
+  json_t *denominations_by_group;
+  json_t *auditors;
+  json_t *recoup;
+  json_t *wire_fees;
+  json_t *accounts;
+  json_t *global_fees;
+  json_t *wblwk = NULL;
+
+  now = GNUNET_TIME_timestamp_get ();
+  signkeys = json_array ();
+  GNUNET_assert (NULL != signkeys);
+  for (unsigned int i = 0; i<kd->num_sign_keys; i++)
+  {
+    const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i];
+    json_t *signkey;
+
+    if (GNUNET_TIME_timestamp_cmp (now,
+                                   >,
+                                   sk->valid_until))
+      continue; /* skip keys that have expired */
+    signkey = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("key",
+                                  &sk->key),
+      GNUNET_JSON_pack_data_auto ("master_sig",
+                                  &sk->master_sig),
+      GNUNET_JSON_pack_timestamp ("stamp_start",
+                                  sk->valid_from),
+      GNUNET_JSON_pack_timestamp ("stamp_expire",
+                                  sk->valid_until),
+      GNUNET_JSON_pack_timestamp ("stamp_end",
+                                  sk->valid_legal));
+    GNUNET_assert (NULL != signkey);
+    GNUNET_assert (0 ==
+                   json_array_append_new (signkeys,
+                                          signkey));
+  }
+
+  denominations_by_group = json_array ();
+  GNUNET_assert (NULL != denominations_by_group);
+  {
+    struct GNUNET_CONTAINER_MultiHashMap *dbg;
+
+    dbg = GNUNET_CONTAINER_multihashmap_create (128,
+                                                false);
+    for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+    {
+      const struct TALER_EXCHANGE_DenomPublicKey *dk = &kd->denom_keys[i];
+      struct TALER_DenominationGroup meta = {
+        .cipher = dk->key.cipher,
+        .value = dk->value,
+        .fees = dk->fees,
+        .age_mask = dk->key.age_mask
+      };
+      struct GNUNET_HashCode key;
+      struct GroupData *gd;
+      json_t *denom;
+      struct GNUNET_JSON_PackSpec key_spec;
+
+      if (GNUNET_TIME_timestamp_cmp (now,
+                                     >,
+                                     dk->expire_deposit))
+        continue; /* skip keys that have expired */
+      TALER_denomination_group_get_key (&meta,
+                                        &key);
+      gd = GNUNET_CONTAINER_multihashmap_get (dbg,
+                                              &key);
+      if (NULL == gd)
+      {
+        gd = GNUNET_new (struct GroupData);
+        gd->meta = meta;
+        gd->json = json_array ();
+        GNUNET_assert (NULL != gd->json);
+        GNUNET_assert (
+          GNUNET_OK ==
+          GNUNET_CONTAINER_multihashmap_put (dbg,
+                                             &key,
+                                             gd,
+                                             
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+
+      }
+      /* Build the running xor of the SHA512-hash of the public keys */
+      GNUNET_CRYPTO_hash_xor (&dk->h_key.hash,
+                              &gd->meta.hash,
+                              &gd->meta.hash);
+      switch (meta.cipher)
+      {
+      case TALER_DENOMINATION_RSA:
+        key_spec =
+          GNUNET_JSON_pack_rsa_public_key (
+            "rsa_pub",
+            dk->key.details.rsa_public_key);
+        break;
+      case TALER_DENOMINATION_CS:
+        key_spec =
+          GNUNET_JSON_pack_data_varsize (
+            "cs_pub",
+            &dk->key.details.cs_public_key,
+            sizeof (dk->key.details.cs_public_key));
+        break;
+      default:
+        GNUNET_assert (false);
+      }
+      denom = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+                                    dk->expire_deposit),
+        GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+                                    dk->withdraw_valid_until),
+        GNUNET_JSON_pack_timestamp ("stamp_start",
+                                    dk->valid_from),
+        GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+                                    dk->expire_legal),
+        GNUNET_JSON_pack_data_auto ("master_sig",
+                                    &dk->master_sig),
+        key_spec
+        );
+      GNUNET_assert (0 ==
+                     json_array_append_new (gd->json,
+                                            denom));
+    }
+    GNUNET_CONTAINER_multihashmap_iterate (dbg,
+                                           &add_grp,
+                                           denominations_by_group);
+    GNUNET_CONTAINER_multihashmap_destroy (dbg);
+  }
+
+  auditors = json_array ();
+  GNUNET_assert (NULL != auditors);
+  for (unsigned int i = 0; i<kd->num_auditors; i++)
+  {
+    const struct TALER_EXCHANGE_AuditorInformation *ai = &kd->auditors[i];
+    json_t *a;
+    json_t *adenoms;
+
+    adenoms = json_array ();
+    GNUNET_assert (NULL != adenoms);
+    for (unsigned int j = 0; j<ai->num_denom_keys; j++)
+    {
+      const struct TALER_EXCHANGE_AuditorDenominationInfo *adi =
+        &ai->denom_keys[j];
+      const struct TALER_EXCHANGE_DenomPublicKey *dk =
+        &kd->denom_keys[adi->denom_key_offset];
+      json_t *k;
+
+      GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+      if (GNUNET_TIME_timestamp_cmp (now,
+                                     >,
+                                     dk->expire_deposit))
+        continue; /* skip auditor signatures for denomination keys that have 
expired */
+      GNUNET_assert (adi->denom_key_offset < kd->num_denom_keys);
+      k = GNUNET_JSON_PACK (
+        GNUNET_JSON_pack_data_auto ("denom_pub_h",
+                                    &dk->h_key),
+        GNUNET_JSON_pack_data_auto ("auditor_sig",
+                                    &adi->auditor_sig));
+      GNUNET_assert (0 ==
+                     json_array_append_new (adenoms,
+                                            k));
+    }
+
+    a = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("auditor_pub",
+                                  &ai->auditor_pub),
+      GNUNET_JSON_pack_string ("auditor_url",
+                               ai->auditor_url),
+      GNUNET_JSON_pack_array_steal ("denomination_keys",
+                                    adenoms));
+    GNUNET_assert (0 ==
+                   json_array_append_new (auditors,
+                                          a));
+  }
+
+  global_fees = json_array ();
+  GNUNET_assert (NULL != global_fees);
+  for (unsigned int i = 0; i<kd->num_global_fees; i++)
+  {
+    const struct TALER_EXCHANGE_GlobalFee *gf
+      = &kd->global_fees[i];
+
+    if (GNUNET_TIME_absolute_is_past (gf->end_date.abs_time))
+      continue;
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        global_fees,
+        GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_timestamp ("start_date",
+                                      gf->start_date),
+          GNUNET_JSON_pack_timestamp ("end_date",
+                                      gf->end_date),
+          TALER_JSON_PACK_GLOBAL_FEES (&gf->fees),
+          GNUNET_JSON_pack_time_rel ("history_expiration",
+                                     gf->history_expiration),
+          GNUNET_JSON_pack_time_rel ("purse_timeout",
+                                     gf->purse_timeout),
+          GNUNET_JSON_pack_uint64 ("purse_account_limit",
+                                   gf->purse_account_limit),
+          GNUNET_JSON_pack_data_auto ("master_sig",
+                                      &gf->master_sig))));
+  }
+
+  accounts = json_array ();
+  GNUNET_assert (NULL != accounts);
+  for (unsigned int i = 0; i<kd->accounts_len; i++)
+  {
+    const struct TALER_EXCHANGE_WireAccount *acc
+      = &kd->accounts[i];
+    json_t *credit_restrictions;
+    json_t *debit_restrictions;
+
+    credit_restrictions
+      = ar_to_json (acc->credit_restrictions_length,
+                    acc->credit_restrictions);
+    GNUNET_assert (NULL != credit_restrictions);
+    debit_restrictions
+      = ar_to_json (acc->debit_restrictions_length,
+                    acc->debit_restrictions);
+    GNUNET_assert (NULL != debit_restrictions);
+    GNUNET_assert (
+      0 ==
+      json_array_append_new (
+        accounts,
+        GNUNET_JSON_PACK (
+          GNUNET_JSON_pack_string ("payto_uri",
+                                   acc->payto_uri),
+          GNUNET_JSON_pack_allow_null (
+            GNUNET_JSON_pack_string ("conversion_url",
+                                     acc->conversion_url)),
+          GNUNET_JSON_pack_array_steal ("debit_restrictions",
+                                        debit_restrictions),
+          GNUNET_JSON_pack_array_steal ("credit_restrictions",
+                                        credit_restrictions),
+          GNUNET_JSON_pack_data_auto ("master_sig",
+                                      &acc->master_sig))));
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Serialized %u/%u wire accounts to JSON\n",
+              (unsigned int) json_array_size (accounts),
+              kd->accounts_len);
+
+  wire_fees = json_object ();
+  GNUNET_assert (NULL != wire_fees);
+  for (unsigned int i = 0; i<kd->fees_len; i++)
+  {
+    const struct TALER_EXCHANGE_WireFeesByMethod *fbw
+      = &kd->fees[i];
+    json_t *wf;
+
+    wf = json_array ();
+    GNUNET_assert (NULL != wf);
+    for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head;
+         NULL != p;
+         p = p->next)
+    {
+      GNUNET_assert (
+        0 ==
+        json_array_append_new (
+          wf,
+          GNUNET_JSON_PACK (
+            TALER_JSON_pack_amount ("wire_fee",
+                                    &p->fees.wire),
+            TALER_JSON_pack_amount ("closing_fee",
+                                    &p->fees.closing),
+            GNUNET_JSON_pack_timestamp ("start_date",
+                                        p->start_date),
+            GNUNET_JSON_pack_timestamp ("end_date",
+                                        p->end_date),
+            GNUNET_JSON_pack_data_auto ("sig",
+                                        &p->master_sig))));
+    }
+    GNUNET_assert (0 ==
+                   json_object_set_new (wire_fees,
+                                        fbw->method,
+                                        wf));
+  }
+
+  recoup = json_array ();
+  GNUNET_assert (NULL != recoup);
+  for (unsigned int i = 0; i<kd->num_denom_keys; i++)
+  {
+    const struct TALER_EXCHANGE_DenomPublicKey *dk
+      = &kd->denom_keys[i];
+    if (! dk->revoked)
+      continue;
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     recoup,
+                     GNUNET_JSON_PACK (
+                       GNUNET_JSON_pack_data_auto ("h_denom_pub",
+                                                   &dk->h_key))));
+  }
+
+  wblwk = json_array ();
+  GNUNET_assert (NULL != wblwk);
+  for (unsigned int i = 0; i<kd->wblwk_length; i++)
+  {
+    const struct TALER_Amount *a = &kd->wallet_balance_limit_without_kyc[i];
+
+    GNUNET_assert (0 ==
+                   json_array_append_new (
+                     wblwk,
+                     TALER_JSON_from_amount (a)));
+  }
+
+  keys = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("version",
+                             kd->version),
+    GNUNET_JSON_pack_string ("currency",
+                             kd->currency),
+    GNUNET_JSON_pack_uint64 ("currency_fraction_digits",
+                             kd->currency_fraction_digits),
+    TALER_JSON_pack_amount ("stefan_abs",
+                            &kd->stefan_abs),
+    TALER_JSON_pack_amount ("stefan_log",
+                            &kd->stefan_log),
+    TALER_JSON_pack_amount ("stefan_lin",
+                            &kd->stefan_lin),
+    GNUNET_JSON_pack_string ("asset_type",
+                             kd->asset_type),
+    GNUNET_JSON_pack_data_auto ("master_public_key",
+                                &kd->master_pub),
+    GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+                               kd->reserve_closing_delay),
+    GNUNET_JSON_pack_timestamp ("list_issue_date",
+                                kd->list_issue_date),
+    GNUNET_JSON_pack_array_steal ("global_fees",
+                                  global_fees),
+    GNUNET_JSON_pack_array_steal ("signkeys",
+                                  signkeys),
+    GNUNET_JSON_pack_object_steal ("wire_fees",
+                                   wire_fees),
+    GNUNET_JSON_pack_array_steal ("accounts",
+                                  accounts),
+    GNUNET_JSON_pack_array_steal ("wads",
+                                  json_array ()),
+    GNUNET_JSON_pack_array_steal ("denominations",
+                                  denominations_by_group),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_array_steal ("recoup",
+                                    recoup)),
+    GNUNET_JSON_pack_array_steal ("auditors",
+                                  auditors),
+    GNUNET_JSON_pack_bool ("rewards_allowed",
+                           kd->rewards_allowed),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_object_incref ("extensions",
+                                      kd->extensions)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_is_zero (&kd->extensions_sig)
+      ? GNUNET_JSON_pack_string ("dummy",
+                                 NULL)
+      : GNUNET_JSON_pack_data_auto ("extensions_sig",
+                                    &kd->extensions_sig)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_array_steal ("wallet_balance_limit_without_kyc",
+                                    wblwk))
+
+    );
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_uint64 ("version",
+                             EXCHANGE_SERIALIZATION_FORMAT_VERSION),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_timestamp ("expire",
+                                  kd->key_data_expiration)),
+    GNUNET_JSON_pack_string ("exchange_url",
+                             kd->exchange_url),
+    GNUNET_JSON_pack_object_steal ("keys",
+                                   keys));
+}
+
+
+/* end of exchange_api_handle.c */
diff --git a/src/lib/exchange_api_handle.h b/src/lib/exchange_api_handle.h
new file mode 100644
index 0000000..7c01b9a
--- /dev/null
+++ b/src/lib/exchange_api_handle.h
@@ -0,0 +1,64 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014, 2015, 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_handle.h
+ * @brief Internal interface to the handle part of the exchange's HTTP API
+ * @author Christian Grothoff
+ */
+#ifndef EXCHANGE_API_HANDLE_H
+#define EXCHANGE_API_HANDLE_H
+
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_auditor_service.h"
+#include "taler_exchange_service.h"
+#include "taler_util.h"
+#include "taler_curl_lib.h"
+
+
+/**
+ * Function called for each auditor to give us a chance to possibly
+ * launch a deposit confirmation interaction.
+ *
+ * @param cls closure
+ * @param auditor_url base URL of the auditor
+ * @param auditor_pub public key of the auditor
+ */
+typedef void
+(*TEAH_AuditorCallback)(
+  void *cls,
+  const char *auditor_url,
+  const struct TALER_AuditorPublicKeyP *auditor_pub);
+
+
+/**
+ * Iterate over all available auditors for @a h, calling
+ * @a ac and giving it a chance to start a deposit
+ * confirmation interaction.
+ *
+ * @param keys the keys to go over auditors for
+ * @param ac function to call per auditor
+ * @param ac_cls closure for @a ac
+ */
+void
+TEAH_get_auditors_for_dc (
+  struct TALER_EXCHANGE_Keys *keys,
+  TEAH_AuditorCallback ac,
+  void *ac_cls);
+
+
+/* end of exchange_api_handle.h */
+#endif
diff --git a/src/lib/exchange_api_reserves_close.c 
b/src/lib/exchange_api_reserves_close.c
new file mode 100644
index 0000000..a3769a2
--- /dev/null
+++ b/src/lib/exchange_api_reserves_close.c
@@ -0,0 +1,373 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_close.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP close codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/close Handle
+ */
+struct TALER_EXCHANGE_ReservesCloseHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_ReservesCloseCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Public key of the reserve we are querying.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Our signature.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * When did we make the request.
+   */
+  struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK close code. Handle the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+                          const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveCloseResult rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("wire_amount",
+                                &rs.details.ok.wire_amount),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rch->cb (rch->cb_cls,
+           &rs);
+  rch->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle 
the JSON
+ * response.
+ *
+ * @param rch handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch,
+                           const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveCloseResult rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto (
+      "h_payto",
+      &rs.details.unavailable_for_legal_reasons.h_payto),
+    GNUNET_JSON_spec_uint64 (
+      "requirement_row",
+      &rs.details.unavailable_for_legal_reasons.requirement_row),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rch->cb (rch->cb_cls,
+           &rs);
+  rch->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/close request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_close_finished (void *cls,
+                                long response_code,
+                                const void *response)
+{
+  struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_ReserveCloseResult rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  rch->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_reserves_close_ok (rch,
+                                  j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* Insufficient balance to inquire for reserve close */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    if (GNUNET_OK !=
+        handle_reserves_close_kyc (rch,
+                                   j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for reserves close\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec);
+    break;
+  }
+  if (NULL != rch->cb)
+  {
+    rch->cb (rch->cb_cls,
+             &rs);
+    rch->cb = NULL;
+  }
+  TALER_EXCHANGE_reserves_close_cancel (rch);
+}
+
+
+struct TALER_EXCHANGE_ReservesCloseHandle *
+TALER_EXCHANGE_reserves_close (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const char *target_payto_uri,
+  TALER_EXCHANGE_ReservesCloseCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_ReservesCloseHandle *rch;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  struct TALER_PaytoHashP h_payto;
+
+  rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle);
+  rch->cb = cb;
+  rch->cb_cls = cb_cls;
+  rch->ts = GNUNET_TIME_timestamp_get ();
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &rch->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &rch->reserve_pub,
+      sizeof (rch->reserve_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/close",
+                     pub_str);
+  }
+  rch->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == rch->url)
+  {
+    GNUNET_free (rch);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (rch->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (rch->url);
+    GNUNET_free (rch);
+    return NULL;
+  }
+  if (NULL != target_payto_uri)
+    TALER_payto_hash (target_payto_uri,
+                      &h_payto);
+  TALER_wallet_reserve_close_sign (rch->ts,
+                                   (NULL != target_payto_uri)
+                                   ? &h_payto
+                                   : NULL,
+                                   reserve_priv,
+                                   &rch->reserve_sig);
+  {
+    json_t *close_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_string ("payto_uri",
+                                 target_payto_uri)),
+      GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                  rch->ts),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &rch->reserve_sig));
+
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&rch->post_ctx,
+                              eh,
+                              close_obj))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      json_decref (close_obj);
+      GNUNET_free (rch->url);
+      GNUNET_free (rch);
+      return NULL;
+    }
+    json_decref (close_obj);
+  }
+  rch->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   rch->post_ctx.headers,
+                                   &handle_reserves_close_finished,
+                                   rch);
+  return rch;
+}
+
+
+void
+TALER_EXCHANGE_reserves_close_cancel (
+  struct TALER_EXCHANGE_ReservesCloseHandle *rch)
+{
+  if (NULL != rch->job)
+  {
+    GNUNET_CURL_job_cancel (rch->job);
+    rch->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&rch->post_ctx);
+  GNUNET_free (rch->url);
+  GNUNET_free (rch);
+}
+
+
+/* end of exchange_api_reserves_close.c */
diff --git a/src/lib/exchange_api_reserves_get.c 
b/src/lib/exchange_api_reserves_get.c
new file mode 100644
index 0000000..7b59ba9
--- /dev/null
+++ b/src/lib/exchange_api_reserves_get.c
@@ -0,0 +1,266 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_get.c
+ * @brief Implementation of the GET /reserves/$RESERVE_PUB requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/ GET Handle
+ */
+struct TALER_EXCHANGE_ReservesGetHandle
+{
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_ReservesGetCallback cb;
+
+  /**
+   * Public key of the reserve we are querying.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rgh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_get_ok (struct TALER_EXCHANGE_ReservesGetHandle *rgh,
+                        const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveSummary rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &rs.details.ok.balance),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  rgh->cb (rgh->cb_cls,
+           &rs);
+  rgh->cb = NULL;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/ GET request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_get_finished (void *cls,
+                              long response_code,
+                              const void *response)
+{
+  struct TALER_EXCHANGE_ReservesGetHandle *rgh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_ReserveSummary rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  rgh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_reserves_get_ok (rgh,
+                                j))
+    {
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for GET %s\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec,
+                rgh->url);
+    break;
+  }
+  if (NULL != rgh->cb)
+  {
+    rgh->cb (rgh->cb_cls,
+             &rs);
+    rgh->cb = NULL;
+  }
+  TALER_EXCHANGE_reserves_get_cancel (rgh);
+}
+
+
+struct TALER_EXCHANGE_ReservesGetHandle *
+TALER_EXCHANGE_reserves_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct GNUNET_TIME_Relative timeout,
+  TALER_EXCHANGE_ReservesGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_ReservesGetHandle *rgh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16 + 32];
+
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+    char timeout_str[32];
+
+    end = GNUNET_STRINGS_data_to_string (
+      reserve_pub,
+      sizeof (*reserve_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (timeout_str,
+                     sizeof (timeout_str),
+                     "%llu",
+                     (unsigned long long)
+                     (timeout.rel_value_us
+                      / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us));
+    if (GNUNET_TIME_relative_is_zero (timeout))
+      GNUNET_snprintf (arg_str,
+                       sizeof (arg_str),
+                       "reserves/%s",
+                       pub_str);
+    else
+      GNUNET_snprintf (arg_str,
+                       sizeof (arg_str),
+                       "reserves/%s?timeout_ms=%s",
+                       pub_str,
+                       timeout_str);
+  }
+  rgh = GNUNET_new (struct TALER_EXCHANGE_ReservesGetHandle);
+  rgh->cb = cb;
+  rgh->cb_cls = cb_cls;
+  rgh->reserve_pub = *reserve_pub;
+  rgh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == rgh->url)
+  {
+    GNUNET_free (rgh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (rgh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (rgh->url);
+    GNUNET_free (rgh);
+    return NULL;
+  }
+  rgh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  &handle_reserves_get_finished,
+                                  rgh);
+  return rgh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_get_cancel (
+  struct TALER_EXCHANGE_ReservesGetHandle *rgh)
+{
+  if (NULL != rgh->job)
+  {
+    GNUNET_CURL_job_cancel (rgh->job);
+    rgh->job = NULL;
+  }
+  GNUNET_free (rgh->url);
+  GNUNET_free (rgh);
+}
+
+
+/* end of exchange_api_reserves_get.c */
diff --git a/src/lib/exchange_api_reserves_history.c 
b/src/lib/exchange_api_reserves_history.c
new file mode 100644
index 0000000..d4366eb
--- /dev/null
+++ b/src/lib/exchange_api_reserves_history.c
@@ -0,0 +1,363 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_history.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP history codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/history Handle
+ */
+struct TALER_EXCHANGE_ReservesHistoryHandle
+{
+
+  /**
+   * The keys of the exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_ReservesHistoryCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Public key of the reserve we are querying.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Our signature.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * When did we make the request.
+   */
+  struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK history code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_history_ok (struct TALER_EXCHANGE_ReservesHistoryHandle *rsh,
+                            const json_t *j)
+{
+  const json_t *history;
+  unsigned int len;
+  struct TALER_EXCHANGE_ReserveHistory rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK,
+    .ts = rsh->ts,
+    .reserve_sig = &rsh->reserve_sig
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &rs.details.ok.balance),
+    GNUNET_JSON_spec_array_const ("history",
+                                  &history),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+    rhistory = GNUNET_new_array (len,
+                                 struct TALER_EXCHANGE_ReserveHistoryEntry);
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_parse_reserve_history (rsh->keys,
+                                              history,
+                                              &rsh->reserve_pub,
+                                              rs.details.ok.balance.currency,
+                                              &rs.details.ok.total_in,
+                                              &rs.details.ok.total_out,
+                                              len,
+                                              rhistory))
+    {
+      GNUNET_break_op (0);
+      TALER_EXCHANGE_free_reserve_history (len,
+                                           rhistory);
+      return GNUNET_SYSERR;
+    }
+    if (NULL != rsh->cb)
+    {
+      rs.details.ok.history = rhistory;
+      rs.details.ok.history_len = len;
+      rsh->cb (rsh->cb_cls,
+               &rs);
+      rsh->cb = NULL;
+    }
+    TALER_EXCHANGE_free_reserve_history (len,
+                                         rhistory);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/history request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesHistoryHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_history_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
+{
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rsh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_ReserveHistory rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  rsh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_reserves_history_ok (rsh,
+                                    j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* Insufficient balance to inquire for reserve history */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for reserves history\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec);
+    break;
+  }
+  if (NULL != rsh->cb)
+  {
+    rsh->cb (rsh->cb_cls,
+             &rs);
+    rsh->cb = NULL;
+  }
+  TALER_EXCHANGE_reserves_history_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesHistoryHandle *
+TALER_EXCHANGE_reserves_history (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesHistoryCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  const struct TALER_EXCHANGE_GlobalFee *gf;
+
+  rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesHistoryHandle);
+  rsh->cb = cb;
+  rsh->cb_cls = cb_cls;
+  rsh->ts = GNUNET_TIME_timestamp_get ();
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &rsh->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &rsh->reserve_pub,
+      sizeof (rsh->reserve_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/history",
+                     pub_str);
+  }
+  rsh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == rsh->url)
+  {
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (rsh->url);
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  gf = TALER_EXCHANGE_get_global_fee (keys,
+                                      rsh->ts);
+  if (NULL == gf)
+  {
+    GNUNET_break_op (0);
+    curl_easy_cleanup (eh);
+    GNUNET_free (rsh->url);
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  TALER_wallet_reserve_history_sign (rsh->ts,
+                                     &gf->fees.history,
+                                     reserve_priv,
+                                     &rsh->reserve_sig);
+  {
+    json_t *history_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                  rsh->ts),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &rsh->reserve_sig));
+
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&rsh->post_ctx,
+                              eh,
+                              history_obj))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      json_decref (history_obj);
+      GNUNET_free (rsh->url);
+      GNUNET_free (rsh);
+      return NULL;
+    }
+    json_decref (history_obj);
+  }
+  rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+  rsh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   rsh->post_ctx.headers,
+                                   &handle_reserves_history_finished,
+                                   rsh);
+  return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_history_cancel (
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rsh)
+{
+  if (NULL != rsh->job)
+  {
+    GNUNET_CURL_job_cancel (rsh->job);
+    rsh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&rsh->post_ctx);
+  GNUNET_free (rsh->url);
+  TALER_EXCHANGE_keys_decref (rsh->keys);
+  GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_history.c */
diff --git a/src/lib/exchange_api_reserves_open.c 
b/src/lib/exchange_api_reserves_open.c
new file mode 100644
index 0000000..536efdb
--- /dev/null
+++ b/src/lib/exchange_api_reserves_open.c
@@ -0,0 +1,591 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_open.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP open codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_common.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * Information we keep per coin to validate the reply.
+ */
+struct CoinData
+{
+  /**
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Signature by the coin.
+   */
+  struct TALER_CoinSpendSignatureP coin_sig;
+
+  /**
+   * The hash of the denomination's public key
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * How much did this coin contribute.
+   */
+  struct TALER_Amount contribution;
+};
+
+
+/**
+ * @brief A /reserves/$RID/open Handle
+ */
+struct TALER_EXCHANGE_ReservesOpenHandle
+{
+
+  /**
+   * The keys of the exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_ReservesOpenCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Information we keep per coin to validate the reply.
+   */
+  struct CoinData *coins;
+
+  /**
+   * Length of the @e coins array.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Public key of the reserve we are querying.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Our signature.
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * When did we make the request.
+   */
+  struct GNUNET_TIME_Timestamp ts;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_ok (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+                         const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveOpenResult rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("open_cost",
+                                &rs.details.ok.open_cost),
+    GNUNET_JSON_spec_timestamp ("reserve_expiration",
+                                &rs.details.ok.expiration_time),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  roh->cb (roh->cb_cls,
+           &rs);
+  roh->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_PAYMENT_REQUIRED open code. Handle the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_pr (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+                         const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveOpenResult rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("open_cost",
+                                &rs.details.payment_required.open_cost),
+    GNUNET_JSON_spec_timestamp ("reserve_expiration",
+                                &rs.details.payment_required.expiration_time),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  roh->cb (roh->cb_cls,
+           &rs);
+  roh->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS open code. Handle 
the JSON
+ * response.
+ *
+ * @param roh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_open_kyc (struct TALER_EXCHANGE_ReservesOpenHandle *roh,
+                          const json_t *j)
+{
+  struct TALER_EXCHANGE_ReserveOpenResult rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto (
+      "h_payto",
+      &rs.details.unavailable_for_legal_reasons.h_payto),
+    GNUNET_JSON_spec_uint64 (
+      "requirement_row",
+      &rs.details.unavailable_for_legal_reasons.requirement_row),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  roh->cb (roh->cb_cls,
+           &rs);
+  roh->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/open request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesOpenHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_open_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_EXCHANGE_ReservesOpenHandle *roh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_ReserveOpenResult rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  roh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_reserves_open_ok (roh,
+                                 j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    json_dumpf (j,
+                stderr,
+                JSON_INDENT (2));
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_PAYMENT_REQUIRED:
+    if (GNUNET_OK !=
+        handle_reserves_open_pr (roh,
+                                 j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    {
+      const struct CoinData *cd = NULL;
+      struct TALER_CoinSpendPublicKeyP coin_pub;
+      const struct TALER_EXCHANGE_DenomPublicKey *dk;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &coin_pub),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL,
+                             NULL))
+      {
+        GNUNET_break_op (0);
+        rs.hr.http_status = 0;
+        rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      for (unsigned int i = 0; i<roh->num_coins; i++)
+      {
+        const struct CoinData *cdi = &roh->coins[i];
+
+        if (0 == GNUNET_memcmp (&coin_pub,
+                                &cdi->coin_pub))
+        {
+          cd = cdi;
+          break;
+        }
+      }
+      if (NULL == cd)
+      {
+        GNUNET_break_op (0);
+        rs.hr.http_status = 0;
+        rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      dk = TALER_EXCHANGE_get_denomination_key_by_hash (roh->keys,
+                                                        &cd->h_denom_pub);
+      if (NULL == dk)
+      {
+        GNUNET_break_op (0);
+        rs.hr.http_status = 0;
+        rs.hr.ec = TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR;
+        break;
+      }
+      if (GNUNET_OK !=
+          TALER_EXCHANGE_check_coin_conflict_ (roh->keys,
+                                               j,
+                                               dk,
+                                               &coin_pub,
+                                               &cd->coin_sig,
+                                               &cd->contribution))
+      {
+        GNUNET_break_op (0);
+        rs.hr.http_status = 0;
+        rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+      rs.hr.ec = TALER_JSON_get_error_code (j);
+      rs.hr.hint = TALER_JSON_get_error_hint (j);
+      break;
+    }
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    if (GNUNET_OK !=
+        handle_reserves_open_kyc (roh,
+                                  j))
+    {
+      GNUNET_break_op (0);
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for reserves open\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec);
+    break;
+  }
+  if (NULL != roh->cb)
+  {
+    roh->cb (roh->cb_cls,
+             &rs);
+    roh->cb = NULL;
+  }
+  TALER_EXCHANGE_reserves_open_cancel (roh);
+}
+
+
+struct TALER_EXCHANGE_ReservesOpenHandle *
+TALER_EXCHANGE_reserves_open (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_Amount *reserve_contribution,
+  unsigned int coin_payments_length,
+  const struct TALER_EXCHANGE_PurseDeposit coin_payments[
+    static coin_payments_length],
+  struct GNUNET_TIME_Timestamp expiration_time,
+  uint32_t min_purses,
+  TALER_EXCHANGE_ReservesOpenCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_ReservesOpenHandle *roh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  json_t *cpa;
+
+  roh = GNUNET_new (struct TALER_EXCHANGE_ReservesOpenHandle);
+  roh->cb = cb;
+  roh->cb_cls = cb_cls;
+  roh->ts = GNUNET_TIME_timestamp_get ();
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &roh->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &roh->reserve_pub,
+      sizeof (roh->reserve_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/open",
+                     pub_str);
+  }
+  roh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == roh->url)
+  {
+    GNUNET_free (roh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (roh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (roh->url);
+    GNUNET_free (roh);
+    return NULL;
+  }
+  TALER_wallet_reserve_open_sign (reserve_contribution,
+                                  roh->ts,
+                                  expiration_time,
+                                  min_purses,
+                                  reserve_priv,
+                                  &roh->reserve_sig);
+  roh->coins = GNUNET_new_array (coin_payments_length,
+                                 struct CoinData);
+  cpa = json_array ();
+  GNUNET_assert (NULL != cpa);
+  for (unsigned int i = 0; i<coin_payments_length; i++)
+  {
+    const struct TALER_EXCHANGE_PurseDeposit *pd = &coin_payments[i];
+    const struct TALER_AgeCommitmentProof *acp = pd->age_commitment_proof;
+    struct TALER_AgeCommitmentHash ahac;
+    struct TALER_AgeCommitmentHash *achp = NULL;
+    struct CoinData *cd = &roh->coins[i];
+    json_t *cp;
+
+    cd->contribution = pd->amount;
+    cd->h_denom_pub = pd->h_denom_pub;
+    if (NULL != acp)
+    {
+      TALER_age_commitment_hash (&acp->commitment,
+                                 &ahac);
+      achp = &ahac;
+    }
+    TALER_wallet_reserve_open_deposit_sign (&pd->amount,
+                                            &roh->reserve_sig,
+                                            &pd->coin_priv,
+                                            &cd->coin_sig);
+    GNUNET_CRYPTO_eddsa_key_get_public (&pd->coin_priv.eddsa_priv,
+                                        &cd->coin_pub.eddsa_pub);
+
+    cp = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_data_auto ("h_age_commitment",
+                                    achp)),
+      TALER_JSON_pack_amount ("amount",
+                              &pd->amount),
+      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                  &pd->h_denom_pub),
+      TALER_JSON_pack_denom_sig ("ub_sig",
+                                 &pd->denom_sig),
+      GNUNET_JSON_pack_data_auto ("coin_pub",
+                                  &cd->coin_pub),
+      GNUNET_JSON_pack_data_auto ("coin_sig",
+                                  &cd->coin_sig));
+    GNUNET_assert (0 ==
+                   json_array_append_new (cpa,
+                                          cp));
+  }
+  {
+    json_t *open_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                  roh->ts),
+      GNUNET_JSON_pack_timestamp ("reserve_expiration",
+                                  expiration_time),
+      GNUNET_JSON_pack_array_steal ("payments",
+                                    cpa),
+      TALER_JSON_pack_amount ("reserve_payment",
+                              reserve_contribution),
+      GNUNET_JSON_pack_uint64 ("purse_limit",
+                               min_purses),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &roh->reserve_sig));
+
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&roh->post_ctx,
+                              eh,
+                              open_obj))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      json_decref (open_obj);
+      GNUNET_free (roh->coins);
+      GNUNET_free (roh->url);
+      GNUNET_free (roh);
+      return NULL;
+    }
+    json_decref (open_obj);
+  }
+  roh->keys = TALER_EXCHANGE_keys_incref (keys);
+  roh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   roh->post_ctx.headers,
+                                   &handle_reserves_open_finished,
+                                   roh);
+  return roh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_open_cancel (
+  struct TALER_EXCHANGE_ReservesOpenHandle *roh)
+{
+  if (NULL != roh->job)
+  {
+    GNUNET_CURL_job_cancel (roh->job);
+    roh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&roh->post_ctx);
+  GNUNET_free (roh->coins);
+  GNUNET_free (roh->url);
+  TALER_EXCHANGE_keys_decref (roh->keys);
+  GNUNET_free (roh);
+}
+
+
+/* end of exchange_api_reserves_open.c */
diff --git a/src/lib/exchange_api_reserves_status.c 
b/src/lib/exchange_api_reserves_status.c
new file mode 100644
index 0000000..2ea64e8
--- /dev/null
+++ b/src/lib/exchange_api_reserves_status.c
@@ -0,0 +1,336 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_reserves_status.c
+ * @brief Implementation of the POST /reserves/$RESERVE_PUB/status requests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /reserves/$RID/status Handle
+ */
+struct TALER_EXCHANGE_ReservesStatusHandle
+{
+
+  /**
+   * The keys of the exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_ReservesStatusCallback cb;
+
+  /**
+   * Public key of the reserve we are querying.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+};
+
+
+/**
+ * We received an #MHD_HTTP_OK status code. Handle the JSON
+ * response.
+ *
+ * @param rsh handle of the request
+ * @param j JSON response
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+handle_reserves_status_ok (struct TALER_EXCHANGE_ReservesStatusHandle *rsh,
+                           const json_t *j)
+{
+  const json_t *history;
+  unsigned int len;
+  struct TALER_EXCHANGE_ReserveStatus rs = {
+    .hr.reply = j,
+    .hr.http_status = MHD_HTTP_OK
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &rs.details.ok.balance),
+    GNUNET_JSON_spec_array_const ("history",
+                                  &history),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (j,
+                         spec,
+                         NULL,
+                         NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+    rhistory = GNUNET_new_array (len,
+                                 struct TALER_EXCHANGE_ReserveHistoryEntry);
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_parse_reserve_history (rsh->keys,
+                                              history,
+                                              &rsh->reserve_pub,
+                                              rs.details.ok.balance.currency,
+                                              &rs.details.ok.total_in,
+                                              &rs.details.ok.total_out,
+                                              len,
+                                              rhistory))
+    {
+      GNUNET_break_op (0);
+      TALER_EXCHANGE_free_reserve_history (len,
+                                           rhistory);
+      GNUNET_JSON_parse_free (spec);
+      return GNUNET_SYSERR;
+    }
+    if (NULL != rsh->cb)
+    {
+      rs.details.ok.history = rhistory;
+      rs.details.ok.history_len = len;
+      rsh->cb (rsh->cb_cls,
+               &rs);
+      rsh->cb = NULL;
+    }
+    TALER_EXCHANGE_free_reserve_history (len,
+                                         rhistory);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RID/status request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_ReservesStatusHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserves_status_finished (void *cls,
+                                 long response_code,
+                                 const void *response)
+{
+  struct TALER_EXCHANGE_ReservesStatusHandle *rsh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_ReserveStatus rs = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  rsh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        handle_reserves_status_ok (rsh,
+                                   j))
+    {
+      rs.hr.http_status = 0;
+      rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    GNUNET_break (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, this should never
+       happen, we should pass the JSON reply to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    rs.hr.ec = TALER_JSON_get_error_code (j);
+    rs.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for reserves status\n",
+                (unsigned int) response_code,
+                (int) rs.hr.ec);
+    break;
+  }
+  if (NULL != rsh->cb)
+  {
+    rsh->cb (rsh->cb_cls,
+             &rs);
+    rsh->cb = NULL;
+  }
+  TALER_EXCHANGE_reserves_status_cancel (rsh);
+}
+
+
+struct TALER_EXCHANGE_ReservesStatusHandle *
+TALER_EXCHANGE_reserves_status (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_ReservesStatusCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  struct TALER_ReserveSignatureP reserve_sig;
+  struct GNUNET_TIME_Timestamp ts
+    = GNUNET_TIME_timestamp_get ();
+
+  rsh = GNUNET_new (struct TALER_EXCHANGE_ReservesStatusHandle);
+  rsh->cb = cb;
+  rsh->cb_cls = cb_cls;
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &rsh->reserve_pub.eddsa_pub);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &rsh->reserve_pub,
+      sizeof (rsh->reserve_pub),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/status",
+                     pub_str);
+  }
+  rsh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == rsh->url)
+  {
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (rsh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (rsh->url);
+    GNUNET_free (rsh);
+    return NULL;
+  }
+  TALER_wallet_reserve_status_sign (ts,
+                                    reserve_priv,
+                                    &reserve_sig);
+  {
+    json_t *status_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_timestamp ("request_timestamp",
+                                  ts),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &reserve_sig));
+
+    if (GNUNET_OK !=
+        TALER_curl_easy_post (&rsh->post_ctx,
+                              eh,
+                              status_obj))
+    {
+      GNUNET_break (0);
+      curl_easy_cleanup (eh);
+      json_decref (status_obj);
+      GNUNET_free (rsh->url);
+      GNUNET_free (rsh);
+      return NULL;
+    }
+    json_decref (status_obj);
+  }
+  rsh->keys = TALER_EXCHANGE_keys_incref (keys);
+  rsh->job = GNUNET_CURL_job_add2 (ctx,
+                                   eh,
+                                   rsh->post_ctx.headers,
+                                   &handle_reserves_status_finished,
+                                   rsh);
+  return rsh;
+}
+
+
+void
+TALER_EXCHANGE_reserves_status_cancel (
+  struct TALER_EXCHANGE_ReservesStatusHandle *rsh)
+{
+  if (NULL != rsh->job)
+  {
+    GNUNET_CURL_job_cancel (rsh->job);
+    rsh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&rsh->post_ctx);
+  GNUNET_free (rsh->url);
+  TALER_EXCHANGE_keys_decref (rsh->keys);
+  GNUNET_free (rsh);
+}
+
+
+/* end of exchange_api_reserves_status.c */
diff --git a/src/lib/exchange_api_transfers_get.c 
b/src/lib/exchange_api_transfers_get.c
new file mode 100644
index 0000000..14cf51f
--- /dev/null
+++ b/src/lib/exchange_api_transfers_get.c
@@ -0,0 +1,399 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_transfers_get.c
+ * @brief Implementation of the GET /transfers/ request
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A /transfers/ GET Handle
+ */
+struct TALER_EXCHANGE_TransfersGetHandle
+{
+
+  /**
+   * The keys of the exchange this request handle will use
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_TransfersGetCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+};
+
+
+/**
+ * We got a #MHD_HTTP_OK response for the /transfers/ request.
+ * Check that the response is well-formed and if it is, call the
+ * callback.  If not, return an error code.
+ *
+ * This code is very similar to
+ * merchant_api_track_transfer.c::check_transfers_get_response_ok.
+ * Any changes should likely be reflected there as well.
+ *
+ * @param wdh handle to the operation
+ * @param json response we got
+ * @return #GNUNET_OK if we are done and all is well,
+ *         #GNUNET_SYSERR if the response was bogus
+ */
+static enum GNUNET_GenericReturnValue
+check_transfers_get_response_ok (
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh,
+  const json_t *json)
+{
+  const json_t *details_j;
+  struct TALER_Amount total_expected;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+    .hr.reply = json,
+    .hr.http_status = MHD_HTTP_OK
+  };
+  struct TALER_EXCHANGE_TransferData *td
+    = &tgr.details.ok.td;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("total",
+                                &td->total_amount),
+    TALER_JSON_spec_amount_any ("wire_fee",
+                                &td->wire_fee),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_fixed_auto ("h_payto",
+                                 &td->h_payto),
+    GNUNET_JSON_spec_timestamp ("execution_time",
+                                &td->execution_time),
+    GNUNET_JSON_spec_array_const ("deposits",
+                                  &details_j),
+    GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                 &td->exchange_sig),
+    GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                 &td->exchange_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_amount_set_zero (td->total_amount.currency,
+                             &total_expected))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      TALER_EXCHANGE_test_signing_key (
+        wdh->keys,
+        &td->exchange_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  td->details_length = json_array_size (details_j);
+  {
+    struct GNUNET_HashContext *hash_context;
+    struct TALER_TrackTransferDetails *details;
+
+    details = GNUNET_new_array (td->details_length,
+                                struct TALER_TrackTransferDetails);
+    td->details = details;
+    hash_context = GNUNET_CRYPTO_hash_context_start ();
+    for (unsigned int i = 0; i<td->details_length; i++)
+    {
+      struct TALER_TrackTransferDetails *detail = &details[i];
+      struct json_t *detail_j = json_array_get (details_j, i);
+      struct GNUNET_JSON_Specification spec_detail[] = {
+        GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                     &detail->h_contract_terms),
+        GNUNET_JSON_spec_fixed_auto ("coin_pub", &detail->coin_pub),
+        TALER_JSON_spec_amount_any ("deposit_value", &detail->coin_value),
+        TALER_JSON_spec_amount_any ("deposit_fee", &detail->coin_fee),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if ( (GNUNET_OK !=
+            GNUNET_JSON_parse (detail_j,
+                               spec_detail,
+                               NULL, NULL)) ||
+           (GNUNET_OK !=
+            TALER_amount_cmp_currency (&total_expected,
+                                       &detail->coin_value)) ||
+           (GNUNET_OK !=
+            TALER_amount_cmp_currency (&total_expected,
+                                       &detail->coin_fee)) ||
+           (0 >
+            TALER_amount_add (&total_expected,
+                              &total_expected,
+                              &detail->coin_value)) ||
+           (0 >
+            TALER_amount_subtract (&total_expected,
+                                   &total_expected,
+                                   &detail->coin_fee)) )
+      {
+        GNUNET_break_op (0);
+        GNUNET_CRYPTO_hash_context_abort (hash_context);
+        GNUNET_free (details);
+        return GNUNET_SYSERR;
+      }
+      /* build up big hash for signature checking later */
+      TALER_exchange_online_wire_deposit_append (
+        hash_context,
+        &detail->h_contract_terms,
+        td->execution_time,
+        &detail->coin_pub,
+        &detail->coin_value,
+        &detail->coin_fee);
+    }
+    /* Check signature */
+    {
+      struct GNUNET_HashCode h_details;
+
+      GNUNET_CRYPTO_hash_context_finish (hash_context,
+                                         &h_details);
+      if (GNUNET_OK !=
+          TALER_exchange_online_wire_deposit_verify (
+            &td->total_amount,
+            &td->wire_fee,
+            &merchant_pub,
+            &td->h_payto,
+            &h_details,
+            &td->exchange_pub,
+            &td->exchange_sig))
+      {
+        GNUNET_break_op (0);
+        GNUNET_free (details);
+        return GNUNET_SYSERR;
+      }
+    }
+
+    if (0 >
+        TALER_amount_subtract (&total_expected,
+                               &total_expected,
+                               &td->wire_fee))
+    {
+      GNUNET_break_op (0);
+      GNUNET_free (details);
+      return GNUNET_SYSERR;
+    }
+    if (0 !=
+        TALER_amount_cmp (&total_expected,
+                          &td->total_amount))
+    {
+      GNUNET_break_op (0);
+      GNUNET_free (details);
+      return GNUNET_SYSERR;
+    }
+    wdh->cb (wdh->cb_cls,
+             &tgr);
+    GNUNET_free (details);
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /transfers/ request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_TransfersGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_transfers_get_finished (void *cls,
+                               long response_code,
+                               const void *response)
+{
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_TransfersGetResponse tgr = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  wdh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    tgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK ==
+        check_transfers_get_response_ok (wdh,
+                                         j))
+    {
+      TALER_EXCHANGE_transfers_get_cancel (wdh);
+      return;
+    }
+    GNUNET_break_op (0);
+    tgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    tgr.hr.http_status = 0;
+    break;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    tgr.hr.ec = TALER_JSON_get_error_code (j);
+    tgr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    tgr.hr.ec = TALER_JSON_get_error_code (j);
+    tgr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Exchange does not know about transaction;
+       we should pass the reply to the application */
+    tgr.hr.ec = TALER_JSON_get_error_code (j);
+    tgr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    tgr.hr.ec = TALER_JSON_get_error_code (j);
+    tgr.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    tgr.hr.ec = TALER_JSON_get_error_code (j);
+    tgr.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for transfers get\n",
+                (unsigned int) response_code,
+                (int) tgr.hr.ec);
+    break;
+  }
+  wdh->cb (wdh->cb_cls,
+           &tgr);
+  TALER_EXCHANGE_transfers_get_cancel (wdh);
+}
+
+
+struct TALER_EXCHANGE_TransfersGetHandle *
+TALER_EXCHANGE_transfers_get (
+  struct GNUNET_CURL_Context *ctx,
+  const char *url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  TALER_EXCHANGE_TransfersGetCallback cb,
+  void *cb_cls)
+{
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh;
+  CURL *eh;
+  char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
+
+  wdh = GNUNET_new (struct TALER_EXCHANGE_TransfersGetHandle);
+  wdh->cb = cb;
+  wdh->cb_cls = cb_cls;
+
+  {
+    char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (wtid,
+                                         sizeof (struct
+                                                 
TALER_WireTransferIdentifierRawP),
+                                         wtid_str,
+                                         sizeof (wtid_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "transfers/%s",
+                     wtid_str);
+  }
+  wdh->url = TALER_url_join (url,
+                             arg_str,
+                             NULL);
+  if (NULL == wdh->url)
+  {
+    GNUNET_free (wdh);
+    return NULL;
+  }
+  eh = TALER_EXCHANGE_curl_easy_get_ (wdh->url);
+  if (NULL == eh)
+  {
+    GNUNET_break (0);
+    GNUNET_free (wdh->url);
+    GNUNET_free (wdh);
+    return NULL;
+  }
+  wdh->keys = TALER_EXCHANGE_keys_incref (keys);
+  wdh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
+                                               eh,
+                                               &handle_transfers_get_finished,
+                                               wdh);
+  return wdh;
+}
+
+
+/**
+ * Cancel wire deposits request.  This function cannot be used on a request
+ * handle if a response is already served for it.
+ *
+ * @param wdh the wire deposits request handle
+ */
+void
+TALER_EXCHANGE_transfers_get_cancel (
+  struct TALER_EXCHANGE_TransfersGetHandle *wdh)
+{
+  if (NULL != wdh->job)
+  {
+    GNUNET_CURL_job_cancel (wdh->job);
+    wdh->job = NULL;
+  }
+  GNUNET_free (wdh->url);
+  TALER_EXCHANGE_keys_decref (wdh->keys);
+  GNUNET_free (wdh);
+}
+
+
+/* end of exchange_api_transfers_get.c */
diff --git a/src/lib/exchange_api_withdraw.c b/src/lib/exchange_api_withdraw.c
new file mode 100644
index 0000000..8721898
--- /dev/null
+++ b/src/lib/exchange_api_withdraw.c
@@ -0,0 +1,364 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_withdraw.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests with 
blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Withdraw Handle
+ */
+struct TALER_EXCHANGE_WithdrawHandle
+{
+
+  /**
+   * The curl context to use
+   */
+  struct GNUNET_CURL_Context *curl_ctx;
+
+  /**
+   * The base-URL to the exchange
+   */
+  const char *exchange_url;
+
+  /**
+   * The /keys material from the exchange
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * Handle for the actual (internal) withdraw operation.
+   */
+  struct TALER_EXCHANGE_Withdraw2Handle *wh2;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_WithdrawCallback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reserve private key.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Seed of the planchet.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   *  blinding secret
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Private key of the coin we are withdrawing.
+   */
+  struct TALER_CoinSpendPrivateKeyP priv;
+
+  /**
+   * Details of the planchet.
+   */
+  struct TALER_PlanchetDetail pd;
+
+  /**
+   * Values of the @cipher selected
+   */
+  struct TALER_ExchangeWithdrawValues alg_values;
+
+  /**
+   * Hash of the age commitment for this coin, if applicable. Maybe NULL
+   */
+  const struct TALER_AgeCommitmentHash *ach;
+
+  /**
+   * Denomination key we are withdrawing.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey pk;
+
+  /**
+   * Hash of the public key of the coin we are signing.
+   */
+  struct TALER_CoinPubHashP c_hash;
+
+  /**
+   * Handler for the CS R request (only used for TALER_DENOMINATION_CS 
denominations)
+   */
+  struct TALER_EXCHANGE_CsRWithdrawHandle *csrh;
+
+};
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
+ * @param w2r response data
+ */
+static void
+handle_reserve_withdraw_finished (
+  void *cls,
+  const struct TALER_EXCHANGE_Withdraw2Response *w2r)
+{
+  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
+  struct TALER_EXCHANGE_WithdrawResponse wr = {
+    .hr = w2r->hr
+  };
+
+  wh->wh2 = NULL;
+  switch (w2r->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      struct TALER_FreshCoin fc;
+
+      if (GNUNET_OK !=
+          TALER_planchet_to_coin (&wh->pk.key,
+                                  &w2r->details.ok.blind_sig,
+                                  &wh->bks,
+                                  &wh->priv,
+                                  wh->ach,
+                                  &wh->c_hash,
+                                  &wh->alg_values,
+                                  &fc))
+      {
+        wr.hr.http_status = 0;
+        wr.hr.ec = TALER_EC_EXCHANGE_WITHDRAW_UNBLIND_FAILURE;
+        break;
+      }
+      wr.details.ok.coin_priv = wh->priv;
+      wr.details.ok.bks = wh->bks;
+      wr.details.ok.sig = fc.sig;
+      wr.details.ok.exchange_vals = wh->alg_values;
+      break;
+    }
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    {
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_fixed_auto (
+          "h_payto",
+          &wr.details.unavailable_for_legal_reasons.h_payto),
+        GNUNET_JSON_spec_uint64 (
+          "requirement_row",
+          &wr.details.unavailable_for_legal_reasons.requirement_row),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (w2r->hr.reply,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        wr.hr.http_status = 0;
+        wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  if (MHD_HTTP_OK == w2r->hr.http_status)
+    TALER_denom_sig_free (&wr.details.ok.sig);
+  TALER_EXCHANGE_withdraw_cancel (wh);
+}
+
+
+/**
+ * Function called when stage 1 of CS withdraw is finished (request r_pub's)
+ *
+ * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
+ * @param csrr replies from the /csr-withdraw request
+ */
+static void
+withdraw_cs_stage_two_callback (
+  void *cls,
+  const struct TALER_EXCHANGE_CsRWithdrawResponse *csrr)
+{
+  struct TALER_EXCHANGE_WithdrawHandle *wh = cls;
+  struct TALER_EXCHANGE_WithdrawResponse wr = {
+    .hr = csrr->hr
+  };
+
+  wh->csrh = NULL;
+  GNUNET_assert (TALER_DENOMINATION_CS == wh->pk.key.cipher);
+  switch (csrr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    wh->alg_values = csrr->details.ok.alg_values;
+    TALER_planchet_setup_coin_priv (&wh->ps,
+                                    &wh->alg_values,
+                                    &wh->priv);
+    TALER_planchet_blinding_secret_create (&wh->ps,
+                                           &wh->alg_values,
+                                           &wh->bks);
+    /* This initializes the 2nd half of the
+       wh->pd.blinded_planchet! */
+    if (GNUNET_OK !=
+        TALER_planchet_prepare (&wh->pk.key,
+                                &wh->alg_values,
+                                &wh->bks,
+                                &wh->priv,
+                                wh->ach,
+                                &wh->c_hash,
+                                &wh->pd))
+    {
+      GNUNET_break (0);
+      break;
+    }
+    wh->wh2 = TALER_EXCHANGE_withdraw2 (wh->curl_ctx,
+                                        wh->exchange_url,
+                                        wh->keys,
+                                        &wh->pd,
+                                        wh->reserve_priv,
+                                        &handle_reserve_withdraw_finished,
+                                        wh);
+    return;
+  default:
+    break;
+  }
+  wh->cb (wh->cb_cls,
+          &wr);
+  TALER_EXCHANGE_withdraw_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_WithdrawHandle *
+TALER_EXCHANGE_withdraw (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_EXCHANGE_WithdrawCoinInput *wci,
+  TALER_EXCHANGE_WithdrawCallback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_WithdrawHandle *wh;
+
+  wh = GNUNET_new (struct TALER_EXCHANGE_WithdrawHandle);
+  wh->keys = TALER_EXCHANGE_keys_incref (keys);
+  wh->exchange_url = exchange_url;
+  wh->curl_ctx = curl_ctx;
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  wh->reserve_priv = reserve_priv;
+  wh->ps = *wci->ps;
+  wh->ach = wci->ach;
+  wh->pk = *wci->pk;
+  TALER_denom_pub_deep_copy (&wh->pk.key,
+                             &wci->pk->key);
+
+  switch (wci->pk->key.cipher)
+  {
+  case TALER_DENOMINATION_RSA:
+    {
+      wh->alg_values.cipher = TALER_DENOMINATION_RSA;
+      TALER_planchet_setup_coin_priv (&wh->ps,
+                                      &wh->alg_values,
+                                      &wh->priv);
+      TALER_planchet_blinding_secret_create (&wh->ps,
+                                             &wh->alg_values,
+                                             &wh->bks);
+      if (GNUNET_OK !=
+          TALER_planchet_prepare (&wh->pk.key,
+                                  &wh->alg_values,
+                                  &wh->bks,
+                                  &wh->priv,
+                                  wh->ach,
+                                  &wh->c_hash,
+                                  &wh->pd))
+      {
+        GNUNET_break (0);
+        GNUNET_free (wh);
+        return NULL;
+      }
+      wh->wh2 = TALER_EXCHANGE_withdraw2 (curl_ctx,
+                                          exchange_url,
+                                          keys,
+                                          &wh->pd,
+                                          wh->reserve_priv,
+                                          &handle_reserve_withdraw_finished,
+                                          wh);
+      break;
+    }
+  case TALER_DENOMINATION_CS:
+    {
+      TALER_cs_withdraw_nonce_derive (
+        &wh->ps,
+        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce);
+      /* Note that we only initialize the first half
+         of the blinded_planchet here; the other part
+         will be done after the /csr-withdraw request! */
+      wh->pd.blinded_planchet.cipher = TALER_DENOMINATION_CS;
+      wh->csrh = TALER_EXCHANGE_csr_withdraw (
+        curl_ctx,
+        exchange_url,
+        &wh->pk,
+        &wh->pd.blinded_planchet.details.cs_blinded_planchet.nonce,
+        &withdraw_cs_stage_two_callback,
+        wh);
+      break;
+    }
+  default:
+    GNUNET_break (0);
+    GNUNET_free (wh);
+    return NULL;
+  }
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_withdraw_cancel (struct TALER_EXCHANGE_WithdrawHandle *wh)
+{
+  TALER_blinded_planchet_free (&wh->pd.blinded_planchet);
+  if (NULL != wh->csrh)
+  {
+    TALER_EXCHANGE_csr_withdraw_cancel (wh->csrh);
+    wh->csrh = NULL;
+  }
+  if (NULL != wh->wh2)
+  {
+    TALER_EXCHANGE_withdraw2_cancel (wh->wh2);
+    wh->wh2 = NULL;
+  }
+  TALER_EXCHANGE_keys_decref (wh->keys);
+  TALER_denom_pub_free (&wh->pk.key);
+  GNUNET_free (wh);
+}
diff --git a/src/lib/exchange_api_withdraw2.c b/src/lib/exchange_api_withdraw2.c
new file mode 100644
index 0000000..6de7adc
--- /dev/null
+++ b/src/lib/exchange_api_withdraw2.c
@@ -0,0 +1,498 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file lib/exchange_api_withdraw2.c
+ * @brief Implementation of /reserves/$RESERVE_PUB/withdraw requests without 
blinding/unblinding
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include "exchange_api_handle.h"
+#include "taler_signatures.h"
+#include "exchange_api_curl_defaults.h"
+
+
+/**
+ * @brief A Withdraw Handle
+ */
+struct TALER_EXCHANGE_Withdraw2Handle
+{
+
+  /**
+   * The /keys material from the exchange
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * The url for this request.
+   */
+  char *url;
+
+  /**
+   * Handle for the request.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  TALER_EXCHANGE_Withdraw2Callback cb;
+
+  /**
+   * Closure for @a cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Context for #TEH_curl_easy_post(). Keeps the data that must
+   * persist for Curl to make the upload.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Total amount requested (value plus withdraw fee).
+   */
+  struct TALER_Amount requested_amount;
+
+  /**
+   * Public key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+};
+
+
+/**
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller.  The signature we
+ * get from the exchange is for the blinded value.  Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
+ *
+ * If everything checks out, we return the unblinded signature
+ * to the application via the callback.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_withdraw_ok (struct TALER_EXCHANGE_Withdraw2Handle *wh,
+                     const json_t *json)
+{
+  struct TALER_EXCHANGE_Withdraw2Response w2r = {
+    .hr.reply = json,
+    .hr.http_status = MHD_HTTP_OK
+  };
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_blinded_denom_sig ("ev_sig",
+                                       &w2r.details.ok.blind_sig),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* signature is valid, return it to the application */
+  wh->cb (wh->cb_cls,
+          &w2r);
+  /* make sure callback isn't called again after return */
+  wh->cb = NULL;
+  GNUNET_JSON_parse_free (spec);
+  return GNUNET_OK;
+}
+
+
+/**
+ * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw 
operation.
+ * Check the signatures on the withdraw transactions in the provided
+ * history and that the balances add up.  We don't do anything directly
+ * with the information, as the JSON will be returned to the application.
+ * However, our job is ensuring that the exchange followed the protocol, and
+ * this in particular means checking all of the signatures in the history.
+ *
+ * @param wh operation handle
+ * @param json reply from the exchange
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
+ */
+static enum GNUNET_GenericReturnValue
+reserve_withdraw_payment_required (
+  struct TALER_EXCHANGE_Withdraw2Handle *wh,
+  const json_t *json)
+{
+  struct TALER_Amount balance;
+  struct TALER_Amount total_in_from_history;
+  struct TALER_Amount total_out_from_history;
+  json_t *history;
+  size_t len;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("balance",
+                                &balance),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (json,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  history = json_object_get (json,
+                             "history");
+  if (NULL == history)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+
+  /* go over transaction history and compute
+     total incoming and outgoing amounts */
+  len = json_array_size (history);
+  {
+    struct TALER_EXCHANGE_ReserveHistoryEntry *rhistory;
+
+    /* Use heap allocation as "len" may be very big and thus this may
+       not fit on the stack. Use "GNUNET_malloc_large" as a malicious
+       exchange may theoretically try to crash us by giving a history
+       that does not fit into our memory. */
+    rhistory = GNUNET_malloc_large (
+      sizeof (struct TALER_EXCHANGE_ReserveHistoryEntry)
+      * len);
+    if (NULL == rhistory)
+    {
+      GNUNET_break (0);
+      return GNUNET_SYSERR;
+    }
+
+    if (GNUNET_OK !=
+        TALER_EXCHANGE_parse_reserve_history (wh->keys,
+                                              history,
+                                              &wh->reserve_pub,
+                                              balance.currency,
+                                              &total_in_from_history,
+                                              &total_out_from_history,
+                                              len,
+                                              rhistory))
+    {
+      GNUNET_break_op (0);
+      TALER_EXCHANGE_free_reserve_history (len,
+                                           rhistory);
+      return GNUNET_SYSERR;
+    }
+    TALER_EXCHANGE_free_reserve_history (len,
+                                         rhistory);
+  }
+
+  /* Check that funds were really insufficient */
+  if (0 >= TALER_amount_cmp (&wh->requested_amount,
+                             &balance))
+  {
+    /* Requested amount is smaller or equal to reported balance,
+       so this should not have failed. */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /reserves/$RESERVE_PUB/withdraw request.
+ *
+ * @param cls the `struct TALER_EXCHANGE_WithdrawHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response parsed JSON result, NULL on error
+ */
+static void
+handle_reserve_withdraw_finished (void *cls,
+                                  long response_code,
+                                  const void *response)
+{
+  struct TALER_EXCHANGE_Withdraw2Handle *wh = cls;
+  const json_t *j = response;
+  struct TALER_EXCHANGE_Withdraw2Response w2r = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  wh->job = NULL;
+  switch (response_code)
+  {
+  case 0:
+    w2r.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    break;
+  case MHD_HTTP_OK:
+    if (GNUNET_OK !=
+        reserve_withdraw_ok (wh,
+                             j))
+    {
+      GNUNET_break_op (0);
+      w2r.hr.http_status = 0;
+      w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+      break;
+    }
+    GNUNET_assert (NULL == wh->cb);
+    TALER_EXCHANGE_withdraw2_cancel (wh);
+    return;
+  case MHD_HTTP_BAD_REQUEST:
+    /* This should never happen, either us or the exchange is buggy
+       (or API version conflict); just pass JSON reply to the application */
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    GNUNET_break_op (0);
+    /* Nothing really to verify, exchange says one of the signatures is
+       invalid; as we checked them, this should never happen, we
+       should pass the JSON reply to the application */
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* Nothing really to verify, the exchange basically just says
+       that it doesn't know this reserve.  Can happen if we
+       query before the wire transfer went through.
+       We should simply pass the JSON reply to the application. */
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONFLICT:
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+
+    if (TALER_EC_EXCHANGE_RESERVES_AGE_RESTRICTION_REQUIRED == w2r.hr.ec)
+      break;
+
+    /* The exchange says that the reserve has insufficient funds;
+       check the signatures in the history... */
+    if (GNUNET_OK !=
+        reserve_withdraw_payment_required (wh,
+                                           j))
+    {
+      GNUNET_break_op (0);
+      w2r.hr.http_status = 0;
+      w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+    }
+    break;
+  case MHD_HTTP_GONE:
+    /* could happen if denomination was revoked */
+    /* Note: one might want to check /keys for revocation
+       signature here, alas tricky in case our /keys
+       is outdated => left to clients */
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* only validate reply is well-formed */
+    {
+      uint64_t ptu;
+      struct GNUNET_JSON_Specification spec[] = {
+        GNUNET_JSON_spec_uint64 ("requirement_row",
+                                 &ptu),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (j,
+                             spec,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        w2r.hr.http_status = 0;
+        w2r.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        break;
+      }
+    }
+    break;
+  case MHD_HTTP_INTERNAL_SERVER_ERROR:
+    /* Server had an internal issue; we should retry, but this API
+       leaves this to the application */
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    w2r.hr.ec = TALER_JSON_get_error_code (j);
+    w2r.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for exchange withdraw\n",
+                (unsigned int) response_code,
+                (int) w2r.hr.ec);
+    break;
+  }
+  if (NULL != wh->cb)
+  {
+    wh->cb (wh->cb_cls,
+            &w2r);
+    wh->cb = NULL;
+  }
+  TALER_EXCHANGE_withdraw2_cancel (wh);
+}
+
+
+struct TALER_EXCHANGE_Withdraw2Handle *
+TALER_EXCHANGE_withdraw2 (
+  struct GNUNET_CURL_Context *curl_ctx,
+  const char *exchange_url,
+  struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_PlanchetDetail *pd,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  TALER_EXCHANGE_Withdraw2Callback res_cb,
+  void *res_cb_cls)
+{
+  struct TALER_EXCHANGE_Withdraw2Handle *wh;
+  const struct TALER_EXCHANGE_DenomPublicKey *dk;
+  struct TALER_ReserveSignatureP reserve_sig;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
+  struct TALER_BlindedCoinHashP bch;
+
+  GNUNET_assert (NULL != keys);
+  dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                    &pd->denom_pub_hash);
+  if (NULL == dk)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  wh = GNUNET_new (struct TALER_EXCHANGE_Withdraw2Handle);
+  wh->keys = TALER_EXCHANGE_keys_incref (keys);
+  wh->cb = res_cb;
+  wh->cb_cls = res_cb_cls;
+  /* Compute how much we expected to charge to the reserve */
+  if (0 >
+      TALER_amount_add (&wh->requested_amount,
+                        &dk->value,
+                        &dk->fees.withdraw))
+  {
+    /* Overflow here? Very strange, our CPU must be fried... */
+    GNUNET_break (0);
+    GNUNET_free (wh);
+    return NULL;
+  }
+
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &wh->reserve_pub.eddsa_pub);
+
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (
+      &wh->reserve_pub,
+      sizeof (struct TALER_ReservePublicKeyP),
+      pub_str,
+      sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "reserves/%s/withdraw",
+                     pub_str);
+  }
+
+  if (GNUNET_OK !=
+      TALER_coin_ev_hash (&pd->blinded_planchet,
+                          &pd->denom_pub_hash,
+                          &bch))
+  {
+    GNUNET_break (0);
+    GNUNET_free (wh);
+    return NULL;
+  }
+
+  TALER_wallet_withdraw_sign (&pd->denom_pub_hash,
+                              &wh->requested_amount,
+                              &bch,
+                              reserve_priv,
+                              &reserve_sig);
+  {
+    json_t *withdraw_obj = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("denom_pub_hash",
+                                  &pd->denom_pub_hash),
+      TALER_JSON_pack_blinded_planchet ("coin_ev",
+                                        &pd->blinded_planchet),
+      GNUNET_JSON_pack_data_auto ("reserve_sig",
+                                  &reserve_sig));
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Attempting to withdraw from reserve %s\n",
+                TALER_B2S (&wh->reserve_pub));
+    wh->url = TALER_url_join (exchange_url,
+                              arg_str,
+                              NULL);
+    if (NULL == wh->url)
+    {
+      json_decref (withdraw_obj);
+      GNUNET_free (wh);
+      return NULL;
+    }
+    {
+      CURL *eh;
+
+      eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
+      if ( (NULL == eh) ||
+           (GNUNET_OK !=
+            TALER_curl_easy_post (&wh->post_ctx,
+                                  eh,
+                                  withdraw_obj)) )
+      {
+        GNUNET_break (0);
+        if (NULL != eh)
+          curl_easy_cleanup (eh);
+        json_decref (withdraw_obj);
+        GNUNET_free (wh->url);
+        GNUNET_free (wh);
+        return NULL;
+      }
+      json_decref (withdraw_obj);
+      wh->job = GNUNET_CURL_job_add2 (curl_ctx,
+                                      eh,
+                                      wh->post_ctx.headers,
+                                      &handle_reserve_withdraw_finished,
+                                      wh);
+    }
+  }
+  return wh;
+}
+
+
+void
+TALER_EXCHANGE_withdraw2_cancel (struct TALER_EXCHANGE_Withdraw2Handle *wh)
+{
+  if (NULL != wh->job)
+  {
+    GNUNET_CURL_job_cancel (wh->job);
+    wh->job = NULL;
+  }
+  GNUNET_free (wh->url);
+  TALER_curl_easy_post_finished (&wh->post_ctx);
+  TALER_EXCHANGE_keys_decref (wh->keys);
+  GNUNET_free (wh);
+}
diff --git a/src/testing/.gitignore b/src/testing/.gitignore
new file mode 100644
index 0000000..7bdd9a1
--- /dev/null
+++ b/src/testing/.gitignore
@@ -0,0 +1,59 @@
+test_auditor_api_version_cs
+test_auditor_api_version_rsa
+test_bank_api_with_fakebank
+test_bank_api_with_fakebank_twisted
+test_bank_api_with_pybank
+test_bank_api_with_pybank_twisted
+test_taler_exchange_aggregator-postgres
+test_taler_exchange_wirewatch-postgres
+test_exchange_api_revocation_cs
+test_exchange_api_revocation_rsa
+test_exchange_api_age_restriction_cs
+test_exchange_api_age_restriction_rsa
+report*
+test_exchange_management_api_cs
+test_exchange_management_api_rsa
+test_exchange_api_home/.local/share/taler/crypto-eddsa/
+test_exchange_api_home/.local/share/taler/crypto-rsa/
+test_exchange_api_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_exchange_api_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_exchange_api_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/crypto-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/crypto-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/crypto-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/secm_tofus.priv
+test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/taler-exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/crypto-rsa/
+test_exchange_api_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_home/.local/share/taler/exchange-secmod-cs/
+test_exchange_api_home/.local/share/taler/exchange-secmod-eddsa/
+test_exchange_api_home/.local/share/taler/exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-cs/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-eddsa/
+test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-secmod-rsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/secm_tofus.pub
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-cs/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-eddsa/
+test_taler_exchange_httpd_home/.local/share/taler/exchange-secmod-rsa/
+test_kyc_api
+test_helper_cs_home/
+test_helper_rsa_home/test_exchange_api_twisted-cs
+test_exchange_api_twisted-rsa
+test_exchange_api_twisted_cs
+test_exchange_api_twisted_rsa
+test_exchange_p2p_cs
+test_exchange_p2p_rsa
+*.edited
+tmp-last-response.*
+test_exchange_api_home/taler/auditor/
+test_exchange_api_home/taler/exchange-offline/secm_tofus.pub
+test_exchange_api_home/taler/exchange-secmod-cs/
+test_exchange_api_home/taler/exchange-secmod-eddsa/
+test_exchange_api_home/taler/exchange-secmod-rsa/
+test_exchange_api_keys_cherry_picking_home/taler/
+test_taler_exchange_httpd_home/taler/
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
new file mode 100644
index 0000000..957f028
--- /dev/null
+++ b/src/testing/Makefile.am
@@ -0,0 +1,589 @@
+# This Makefile.am is in the public domain
+
+AM_CPPFLAGS = \
+  -I$(top_srcdir)/src/include \
+  $(LIBGCRYPT_CFLAGS) \
+  $(POSTGRESQL_CPPFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+clean-local:
+       rm -rf report*
+
+bin_SCRIPTS = \
+  taler-unified-setup.sh
+
+# Libraries
+
+lib_LTLIBRARIES = \
+  libtalertesting.la
+
+if HAVE_TWISTER
+lib_LTLIBRARIES += libtalertwistertesting.la
+libtalertwistertesting_la_SOURCES = \
+  testing_api_twister_helpers.c \
+  testing_api_cmd_twister_exec_client.c
+libtalertwistertesting_la_LIBADD = \
+  -lgnunetutil \
+  libtalertesting.la \
+  -ltalertwister \
+  $(XLIB)
+libtalertwistertesting_la_LDFLAGS = \
+  $(GN_LIB_LDFLAGS) $(WINFLAGS) \
+  -version-info 0:0:0
+endif
+
+libtalertesting_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+libtalertesting_la_SOURCES = \
+  testing_api_cmd_age_withdraw.c \
+  testing_api_cmd_auditor_add_denom_sig.c \
+  testing_api_cmd_auditor_add.c \
+  testing_api_cmd_auditor_del.c \
+  testing_api_cmd_auditor_deposit_confirmation.c \
+  testing_api_cmd_auditor_exchanges.c \
+  testing_api_cmd_auditor_exec_auditor.c \
+  testing_api_cmd_auditor_exec_auditor_dbinit.c \
+  testing_api_cmd_bank_admin_add_incoming.c \
+  testing_api_cmd_bank_check.c \
+  testing_api_cmd_bank_admin_check.c \
+  testing_api_cmd_bank_check_empty.c \
+  testing_api_cmd_bank_history_credit.c \
+  testing_api_cmd_bank_history_debit.c \
+  testing_api_cmd_bank_transfer.c \
+  testing_api_cmd_batch.c \
+  testing_api_cmd_batch_deposit.c \
+  testing_api_cmd_batch_withdraw.c \
+  testing_api_cmd_check_aml_decision.c \
+  testing_api_cmd_check_aml_decisions.c \
+  testing_api_cmd_common.c \
+  testing_api_cmd_contract_get.c \
+  testing_api_cmd_deposit.c \
+  testing_api_cmd_deposits_get.c \
+  testing_api_cmd_exec_aggregator.c \
+  testing_api_cmd_exec_auditor-offline.c \
+  testing_api_cmd_exec_closer.c \
+  testing_api_cmd_exec_expire.c \
+  testing_api_cmd_exec_router.c \
+  testing_api_cmd_exec_transfer.c \
+  testing_api_cmd_exec_wget.c \
+  testing_api_cmd_exec_wirewatch.c \
+  testing_api_cmd_get_auditor.c \
+  testing_api_cmd_get_exchange.c \
+  testing_api_cmd_insert_deposit.c \
+  testing_api_cmd_kyc_check_get.c \
+  testing_api_cmd_kyc_proof.c \
+  testing_api_cmd_kyc_wallet_get.c \
+  testing_api_cmd_nexus_fetch_transactions.c \
+  testing_api_cmd_oauth.c \
+  testing_api_cmd_offline_sign_global_fees.c \
+  testing_api_cmd_offline_sign_wire_fees.c \
+  testing_api_cmd_offline_sign_keys.c \
+  testing_api_cmd_offline_sign_extensions.c \
+  testing_api_cmd_purse_create_deposit.c \
+  testing_api_cmd_purse_delete.c \
+  testing_api_cmd_purse_deposit.c \
+  testing_api_cmd_purse_get.c \
+  testing_api_cmd_purse_merge.c \
+  testing_api_cmd_recoup.c \
+  testing_api_cmd_recoup_refresh.c \
+  testing_api_cmd_refund.c \
+  testing_api_cmd_refresh.c \
+  testing_api_cmd_reserve_attest.c \
+  testing_api_cmd_reserve_close.c \
+  testing_api_cmd_reserve_get.c \
+  testing_api_cmd_reserve_get_attestable.c \
+  testing_api_cmd_reserve_history.c \
+  testing_api_cmd_reserve_open.c \
+  testing_api_cmd_reserve_purse.c \
+  testing_api_cmd_reserve_status.c \
+  testing_api_cmd_revoke.c \
+  testing_api_cmd_revoke_denom_key.c \
+  testing_api_cmd_revoke_sign_key.c \
+  testing_api_cmd_run_fakebank.c \
+  testing_api_cmd_set_officer.c \
+  testing_api_cmd_set_wire_fee.c \
+  testing_api_cmd_signal.c \
+  testing_api_cmd_sleep.c \
+  testing_api_cmd_stat.c \
+  testing_api_cmd_system_start.c \
+  testing_api_cmd_take_aml_decision.c \
+  testing_api_cmd_transfer_get.c \
+  testing_api_cmd_wait.c \
+  testing_api_cmd_wire_add.c \
+  testing_api_cmd_wire_del.c \
+  testing_api_cmd_withdraw.c \
+  testing_api_loop.c \
+  testing_api_misc.c \
+  testing_api_traits.c
+
+
+libtalertesting_la_LIBADD = \
+  $(top_builddir)/src/lib/libtalerauditor.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/mhd/libtalermhd.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  -lgnunetcurl \
+  -lgnunetjson \
+  -lgnunetutil \
+  -ljansson \
+  -lmicrohttpd \
+  $(XLIB)
+
+
+# Test cases
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+# removed for now, due to bug(s) in libeufin!
+noinst_PROGRAMS = \
+  test_bank_api_with_nexus
+
+.NOTPARALLEL:
+check_PROGRAMS = \
+  test_auditor_api_cs \
+  test_auditor_api_rsa \
+  test_auditor_api_version \
+  test_bank_api_with_fakebank \
+  test_exchange_api_cs \
+  test_exchange_api_rsa \
+  test_exchange_api_age_restriction_cs \
+  test_exchange_api_age_restriction_rsa \
+  test_exchange_api_keys_cherry_picking_cs \
+  test_exchange_api_keys_cherry_picking_rsa \
+  test_exchange_api_revocation_cs \
+  test_exchange_api_revocation_rsa \
+  test_exchange_api_overlapping_keys_bug_cs \
+  test_exchange_api_overlapping_keys_bug_rsa \
+  test_exchange_management_api_cs \
+  test_exchange_management_api_rsa \
+  test_kyc_api \
+  test_taler_exchange_aggregator-postgres \
+  test_taler_exchange_wirewatch-postgres \
+  test_exchange_p2p_cs \
+  test_exchange_p2p_rsa
+if HAVE_TWISTER
+  check_PROGRAMS += \
+    test_exchange_api_twisted_cs \
+    test_exchange_api_twisted_rsa \
+    test_bank_api_with_fakebank_twisted
+endif
+
+
+
+TESTS = \
+  $(check_PROGRAMS)
+
+test_auditor_api_cs_SOURCES = \
+  test_auditor_api.c
+test_auditor_api_cs_LDADD = \
+  $(top_builddir)/src/lib/libtalerauditor.la \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_auditor_api_rsa_SOURCES = \
+  test_auditor_api.c
+test_auditor_api_rsa_LDADD = \
+  $(top_builddir)/src/lib/libtalerauditor.la \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+
+test_auditor_api_version_SOURCES = \
+  test_auditor_api_version.c
+test_auditor_api_version_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerauditor.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_bank_api_with_nexus_SOURCES = \
+  test_bank_api.c
+test_bank_api_with_nexus_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  -lgnunetutil \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(XLIB)
+
+test_bank_api_with_fakebank_SOURCES = \
+  test_bank_api.c
+test_bank_api_with_fakebank_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  -lgnunetutil \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(XLIB)
+
+test_exchange_api_cs_SOURCES = \
+  test_exchange_api.c
+test_exchange_api_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_rsa_SOURCES = \
+  test_exchange_api.c
+test_exchange_api_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_age_restriction_cs_SOURCES = \
+  test_exchange_api_age_restriction.c
+test_exchange_api_age_restriction_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_age_restriction_rsa_SOURCES = \
+  test_exchange_api_age_restriction.c
+test_exchange_api_age_restriction_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_p2p_cs_SOURCES = \
+  test_exchange_p2p.c
+test_exchange_p2p_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_p2p_rsa_SOURCES = \
+  test_exchange_p2p.c
+test_exchange_p2p_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/extensions/libtalerextensions.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+
+test_exchange_api_keys_cherry_picking_cs_SOURCES = \
+  test_exchange_api_keys_cherry_picking.c
+test_exchange_api_keys_cherry_picking_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_keys_cherry_picking_rsa_SOURCES = \
+  test_exchange_api_keys_cherry_picking.c
+test_exchange_api_keys_cherry_picking_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_revocation_cs_SOURCES = \
+  test_exchange_api_revocation.c
+test_exchange_api_revocation_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_revocation_rsa_SOURCES = \
+  test_exchange_api_revocation.c
+test_exchange_api_revocation_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+
+test_exchange_api_overlapping_keys_bug_cs_SOURCES = \
+  test_exchange_api_overlapping_keys_bug.c
+test_exchange_api_overlapping_keys_bug_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_overlapping_keys_bug_rsa_SOURCES = \
+  test_exchange_api_overlapping_keys_bug.c
+test_exchange_api_overlapping_keys_bug_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_management_api_cs_SOURCES = \
+  test_exchange_management_api.c
+test_exchange_management_api_cs_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil \
+  $(XLIB)
+
+test_exchange_management_api_rsa_SOURCES = \
+  test_exchange_management_api.c
+test_exchange_management_api_rsa_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetutil \
+  $(XLIB)
+
+
+test_taler_exchange_aggregator_postgres_SOURCES = \
+  test_taler_exchange_aggregator.c
+test_taler_exchange_aggregator_postgres_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  libtalertesting.la \
+  -lmicrohttpd \
+  -lgnunetutil \
+  -lgnunetjson \
+  -ljansson \
+  -lpthread \
+  $(XLIB)
+
+test_taler_exchange_wirewatch_postgres_SOURCES = \
+  test_taler_exchange_wirewatch.c
+test_taler_exchange_wirewatch_postgres_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  libtalertesting.la \
+  -lmicrohttpd \
+  -lgnunetutil \
+  -lgnunetjson \
+  -lgnunetpq \
+  -ljansson \
+  -lpthread \
+  $(XLIB)
+
+test_exchange_api_twisted_cs_SOURCES = \
+  test_exchange_api_twisted.c
+test_exchange_api_twisted_cs_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  libtalertesting.la \
+  libtalertwistertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_exchange_api_twisted_rsa_SOURCES = \
+  test_exchange_api_twisted.c
+test_exchange_api_twisted_rsa_LDADD = \
+  $(LIBGCRYPT_LIBS) \
+  libtalertesting.la \
+  libtalertwistertesting.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_bank_api_with_fakebank_twisted_SOURCES = \
+  test_bank_api_twisted.c
+test_bank_api_with_fakebank_twisted_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  libtalertwistertesting.la \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+test_kyc_api_SOURCES = \
+  test_kyc_api.c
+test_kyc_api_LDADD = \
+  libtalertesting.la \
+  $(top_builddir)/src/lib/libtalerauditor.la \
+  $(top_builddir)/src/lib/libtalerexchange.la \
+  $(LIBGCRYPT_LIBS) \
+  $(top_builddir)/src/bank-lib/libtalerfakebank.la \
+  $(top_builddir)/src/bank-lib/libtalerbank.la \
+  $(top_builddir)/src/json/libtalerjson.la \
+  $(top_builddir)/src/util/libtalerutil.la \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson \
+  $(XLIB)
+
+# Distribution
+
+EXTRA_DIST = \
+  $(bin_SCRIPTS) \
+  coins-cs.conf \
+  coins-rsa.conf \
+  test_auditor_api-cs.conf \
+  test_auditor_api-rsa.conf \
+  test_auditor_api_expire_reserve_now-cs.conf \
+  test_auditor_api_expire_reserve_now-rsa.conf \
+  test_bank_api.conf \
+  test_bank_api_fakebank.conf \
+  test_bank_api_fakebank_twisted.conf \
+  test_bank_api_nexus.conf \
+  test_exchange_api_home/.config/taler/account-2.json \
+  test_exchange_api_home/.local/share/taler/exchange-offline/master.priv \
+  test_exchange_api_home/.local/share/taler/exchange/offline-keys/master.priv \
+  test_exchange_api_keys_cherry_picking_home/.config/taler/x-taler-bank.json \
+  
test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange-offline/master.priv
 \
+  
test_exchange_api_keys_cherry_picking_home/.local/share/taler/exchange/offline-keys/master.priv
 \
+  test_exchange_api.conf \
+  test_exchange_api-cs.conf \
+  test_exchange_api-rsa.conf \
+  test_exchange_api_age_restiction.conf \
+  test_exchange_api_age_restiction-cs.conf \
+  test_exchange_api_age_restiction-rsa.conf \
+  test_exchange_api_twisted.conf \
+  test_exchange_api_twisted-cs.conf \
+  test_exchange_api_twisted-rsa.conf \
+  test_exchange_api_keys_cherry_picking.conf \
+  test_exchange_api_keys_cherry_picking-cs.conf \
+  test_exchange_api_keys_cherry_picking-rsa.conf \
+  test_exchange_api_expire_reserve_now-cs.conf \
+  test_exchange_api_expire_reserve_now-rsa.conf \
+  test_taler_exchange_httpd_home/.config/taler/account-1.json \
+  
test_taler_exchange_httpd_home/.local/share/taler/exchange-offline/master.priv \
+  
test_taler_exchange_httpd_home/.local/share/taler/exchange/offline-keys/master.priv
 \
+  test-taler-exchange-aggregator-postgres.conf \
+  test-taler-exchange-wirewatch-postgres.conf \
+  test_kyc_api.conf
diff --git a/src/testing/coins-cs.conf b/src/testing/coins-cs.conf
new file mode 100644
index 0000000..92163ba
--- /dev/null
+++ b/src/testing/coins-cs.conf
@@ -0,0 +1,118 @@
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = CS
+
+
+[coin_eur_ct_1_age_restricted]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_ct_10_age_restricted]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_1_age_restricted]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_5_age_restricted]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
+
+[coin_eur_10_age_restricted]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+age_restricted = YES
+CIPHER = CS
diff --git a/src/testing/coins-rsa.conf b/src/testing/coins-rsa.conf
new file mode 100644
index 0000000..7a21a34
--- /dev/null
+++ b/src/testing/coins-rsa.conf
@@ -0,0 +1,128 @@
+# This file is in the public domain.
+
+# Sections starting with "coin_" specify which denominations
+# the exchange should support (and their respective fee structure)
+[coin_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_1]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_5]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_10]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[coin_eur_ct_1_age_restricted]
+value = EUR:0.01
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_ct_10_age_restricted]
+value = EUR:0.10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_1_age_restricted]
+value = EUR:1
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_5_age_restricted]
+value = EUR:5
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
+
+[coin_eur_10_age_restricted]
+value = EUR:10
+duration_withdraw = 7 days
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+rsa_keysize = 1024
+age_restricted = YES
+CIPHER = RSA
\ No newline at end of file
diff --git a/src/testing/test_exchange_api-cs.conf 
b/src/testing/test_exchange_api-cs.conf
new file mode 100644
index 0000000..b80696f
--- /dev/null
+++ b/src/testing/test_exchange_api-cs.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-cs.conf
+@INLINE@ test_exchange_api.conf
diff --git a/src/testing/test_exchange_api-rsa.conf 
b/src/testing/test_exchange_api-rsa.conf
new file mode 100644
index 0000000..351c876
--- /dev/null
+++ b/src/testing/test_exchange_api-rsa.conf
@@ -0,0 +1,4 @@
+# This file is in the public domain.
+#
+@INLINE@ coins-rsa.conf
+@INLINE@ test_exchange_api.conf
\ No newline at end of file
diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c
new file mode 100644
index 0000000..41b8301
--- /dev/null
+++ b/src/testing/test_exchange_api.c
@@ -0,0 +1,1295 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014--2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/test_exchange_api.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_testing_lib.h>
+#include <microhttpd.h>
+#include "taler_bank_service.h"
+#include "taler_fakebank_lib.h"
+#include "taler_testing_lib.h"
+#include "taler_extensions.h"
+
+/**
+ * Configuration file we use.  One (big) configuration is used
+ * for the various components for this test.
+ */
+static char *config_file;
+
+/**
+ * Special configuration file to use when we want reserves
+ * to expire 'immediately'.
+ */
+static char *config_file_expire_reserve_now;
+
+/**
+ * Our credentials.
+ */
+static struct TALER_TESTING_Credentials cred;
+
+/**
+ * Some tests behave differently when using CS as we cannot
+ * re-use the coin private key for different denominations
+ * due to the derivation of it with the /csr values. Hence
+ * some tests behave differently in CS mode, hence this
+ * flag.
+ */
+static bool uses_cs;
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+  TALER_TESTING_cmd_exec_wirewatch2 (label, config_file, "exchange-account-2")
+
+/**
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+  TALER_TESTING_cmd_sleep ("sleep-before-aggregator", 2), \
+  TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
+  TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)
+
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+  TALER_TESTING_cmd_admin_add_incoming (label, amount, \
+                                        &cred.ba,                \
+                                        cred.user42_payto)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ * @param is interpreter we use to run commands
+ */
+static void
+run (void *cls,
+     struct TALER_TESTING_Interpreter *is)
+{
+  /**
+   * Test withdrawal plus spending.
+   */
+  struct TALER_TESTING_Command withdraw[] = {
+    /**
+     * Move money to the exchange's bank account.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+                              "EUR:6.02"),
+    TALER_TESTING_cmd_reserve_poll ("poll-reserve-1",
+                                    "create-reserve-1",
+                                    "EUR:6.02",
+                                    GNUNET_TIME_UNIT_MINUTES,
+                                    MHD_HTTP_OK),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-1",
+                                                 "EUR:6.02",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "create-reserve-1"),
+    /**
+     * Make a reserve exist, according to the previous
+     * transfer.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-1"),
+    TALER_TESTING_cmd_reserve_poll_finish ("finish-poll-reserve-1",
+                                           GNUNET_TIME_UNIT_SECONDS,
+                                           "poll-reserve-1"),
+    /**
+     * Withdraw EUR:5.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /**
+     * Withdraw EUR:1 using the SAME private coin key as for the previous coin
+     * (in violation of the specification, to be detected on spending!).
+     * However, note that this does NOT work with 'CS', as for a different
+     * denomination we get different R0/R1 values from the exchange, and
+     * thus will generate a different coin private key as R0/R1 are hashed
+     * into the coin priv. So here, we fail to 'reuse' the key due to the
+     * cryptographic construction!
+     */
+    TALER_TESTING_cmd_withdraw_amount_reuse_key ("withdraw-coin-1x",
+                                                 "create-reserve-1",
+                                                 "EUR:1",
+                                                 0, /* age restriction off */
+                                                 "withdraw-coin-1",
+                                                 MHD_HTTP_OK),
+    /**
+     * Check the reserve is depleted.
+     */
+    TALER_TESTING_cmd_status ("status-1",
+                              "create-reserve-1",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    /*
+     * Try to overdraw.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_CONFLICT),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command spend[] = {
+    /**
+     * Spend the coin.
+     */
+    TALER_TESTING_cmd_deposit ("deposit-simple",
+                               "withdraw-coin-1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:5",
+                               MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay",
+                                      "deposit-simple",
+                                      MHD_HTTP_OK),
+    /* This creates a conflict, as we have the same coin public key (reuse!),
+       but different denomination public keys (which is not allowed).
+       However, note that this does NOT work with 'CS', as for a different
+       denomination we get different R0/R1 values from the exchange, and
+       thus will generate a different coin private key as R0/R1 are hashed
+       into the coin priv. So here, we fail to 'reuse' the key due to the
+       cryptographic construction! */
+    TALER_TESTING_cmd_deposit ("deposit-reused-coin-key-failure",
+                               "withdraw-coin-1x",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               uses_cs
+                               ? MHD_HTTP_OK
+                               : MHD_HTTP_CONFLICT),
+    /**
+     * Try to double spend using different wire details.
+     */
+    TALER_TESTING_cmd_deposit ("deposit-double-1",
+                               "withdraw-coin-1",
+                               0,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:5",
+                               MHD_HTTP_CONFLICT),
+    /* Try to double spend using a different transaction id.
+     * The test needs the contract terms to differ. This
+     * is currently the case because of the "timestamp" field,
+     * which is set automatically by #TALER_TESTING_cmd_deposit().
+     * This could theoretically fail if at some point a deposit
+     * command executes in less than 1 ms. *///
+    TALER_TESTING_cmd_deposit ("deposit-double-1",
+                               "withdraw-coin-1",
+                               0,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:5",
+                               MHD_HTTP_CONFLICT),
+    /**
+     * Try to double spend with different proposal.
+     */
+    TALER_TESTING_cmd_deposit ("deposit-double-2",
+                               "withdraw-coin-1",
+                               0,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":2}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:5",
+                               MHD_HTTP_CONFLICT),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command refresh[] = {
+    /**
+     * Try to melt the coin that shared the private key with another
+     * coin (should fail). Note that in the CS-case, we fail also
+     * with MHD_HTTP_CONFLICT, but for a different reason: here it
+     * is not a denomination conflict, but a double-spending conflict.
+     */
+    TALER_TESTING_cmd_melt ("refresh-melt-reused-coin-key-failure",
+                            "withdraw-coin-1x",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+
+    /* Fill reserve with EUR:5, 1ct is for fees. */
+    CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-1",
+                              "EUR:5.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("ck-refresh-create-reserve-1",
+                                                 "EUR:5.01",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "refresh-create-reserve-1"),
+    /**
+     * Make previous command effective.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-2"),
+    /**
+     * Withdraw EUR:5.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-1",
+                                       "refresh-create-reserve-1",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
+     * (in full) (merchant would receive EUR:0.99 due to 1 ct
+     * deposit fee)
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-partial",
+                               "refresh-withdraw-coin-1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Melt the rest of the coin's value
+     * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+    TALER_TESTING_cmd_melt_double ("refresh-melt-1",
+                                   "refresh-withdraw-coin-1",
+                                   MHD_HTTP_OK,
+                                   NULL),
+    /**
+     * Complete (successful) melt operation, and
+     * withdraw the coins
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1",
+                                      "refresh-melt-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Do it again to check idempotency
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-1-idempotency",
+                                      "refresh-melt-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Test that /refresh/link works
+     */
+    TALER_TESTING_cmd_refresh_link ("refresh-link-1",
+                                    "refresh-reveal-1",
+                                    MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1a",
+                               "refresh-reveal-1-idempotency",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:0.1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-1b",
+                               "refresh-reveal-1",
+                               3,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:0.1",
+                               MHD_HTTP_OK),
+    /* Test running a failing melt operation (same operation
+     * again must fail) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing",
+                            "refresh-withdraw-coin-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+    /* Test running a failing melt operation (on a coin that
+       was itself revealed and subsequently deposited) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing-2",
+                            "refresh-reveal-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+
+    TALER_TESTING_cmd_end ()
+  };
+
+  /**
+   * Test withdrawal with age restriction.  Success is expected, so it MUST be
+   * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called,
+   * i. e. age restriction is activated in the exchange!
+   *
+   * TODO: create a test that tries to withdraw coins with age restriction but
+   * (expectedly) fails because the exchange doesn't support age restriction
+   * yet.
+   */
+  struct TALER_TESTING_Command withdraw_age[] = {
+    /**
+     * Move money to the exchange's bank account.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age",
+                              "EUR:6.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age",
+                                                 "EUR:6.01",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "create-reserve-age"),
+    /**
+     * Make a reserve exist, according to the previous
+     * transfer.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-age"),
+    /**
+     * Withdraw EUR:5.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age-1",
+                                       "create-reserve-age",
+                                       "EUR:5",
+                                       13,
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command spend_age[] = {
+    /**
+     * Spend the coin.
+     */
+    TALER_TESTING_cmd_deposit ("deposit-simple-age",
+                               "withdraw-coin-age-1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:4.99",
+                               MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit_replay ("deposit-simple-replay-age",
+                                      "deposit-simple-age",
+                                      MHD_HTTP_OK),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command track[] = {
+    /* Try resolving a deposit's WTID, as we never triggered
+     * execution of transactions, the answer should be that
+     * the exchange knows about the deposit, but has no WTID yet.
+     */
+    TALER_TESTING_cmd_track_transaction ("deposit-wtid-found",
+                                         "deposit-simple",
+                                         0,
+                                         MHD_HTTP_ACCEPTED,
+                                         NULL),
+    /* Try resolving a deposit's WTID for a failed deposit.
+     * As the deposit failed, the answer should be that the
+     * exchange does NOT know about the deposit.
+     */
+    TALER_TESTING_cmd_track_transaction ("deposit-wtid-failing",
+                                         "deposit-double-2",
+                                         0,
+                                         MHD_HTTP_NOT_FOUND,
+                                         NULL),
+    /* Try resolving an undefined (all zeros) WTID; this
+     * should fail as obviously the exchange didn't use that
+     * WTID value for any transaction.
+     */
+    TALER_TESTING_cmd_track_transfer_empty ("wire-deposit-failing",
+                                            NULL,
+                                            MHD_HTTP_NOT_FOUND),
+    /* Run transfers. Note that _actual_ aggregation will NOT
+     * happen here, as each deposit operation is run with a
+     * fresh merchant public key, so the aggregator will treat
+     * them as "different" merchants and do the wire transfers
+     * individually. */
+    CMD_EXEC_AGGREGATOR ("run-aggregator"),
+    /**
+     * Check all the transfers took place.
+     */
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c",
+                                           cred.exchange_url,
+                                           "EUR:4.98",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-499c2",
+                                           cred.exchange_url,
+                                           "EUR:4.97",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c1",
+                                           cred.exchange_url,
+                                           "EUR:0.98",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c2",
+                                           cred.exchange_url,
+                                           "EUR:0.98",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c3",
+                                           cred.exchange_url,
+                                           "EUR:0.98",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-99c4",
+                                           cred.exchange_url,
+                                           "EUR:0.98",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c",
+                                           cred.exchange_url,
+                                           "EUR:0.08",
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-08c2",
+                                           cred.exchange_url,
+                                           "EUR:0.08",
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
+    /* In case of CS, one transaction above succeeded that
+       failed for RSA, hence we need to check for an extra transfer here */
+    uses_cs
+    ? TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-98c",
+                                             cred.exchange_url,
+                                             "EUR:0.98",
+                                             cred.exchange_payto,
+                                             cred.user42_payto)
+    : TALER_TESTING_cmd_sleep ("dummy",
+                               0),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+    TALER_TESTING_cmd_track_transaction ("deposit-wtid-ok",
+                                         "deposit-simple",
+                                         0,
+                                         MHD_HTTP_OK,
+                                         "check_bank_transfer-499c"),
+    TALER_TESTING_cmd_track_transfer ("wire-deposit-success-bank",
+                                      "check_bank_transfer-99c1",
+                                      MHD_HTTP_OK,
+                                      "EUR:0.98",
+                                      "EUR:0.01"),
+    TALER_TESTING_cmd_track_transfer ("wire-deposits-success-wtid",
+                                      "deposit-wtid-ok",
+                                      MHD_HTTP_OK,
+                                      "EUR:4.98",
+                                      "EUR:0.01"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  /**
+   * This block checks whether a wire deadline
+   * very far in the future does NOT get aggregated now.
+   */
+  struct TALER_TESTING_Command unaggregation[] = {
+    TALER_TESTING_cmd_check_bank_empty ("far-future-aggregation-a"),
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregated",
+                              "EUR:5.01"),
+    /* "consume" reserve creation transfer.  */
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "check-create-reserve-unaggregated",
+      "EUR:5.01",
+      cred.user42_payto,
+      cred.exchange_payto,
+      "create-reserve-unaggregated"),
+    CMD_EXEC_WIREWATCH ("wirewatch-unaggregated"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregated",
+                                       "create-reserve-unaggregated",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit ("deposit-unaggregated",
+                               "withdraw-coin-unaggregated",
+                               0,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_relative_multiply (
+                                 GNUNET_TIME_UNIT_YEARS,
+                                 3000),
+                               "EUR:5",
+                               MHD_HTTP_OK),
+    CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
+
+    TALER_TESTING_cmd_check_bank_empty (
+      "far-future-aggregation-b"),
+
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command refresh_age[] = {
+    /* Fill reserve with EUR:5, 1ct is for fees. */
+    CMD_TRANSFER_TO_EXCHANGE ("refresh-create-reserve-age-1",
+                              "EUR:6.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "ck-refresh-create-reserve-age-1",
+      "EUR:6.01",
+      cred.user42_payto,
+      cred.exchange_payto,
+      "refresh-create-reserve-age-1"),
+    /**
+     * Make previous command effective.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-age-2"),
+    /**
+     * Withdraw EUR:7 with age restriction for age 13.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("refresh-withdraw-coin-age-1",
+                                       "refresh-create-reserve-age-1",
+                                       "EUR:5",
+                                       13,
+                                       MHD_HTTP_OK),
+    /* Try to partially spend (deposit) 1 EUR of the 5 EUR coin
+     * (in full) (merchant would receive EUR:0.99 due to 1 ct
+     * deposit fee)
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-partial-age",
+                               "refresh-withdraw-coin-age-1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:1\"}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Melt the rest of the coin's value
+     * (EUR:4.00 = 3x EUR:1.03 + 7x EUR:0.13) */
+    TALER_TESTING_cmd_melt_double ("refresh-melt-age-1",
+                                   "refresh-withdraw-coin-age-1",
+                                   MHD_HTTP_OK,
+                                   NULL),
+    /**
+     * Complete (successful) melt operation, and
+     * withdraw the coins
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1",
+                                      "refresh-melt-age-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Do it again to check idempotency
+     */
+    TALER_TESTING_cmd_refresh_reveal ("refresh-reveal-age-1-idempotency",
+                                      "refresh-melt-age-1",
+                                      MHD_HTTP_OK),
+    /**
+     * Test that /refresh/link works
+     */
+    TALER_TESTING_cmd_refresh_link ("refresh-link-age-1",
+                                    "refresh-reveal-age-1",
+                                    MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1a",
+                               "refresh-reveal-age-1-idempotency",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_OK),
+    /**
+     * Try to spend a refreshed EUR:0.1 coin
+     */
+    TALER_TESTING_cmd_deposit ("refresh-deposit-refreshed-age-1b",
+                               "refresh-reveal-age-1",
+                               3,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":3}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:0.1",
+                               MHD_HTTP_OK),
+    /* Test running a failing melt operation (same operation
+     * again must fail) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing-age",
+                            "refresh-withdraw-coin-age-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+    /* Test running a failing melt operation (on a coin that
+       was itself revealed and subsequently deposited) */
+    TALER_TESTING_cmd_melt ("refresh-melt-failing-age-2",
+                            "refresh-reveal-age-1",
+                            MHD_HTTP_CONFLICT,
+                            NULL),
+    TALER_TESTING_cmd_end ()
+  };
+
+  /**
+   * This block exercises the aggretation logic by making two payments
+   * to the same merchant.
+   */
+  struct TALER_TESTING_Command aggregation[] = {
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-aggtest",
+                              "EUR:5.01"),
+    /* "consume" reserve creation transfer.  */
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "check-create-reserve-aggtest",
+      "EUR:5.01",
+      cred.user42_payto,
+      cred.exchange_payto,
+      "create-reserve-aggtest"),
+    CMD_EXEC_WIREWATCH ("wirewatch-aggtest"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-aggtest",
+                                       "create-reserve-aggtest",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit ("deposit-aggtest-1",
+                               "withdraw-coin-aggtest",
+                               0,
+                               cred.user43_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:2",
+                               MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit_with_ref ("deposit-aggtest-2",
+                                        "withdraw-coin-aggtest",
+                                        0,
+                                        cred.user43_payto,
+                                        "{\"items\":[{\"name\":\"foo 
bar\",\"value\":1}]}",
+                                        GNUNET_TIME_UNIT_ZERO,
+                                        "EUR:2",
+                                        MHD_HTTP_OK,
+                                        "deposit-aggtest-1"),
+    CMD_EXEC_AGGREGATOR ("aggregation-aggtest"),
+    TALER_TESTING_cmd_check_bank_transfer ("check-bank-transfer-aggtest",
+                                           cred.exchange_url,
+                                           "EUR:3.97",
+                                           cred.exchange_payto,
+                                           cred.user43_payto),
+    TALER_TESTING_cmd_check_bank_empty ("check-bank-empty-aggtest"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command refund[] = {
+    /**
+     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
+     * config.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-r1",
+                              "EUR:5.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-r1",
+                                                 "EUR:5.01",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "create-reserve-r1"),
+    /**
+     * Run wire-watch to trigger the reserve creation.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-3"),
+    /* Withdraw a 5 EUR coin, at fee of 1 ct */
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-r1",
+                                       "create-reserve-r1",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /**
+     * Spend 5 EUR of the 5 EUR coin (in full) (merchant would
+     * receive EUR:4.99 due to 1 ct deposit fee)
+     */
+    TALER_TESTING_cmd_deposit ("deposit-refund-1",
+                               "withdraw-coin-r1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:5\"}]}",
+                               GNUNET_TIME_UNIT_MINUTES,
+                               "EUR:5",
+                               MHD_HTTP_OK),
+    /**
+     * Run transfers. Should do nothing as refund deadline blocks it
+     */
+    CMD_EXEC_AGGREGATOR ("run-aggregator-refund"),
+    /* Check that aggregator didn't do anything, as expected.
+     * Note, this operation takes two commands: one to "flush"
+     * the preliminary transfer (used to withdraw) from the
+     * fakebank and the second to actually check there are not
+     * other transfers around. */
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_transfer-pre-refund"),
+    TALER_TESTING_cmd_refund_with_id ("refund-ok",
+                                      MHD_HTTP_OK,
+                                      "EUR:3",
+                                      "deposit-refund-1",
+                                      3),
+    TALER_TESTING_cmd_refund_with_id ("refund-ok-double",
+                                      MHD_HTTP_OK,
+                                      "EUR:3",
+                                      "deposit-refund-1",
+                                      3),
+    /* Previous /refund(s) had id == 0.  */
+    TALER_TESTING_cmd_refund_with_id ("refund-conflicting",
+                                      MHD_HTTP_CONFLICT,
+                                      "EUR:5",
+                                      "deposit-refund-1",
+                                      1),
+    TALER_TESTING_cmd_deposit ("deposit-refund-insufficient-refund",
+                               "withdraw-coin-r1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:4\"}]}",
+                               GNUNET_TIME_UNIT_MINUTES,
+                               "EUR:4",
+                               MHD_HTTP_CONFLICT),
+    TALER_TESTING_cmd_refund_with_id ("refund-ok-increase",
+                                      MHD_HTTP_OK,
+                                      "EUR:2",
+                                      "deposit-refund-1",
+                                      2),
+    /**
+     * Spend 4.99 EUR of the refunded 4.99 EUR coin (1ct gone
+     * due to refund) (merchant would receive EUR:4.98 due to
+     * 1 ct deposit fee) */
+    TALER_TESTING_cmd_deposit ("deposit-refund-2",
+                               "withdraw-coin-r1",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":\"EUR:5\"}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:4.99",
+                               MHD_HTTP_OK),
+    /**
+     * Run transfers. This will do the transfer as refund deadline
+     * was 0
+     */
+    CMD_EXEC_AGGREGATOR ("run-aggregator-3"),
+    /**
+     * Check that deposit did run.
+     */
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-pre-refund",
+                                           cred.exchange_url,
+                                           "EUR:4.97",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    /**
+     * Run failing refund, as past deadline & aggregation.
+     */
+    TALER_TESTING_cmd_refund ("refund-fail",
+                              MHD_HTTP_GONE,
+                              "EUR:4.99",
+                              "deposit-refund-2"),
+    TALER_TESTING_cmd_check_bank_empty ("check-empty-after-refund"),
+    /**
+     * Test refunded coins are never executed, even past
+     * refund deadline
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-rb",
+                              "EUR:5.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-rb",
+                                                 "EUR:5.01",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "create-reserve-rb"),
+    CMD_EXEC_WIREWATCH ("wirewatch-rb"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-rb",
+                                       "create-reserve-rb",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit ("deposit-refund-1b",
+                               "withdraw-coin-rb",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"ice 
cream\",\"value\":\"EUR:5\"}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:5",
+                               MHD_HTTP_OK),
+    /**
+     * Trigger refund (before aggregator had a chance to execute
+     * deposit, even though refund deadline was zero).
+     */
+    TALER_TESTING_cmd_refund ("refund-ok-fast",
+                              MHD_HTTP_OK,
+                              "EUR:5",
+                              "deposit-refund-1b"),
+    /**
+     * Run transfers. This will do the transfer as refund deadline
+     * was 0, except of course because the refund succeeded, the
+     * transfer should no longer be done.
+     */
+    CMD_EXEC_AGGREGATOR ("run-aggregator-3b"),
+    /* check that aggregator didn't do anything, as expected */
+    TALER_TESTING_cmd_check_bank_empty ("check-refund-fast-not-run"),
+    TALER_TESTING_cmd_end ()
+  };
+
+  struct TALER_TESTING_Command recoup[] = {
+    /**
+     * Fill reserve with EUR:5.01, as withdraw fee is 1 ct per
+     * config.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-1",
+                              "EUR:15.02"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "recoup-create-reserve-1-check",
+      "EUR:15.02",
+      cred.user42_payto,
+      cred.exchange_payto,
+      "recoup-create-reserve-1"),
+    /**
+     * Run wire-watch to trigger the reserve creation.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-4"),
+    /* Withdraw a 5 EUR coin, at fee of 1 ct */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1",
+                                       "recoup-create-reserve-1",
+                                       "EUR:5",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /* Withdraw a 10 EUR coin, at fee of 1 ct */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-1b",
+                                       "recoup-create-reserve-1",
+                                       "EUR:10",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /* melt 10 EUR coin to get 5 EUR refreshed coin */
+    TALER_TESTING_cmd_melt ("recoup-melt-coin-1b",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:5",
+                            NULL),
+    TALER_TESTING_cmd_refresh_reveal ("recoup-reveal-coin-1b",
+                                      "recoup-melt-coin-1b",
+                                      MHD_HTTP_OK),
+    /* Revoke both 5 EUR coins */
+    TALER_TESTING_cmd_revoke ("revoke-0-EUR:5",
+                              MHD_HTTP_OK,
+                              "recoup-withdraw-coin-1",
+                              config_file),
+    /* Recoup coin to reserve */
+    TALER_TESTING_cmd_recoup ("recoup-1",
+                              MHD_HTTP_OK,
+                              "recoup-withdraw-coin-1",
+                              "EUR:5"),
+    /* Check the money is back with the reserve */
+    TALER_TESTING_cmd_status ("recoup-reserve-status-1",
+                              "recoup-create-reserve-1",
+                              "EUR:5.0",
+                              MHD_HTTP_OK),
+    /* Recoup-refresh coin to 10 EUR coin */
+    TALER_TESTING_cmd_recoup_refresh ("recoup-1b",
+                                      MHD_HTTP_OK,
+                                      "recoup-reveal-coin-1b",
+                                      "recoup-melt-coin-1b",
+                                      "EUR:5"),
+    /* melt 10 EUR coin *again* to get 1 EUR refreshed coin */
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1a",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_refresh_reveal ("recoup-reveal-coin-1a",
+                                      "recoup-remelt-coin-1a",
+                                      MHD_HTTP_OK),
+    /* Try melting for more than the residual value to provoke an error */
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1b",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1c",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1d",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1e",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1f",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1g",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1h",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1i",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_OK,
+                            "EUR:1",
+                            NULL),
+    TALER_TESTING_cmd_melt ("recoup-remelt-coin-1b-failing",
+                            "recoup-withdraw-coin-1b",
+                            MHD_HTTP_CONFLICT,
+                            "EUR:1",
+                            NULL),
+    /* Re-withdraw from this reserve */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2",
+                                       "recoup-create-reserve-1",
+                                       "EUR:1",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /**
+     * This withdrawal will test the logic to create a "recoup"
+     * element to insert into the reserve's history.
+     */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2-over",
+                                       "recoup-create-reserve-1",
+                                       "EUR:10",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_CONFLICT),
+    TALER_TESTING_cmd_status ("recoup-reserve-status-2",
+                              "recoup-create-reserve-1",
+                              "EUR:3.99",
+                              MHD_HTTP_OK),
+    /* These commands should close the reserve because
+     * the aggregator is given a config file that overrides
+     * the reserve expiration time (making it now-ish) */
+    CMD_TRANSFER_TO_EXCHANGE ("short-lived-reserve",
+                              "EUR:5.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check-short-lived-reserve",
+                                                 "EUR:5.01",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "short-lived-reserve"),
+    TALER_TESTING_cmd_exec_wirewatch2 ("short-lived-aggregation",
+                                       config_file_expire_reserve_now,
+                                       "exchange-account-2"),
+    TALER_TESTING_cmd_exec_closer ("close-reserves",
+                                   config_file_expire_reserve_now,
+                                   "EUR:5",
+                                   "EUR:0.01",
+                                   "short-lived-reserve"),
+    TALER_TESTING_cmd_exec_transfer ("close-reserves-transfer",
+                                     config_file_expire_reserve_now),
+
+    TALER_TESTING_cmd_status ("short-lived-status",
+                              "short-lived-reserve",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("expired-withdraw",
+                                       "short-lived-reserve",
+                                       "EUR:1",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_CONFLICT),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_short-lived_reimburse",
+                                           cred.exchange_url,
+                                           "EUR:5",
+                                           cred.exchange_payto,
+                                           cred.user42_payto),
+    /* Fill reserve with EUR:2.02, as withdraw fee is 1 ct per
+     * config, then withdraw two coin, partially spend one, and
+     * then have the rest paid back.  Check deposit of other coin
+     * fails.  Do not use EUR:5 here as the EUR:5 coin was
+     * revoked and we did not bother to create a new one... */
+    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-2",
+                              "EUR:2.02"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("ck-recoup-create-reserve-2",
+                                                 "EUR:2.02",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "recoup-create-reserve-2"),
+    /* Make previous command effective. */
+    CMD_EXEC_WIREWATCH ("wirewatch-5"),
+    /* Withdraw a 1 EUR coin, at fee of 1 ct */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2a",
+                                       "recoup-create-reserve-2",
+                                       "EUR:1",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    /* Withdraw a 1 EUR coin, at fee of 1 ct */
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-2b",
+                                       "recoup-create-reserve-2",
+                                       "EUR:1",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_deposit ("recoup-deposit-partial",
+                               "recoup-withdraw-coin-2a",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:0.5",
+                               MHD_HTTP_OK),
+    TALER_TESTING_cmd_revoke ("revoke-1-EUR:1",
+                              MHD_HTTP_OK,
+                              "recoup-withdraw-coin-2a",
+                              config_file),
+    /* Check recoup is failing for the coin with the reused coin key
+       (fails either because of denomination conflict (RSA) or
+       double-spending (CS))*/
+    TALER_TESTING_cmd_recoup ("recoup-2x",
+                              MHD_HTTP_CONFLICT,
+                              "withdraw-coin-1x",
+                              "EUR:1"),
+    TALER_TESTING_cmd_recoup ("recoup-2",
+                              MHD_HTTP_OK,
+                              "recoup-withdraw-coin-2a",
+                              "EUR:0.5"),
+    /* Idempotency of recoup (withdrawal variant) */
+    TALER_TESTING_cmd_recoup ("recoup-2b",
+                              MHD_HTTP_OK,
+                              "recoup-withdraw-coin-2a",
+                              "EUR:0.5"),
+    TALER_TESTING_cmd_deposit ("recoup-deposit-revoked",
+                               "recoup-withdraw-coin-2b",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"more ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:1",
+                               MHD_HTTP_GONE),
+    /* Test deposit fails after recoup, with proof in recoup */
+
+    /* Note that, the exchange will never return the coin's transaction
+     * history with recoup data, as we get a 410 on the DK! */
+    TALER_TESTING_cmd_deposit ("recoup-deposit-partial-after-recoup",
+                               "recoup-withdraw-coin-2a",
+                               0,
+                               cred.user42_payto,
+                               "{\"items\":[{\"name\":\"extra ice 
cream\",\"value\":1}]}",
+                               GNUNET_TIME_UNIT_ZERO,
+                               "EUR:0.5",
+                               MHD_HTTP_GONE),
+    /* Test that revoked coins cannot be withdrawn */
+    CMD_TRANSFER_TO_EXCHANGE ("recoup-create-reserve-3",
+                              "EUR:1.01"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "check-recoup-create-reserve-3",
+      "EUR:1.01",
+      cred.user42_payto,
+      cred.exchange_payto,
+      "recoup-create-reserve-3"),
+    CMD_EXEC_WIREWATCH ("wirewatch-6"),
+    TALER_TESTING_cmd_withdraw_amount ("recoup-withdraw-coin-3-revoked",
+                                       "recoup-create-reserve-3",
+                                       "EUR:1",
+                                       0, /* age restriction off */
+                                       MHD_HTTP_GONE),
+    /* check that we are empty before the rejection test */
+    TALER_TESTING_cmd_check_bank_empty ("check-empty-again"),
+
+    TALER_TESTING_cmd_end ()
+  };
+
+  /**
+   * Test batch withdrawal plus spending.
+   */
+  struct TALER_TESTING_Command batch_withdraw[] = {
+    /**
+     * Move money to the exchange's bank account.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-batch-reserve-1",
+                              "EUR:6.03"),
+    TALER_TESTING_cmd_reserve_poll ("poll-batch-reserve-1",
+                                    "create-batch-reserve-1",
+                                    "EUR:6.03",
+                                    GNUNET_TIME_UNIT_MINUTES,
+                                    MHD_HTTP_OK),
+    TALER_TESTING_cmd_check_bank_admin_transfer 
("check-create-batch-reserve-1",
+                                                 "EUR:6.03",
+                                                 cred.user42_payto,
+                                                 cred.exchange_payto,
+                                                 "create-batch-reserve-1"),
+    /*
+     * Make a reserve exist, according to the previous
+     * transfer.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-batch-1"),
+    TALER_TESTING_cmd_reserve_poll_finish ("finish-poll-batch-reserve-1",
+                                           GNUNET_TIME_UNIT_SECONDS,
+                                           "poll-batch-reserve-1"),
+    /**
+     * Withdraw EUR:5 AND EUR:1.
+     */
+    TALER_TESTING_cmd_batch_withdraw ("batch-withdraw-coin-1",
+                                      "create-batch-reserve-1",
+                                      0,  /* age restriction off */
+                                      MHD_HTTP_OK,
+                                      "EUR:5",
+                                      "EUR:1",
+                                      NULL),
+    /**
+     * Check the reserve is (almost) depleted.
+     */
+    TALER_TESTING_cmd_status ("status-batch-1",
+                              "create-batch-reserve-1",
+                              "EUR:0.01",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_reserve_history ("history-batch-1",
+                                       "create-batch-reserve-1",
+                                       "EUR:0",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_status ("status-batch-2",
+                              "create-batch-reserve-1",
+                              "EUR:0.0",
+                              MHD_HTTP_OK),
+    /**
+     * Spend the coins.
+     */
+    TALER_TESTING_cmd_batch_deposit ("batch-deposit-1",
+                                     cred.user42_payto,
+                                     "{\"items\":[{\"name\":\"ice 
cream\",\"value\":5}]}",
+                                     GNUNET_TIME_UNIT_ZERO,
+                                     MHD_HTTP_OK,
+                                     "batch-withdraw-coin-1#0",
+                                     "EUR:5",
+                                     "batch-withdraw-coin-1#1",
+                                     "EUR:1",
+                                     NULL),
+    TALER_TESTING_cmd_end ()
+  };
+
+
+#define RESERVE_OPEN_CLOSE_CHUNK 4
+#define RESERVE_OPEN_CLOSE_ITERATIONS 3
+
+  struct TALER_TESTING_Command 
reserve_open_close[(RESERVE_OPEN_CLOSE_ITERATIONS
+                                                   * RESERVE_OPEN_CLOSE_CHUNK)
+                                                  + 1];
+
+  (void) cls;
+  for (unsigned int i = 0;
+       i < RESERVE_OPEN_CLOSE_ITERATIONS;
+       i++)
+  {
+    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 0]
+      = CMD_TRANSFER_TO_EXCHANGE ("reserve-open-close-key",
+                                  "EUR:20");
+    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 1]
+      = TALER_TESTING_cmd_exec_wirewatch2 ("reserve-open-close-wirewatch",
+                                           config_file_expire_reserve_now,
+                                           "exchange-account-2");
+    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 2]
+      = TALER_TESTING_cmd_exec_closer ("reserve-open-close-aggregation",
+                                       config_file_expire_reserve_now,
+                                       "EUR:19.99",
+                                       "EUR:0.01",
+                                       "reserve-open-close-key");
+    reserve_open_close[(i * RESERVE_OPEN_CLOSE_CHUNK) + 3]
+      = TALER_TESTING_cmd_status ("reserve-open-close-status",
+                                  "reserve-open-close-key",
+                                  "EUR:0",
+                                  MHD_HTTP_OK);
+  }
+  reserve_open_close[RESERVE_OPEN_CLOSE_ITERATIONS * RESERVE_OPEN_CLOSE_CHUNK]
+    = TALER_TESTING_cmd_end ();
+
+  {
+    struct TALER_TESTING_Command commands[] = {
+      TALER_TESTING_cmd_run_fakebank ("run-fakebank",
+                                      cred.cfg,
+                                      "exchange-account-2"),
+      TALER_TESTING_cmd_system_start ("start-taler",
+                                      config_file,
+                                      "-e",
+                                      NULL),
+      TALER_TESTING_cmd_get_exchange ("get-exchange",
+                                      cred.cfg,
+                                      NULL,
+                                      true,
+                                      true),
+      TALER_TESTING_cmd_batch ("withdraw",
+                               withdraw),
+      TALER_TESTING_cmd_batch ("spend",
+                               spend),
+      TALER_TESTING_cmd_batch ("refresh",
+                               refresh),
+      TALER_TESTING_cmd_batch ("withdraw-age",
+                               withdraw_age),
+      TALER_TESTING_cmd_batch ("spend-age",
+                               spend_age),
+      TALER_TESTING_cmd_batch ("refresh-age",
+                               refresh_age),
+      TALER_TESTING_cmd_batch ("track",
+                               track),
+      TALER_TESTING_cmd_batch ("unaggregation",
+                               unaggregation),
+      TALER_TESTING_cmd_batch ("aggregation",
+                               aggregation),
+      TALER_TESTING_cmd_batch ("refund",
+                               refund),
+      TALER_TESTING_cmd_batch ("batch-withdraw",
+                               batch_withdraw),
+      TALER_TESTING_cmd_batch ("recoup",
+                               recoup),
+      TALER_TESTING_cmd_batch ("reserve-open-close",
+                               reserve_open_close),
+      /* End the suite. */
+      TALER_TESTING_cmd_end ()
+    };
+
+    TALER_TESTING_run (is,
+                       commands);
+  }
+}
+
+
+int
+main (int argc,
+      char *const *argv)
+{
+  (void) argc;
+  {
+    char *cipher;
+
+    cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
+    GNUNET_assert (NULL != cipher);
+    uses_cs = (0 == strcmp (cipher,
+                            "cs"));
+    GNUNET_asprintf (&config_file,
+                     "test_exchange_api-%s.conf",
+                     cipher);
+    GNUNET_asprintf (&config_file_expire_reserve_now,
+                     "test_exchange_api_expire_reserve_now-%s.conf",
+                     cipher);
+    GNUNET_free (cipher);
+  }
+  return TALER_TESTING_main (argv,
+                             "INFO",
+                             config_file,
+                             "exchange-account-2",
+                             TALER_TESTING_BS_FAKEBANK,
+                             &cred,
+                             &run,
+                             NULL);
+}
+
+
+/* end of test_exchange_api.c */
diff --git a/src/testing/test_exchange_api.conf 
b/src/testing/test_exchange_api.conf
new file mode 100644
index 0000000..f244704
--- /dev/null
+++ b/src/testing/test_exchange_api.conf
@@ -0,0 +1,91 @@
+# This file is in the public domain.
+#
+
+[PATHS]
+TALER_TEST_HOME = test_exchange_api_home/
+
+[taler]
+CURRENCY = EUR
+CURRENCY_ROUND_UNIT = EUR:0.01
+
+[auditor]
+BASE_URL = "http://localhost:8083/";
+PORT = 8083
+PUBLIC_KEY = T0XJ9QZ59YDN7QG3RE40SB2HY7W0ASR1EKF4WZDGZ1G159RSQC80
+TINY_AMOUNT = EUR:0.01
+
+[auditordb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[bank]
+HTTP_PORT = 8082
+
+[exchange]
+TERMS_ETAG = tos
+PRIVACY_ETAG = 0
+AML_THRESHOLD = EUR:1000000
+PORT = 8081
+MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
+DB = postgres
+BASE_URL = "http://localhost:8081/";
+EXPIRE_SHARD_SIZE ="300 ms"
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
+
+[exchangedb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[taler-exchange-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
+
+[taler-exchange-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
+
+
+[exchange-account-1]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-1]
+WIRE_GATEWAY_AUTH_METHOD = none
+WIRE_GATEWAY_URL = "http://localhost:8082/2/";
+
+[exchange-account-2]
+PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2"
+ENABLE_DEBIT = YES
+ENABLE_CREDIT = YES
+
+[exchange-accountcredentials-2]
+WIRE_GATEWAY_AUTH_METHOD = basic
+USERNAME = Exchange
+PASSWORD = x
+WIRE_GATEWAY_URL = "http://localhost:8082/2/";
+
+
+[kyc-provider-test-oauth2]
+COST = 0
+LOGIC = oauth2
+USER_TYPE = INDIVIDUAL
+PROVIDED_CHECKS = DUMMY
+KYC_OAUTH2_VALIDITY = forever
+KYC_OAUTH2_TOKEN_URL = http://localhost:6666/oauth/v2/token
+KYC_OAUTH2_AUTHORIZE_URL = http://localhost:6666/oauth/v2/login
+KYC_OAUTH2_INFO_URL = http://localhost:6666/api/user/me
+KYC_OAUTH2_CLIENT_ID = taler-exchange
+KYC_OAUTH2_CLIENT_SECRET = exchange-secret
+KYC_OAUTH2_POST_URL = http://example.com/
+KYC_OAUTH2_ATTRIBUTE_TEMPLATE = "{"full_name":"{{last_name}}, {{first_name}}"}"
+
+[kyc-legitimization-close]
+OPERATION_TYPE = CLOSE
+REQUIRED_CHECKS = DUMMY
+THRESHOLD = EUR:0
+TIMEFRAME = 1d
+
+[exchange-extension-age_restriction]
+ENABLED = YES
+#AGE_GROUPS = "8:10:12:14:16:18:21"
diff --git a/src/testing/testing_api_cmd_batch.c 
b/src/testing/testing_api_cmd_batch.c
new file mode 100644
index 0000000..5bb7b97
--- /dev/null
+++ b/src/testing/testing_api_cmd_batch.c
@@ -0,0 +1,235 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU 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 testing/testing_api_cmd_batch.c
+ * @brief Implement batch-execution of CMDs.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "batch" CMD.
+ */
+struct BatchState
+{
+  /**
+   * CMDs batch.
+   */
+  struct TALER_TESTING_Command *batch;
+
+  /**
+   * My command (the batch command).
+   */
+  const struct TALER_TESTING_Command *cmd;
+
+  /**
+   * Internal command pointer.
+   */
+  unsigned int batch_ip;
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+batch_run (void *cls,
+           const struct TALER_TESTING_Command *cmd,
+           struct TALER_TESTING_Interpreter *is)
+{
+  struct BatchState *bs = cls;
+
+  bs->cmd = cmd;
+  if (NULL != bs->batch[bs->batch_ip].label)
+    TALER_LOG_INFO ("Running batched command: %s\n",
+                    bs->batch[bs->batch_ip].label);
+
+  /* hit end command, leap to next top-level command.  */
+  if (NULL == bs->batch[bs->batch_ip].label)
+  {
+    TALER_LOG_INFO ("Exiting from batch: %s\n",
+                    cmd->label);
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  bs->batch[bs->batch_ip].start_time
+    = bs->batch[bs->batch_ip].last_req_time
+      = GNUNET_TIME_absolute_get ();
+  bs->batch[bs->batch_ip].num_tries = 1;
+  bs->batch[bs->batch_ip].run (bs->batch[bs->batch_ip].cls,
+                               &bs->batch[bs->batch_ip],
+                               is);
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+batch_cleanup (void *cls,
+               const struct TALER_TESTING_Command *cmd)
+{
+  struct BatchState *bs = cls;
+
+  (void) cmd;
+  for (unsigned int i = 0;
+       NULL != bs->batch[i].label;
+       i++)
+    if (NULL != bs->batch[i].cleanup)
+      bs->batch[i].cleanup (bs->batch[i].cls,
+                            &bs->batch[i]);
+  GNUNET_free (bs->batch);
+  GNUNET_free (bs);
+}
+
+
+/**
+ * Offer internal data from a "batch" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+batch_traits (void *cls,
+              const void **ret,
+              const char *trait,
+              unsigned int index)
+{
+  struct BatchState *bs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_batch_cmds (bs->batch),
+    TALER_TESTING_trait_end ()
+  };
+
+  /* Always return current command.  */
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch (const char *label,
+                         struct TALER_TESTING_Command *batch)
+{
+  struct BatchState *bs;
+  unsigned int i;
+
+  bs = GNUNET_new (struct BatchState);
+
+  /* Get number of commands.  */
+  for (i = 0; NULL != batch[i].label; i++)
+    /* noop */
+    ;
+
+  bs->batch = GNUNET_new_array (i + 1,
+                                struct TALER_TESTING_Command);
+  GNUNET_memcpy (bs->batch,
+                 batch,
+                 sizeof (struct TALER_TESTING_Command) * i);
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = bs,
+      .label = label,
+      .run = &batch_run,
+      .cleanup = &batch_cleanup,
+      .traits = &batch_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+bool
+TALER_TESTING_cmd_batch_next (struct TALER_TESTING_Interpreter *is,
+                              void *cls)
+{
+  struct BatchState *bs = cls;
+  struct TALER_TESTING_Command *bcmd = &bs->batch[bs->batch_ip];
+
+  if (NULL == bcmd->label)
+  {
+    /* This batch is done */
+    return true;
+  }
+  if (TALER_TESTING_cmd_is_batch (bcmd))
+  {
+    if (TALER_TESTING_cmd_batch_next (is,
+                                      bcmd->cls))
+    {
+      /* sub-batch is done */
+      bcmd->finish_time = GNUNET_TIME_absolute_get ();
+      bs->batch_ip++;
+      return false;
+    }
+  }
+  /* Simple command is done */
+  bcmd->finish_time = GNUNET_TIME_absolute_get ();
+  bs->batch_ip++;
+  return false;
+}
+
+
+bool
+TALER_TESTING_cmd_is_batch (const struct TALER_TESTING_Command *cmd)
+{
+  return cmd->run == &batch_run;
+}
+
+
+struct TALER_TESTING_Command *
+TALER_TESTING_cmd_batch_get_current (const struct TALER_TESTING_Command *cmd)
+{
+  struct BatchState *bs = cmd->cls;
+
+  GNUNET_assert (cmd->run == &batch_run);
+  return &bs->batch[bs->batch_ip];
+}
+
+
+void
+TALER_TESTING_cmd_batch_set_current (const struct TALER_TESTING_Command *cmd,
+                                     unsigned int new_ip)
+{
+  struct BatchState *bs = cmd->cls;
+
+  /* sanity checks */
+  GNUNET_assert (cmd->run == &batch_run);
+  for (unsigned int i = 0; i < new_ip; i++)
+    GNUNET_assert (NULL != bs->batch[i].label);
+  /* actual logic */
+  bs->batch_ip = new_ip;
+}
diff --git a/src/testing/testing_api_cmd_batch_deposit.c 
b/src/testing/testing_api_cmd_batch_deposit.c
new file mode 100644
index 0000000..a3c25e3
--- /dev/null
+++ b/src/testing/testing_api_cmd_batch_deposit.c
@@ -0,0 +1,626 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-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 testing/testing_api_cmd_batch_deposit.c
+ * @brief command for testing /batch-deposit.
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * How often do we retry before giving up?
+ */
+#define NUM_RETRIES 5
+
+/**
+ * How long do we wait AT MOST when retrying?
+ */
+#define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MILLISECONDS, 100)
+
+
+/**
+ * Information per coin in the batch.
+ */
+struct Coin
+{
+
+  /**
+   * Amount to deposit.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Deposit fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Reference to any command that is able to provide a coin,
+   * possibly using $LABEL#$INDEX notation.
+   */
+  char *coin_reference;
+
+  /**
+   * The command being referenced.
+   */
+  const struct TALER_TESTING_Command *coin_cmd;
+
+  /**
+   * Index of the coin at @e coin_cmd.
+   */
+  unsigned int coin_idx;
+};
+
+
+/**
+ * State for a "batch deposit" CMD.
+ */
+struct BatchDepositState
+{
+
+  /**
+   * Refund deadline. Zero for no refunds.
+   */
+  struct GNUNET_TIME_Timestamp refund_deadline;
+
+  /**
+   * Wire deadline.
+   */
+  struct GNUNET_TIME_Timestamp wire_deadline;
+
+  /**
+   * Timestamp of the /deposit operation in the wallet (contract signing time).
+   */
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+  /**
+   * How long do we wait until we retry?
+   */
+  struct GNUNET_TIME_Relative backoff;
+
+  /**
+   * When did the exchange receive the deposit?
+   */
+  struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+  /**
+   * Signing key used by the exchange to sign the
+   * deposit confirmation.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Set (by the interpreter) to a fresh private key.  This
+   * key will be used to sign the deposit request.
+   */
+  struct TALER_MerchantPrivateKeyP merchant_priv;
+
+  /**
+   * Deposit handle while operation is running.
+   */
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
+
+  /**
+   * Array of coins to batch-deposit.
+   */
+  struct Coin *coins;
+
+  /**
+   * Wire details of who is depositing -- this would be merchant
+   * wire details in a normal scenario.
+   */
+  json_t *wire_details;
+
+  /**
+   * JSON string describing what a proposal is about.
+   */
+  json_t *contract_terms;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Task scheduled to try later.
+   */
+  struct GNUNET_SCHEDULER_Task *retry_task;
+
+  /**
+   * Array of @e num_coins signatures from the exchange on the
+   * deposit confirmation.
+   */
+  struct TALER_ExchangeSignatureP *exchange_sigs;
+
+  /**
+   * Reference to previous deposit operation.
+   * Only present if we're supposed to replay the previous deposit.
+   */
+  const char *deposit_reference;
+
+  /**
+   * If @e coin_reference refers to an operation that generated
+   * an array of coins, this value determines which coin to pick.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Set to true if the /deposit succeeded
+   * and we now can provide the resulting traits.
+   */
+  bool deposit_succeeded;
+
+};
+
+
+/**
+ * Callback to analyze the /batch-deposit response, just used to check if the
+ * response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr deposit response details
+ */
+static void
+batch_deposit_cb (void *cls,
+                  const struct TALER_EXCHANGE_BatchDepositResult *dr)
+{
+  struct BatchDepositState *ds = cls;
+
+  ds->dh = NULL;
+  if (ds->expected_response_code != dr->hr.http_status)
+  {
+    TALER_TESTING_unexpected_status (ds->is,
+                                     dr->hr.http_status,
+                                     ds->expected_response_code);
+    return;
+  }
+  if (MHD_HTTP_OK == dr->hr.http_status)
+  {
+    if (ds->num_coins != dr->details.ok.num_signatures)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (ds->is);
+      return;
+    }
+    ds->deposit_succeeded = GNUNET_YES;
+    ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
+    ds->exchange_pub = *dr->details.ok.exchange_pub;
+    ds->exchange_sigs = GNUNET_memdup (dr->details.ok.exchange_sigs,
+                                       dr->details.ok.num_signatures
+                                       * sizeof (struct
+                                                 TALER_ExchangeSignatureP));
+  }
+  TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+batch_deposit_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct BatchDepositState *ds = cls;
+  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+  const struct TALER_DenominationSignature *denom_pub_sig;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  enum TALER_ErrorCode ec;
+  struct TALER_WireSaltP wire_salt;
+  struct TALER_MerchantWireHashP h_wire;
+  const char *payto_uri;
+  struct TALER_EXCHANGE_CoinDepositDetail cdds[ds->num_coins];
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_fixed_auto ("salt",
+                                 &wire_salt),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *exchange_url
+    = TALER_TESTING_get_exchange_url (is);
+
+  (void) cmd;
+  if (NULL == exchange_url)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  memset (cdds,
+          0,
+          sizeof (cdds));
+  ds->is = is;
+  GNUNET_assert (NULL != ds->wire_details);
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (ds->wire_details,
+                         spec,
+                         NULL, NULL))
+  {
+    json_dumpf (ds->wire_details,
+                stderr,
+                JSON_INDENT (2));
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_JSON_contract_hash (ds->contract_terms,
+                                &h_contract_terms))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
+                                                          &h_wire));
+  if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
+  {
+    struct GNUNET_TIME_Relative refund_deadline;
+
+    refund_deadline
+      = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
+    ds->wire_deadline
+      =
+        GNUNET_TIME_relative_to_timestamp (
+          GNUNET_TIME_relative_multiply (refund_deadline,
+                                         2));
+  }
+  else
+  {
+    ds->refund_deadline = ds->wallet_timestamp;
+    ds->wire_deadline = GNUNET_TIME_timestamp_get ();
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ds->merchant_priv.eddsa_priv,
+                                      &merchant_pub.eddsa_pub);
+
+  for (unsigned int i = 0; i<ds->num_coins; i++)
+  {
+    struct Coin *coin = &ds->coins[i];
+    struct TALER_EXCHANGE_CoinDepositDetail *cdd = &cdds[i];
+    const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+    const struct TALER_AgeCommitmentProof *age_commitment_proof = NULL;
+
+    GNUNET_assert (NULL != coin->coin_reference);
+    cdd->amount = coin->amount;
+    coin->coin_cmd = TALER_TESTING_interpreter_lookup_command (
+      is,
+      coin->coin_reference);
+    if (NULL == coin->coin_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+
+    if ( (GNUNET_OK !=
+          TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
+                                             coin->coin_idx,
+                                             &coin_priv)) ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
+                                                        coin->coin_idx,
+                                                        &age_commitment_proof))
+         ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_denom_pub (coin->coin_cmd,
+                                             coin->coin_idx,
+                                             &denom_pub)) ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_denom_sig (coin->coin_cmd,
+                                             coin->coin_idx,
+                                             &denom_pub_sig)) )
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (NULL != age_commitment_proof)
+    {
+      TALER_age_commitment_hash (&age_commitment_proof->commitment,
+                                 &cdd->h_age_commitment);
+    }
+    coin->deposit_fee = denom_pub->fees.deposit;
+    GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+                                        &cdd->coin_pub.eddsa_pub);
+    cdd->denom_sig = *denom_pub_sig;
+    cdd->h_denom_pub = denom_pub->h_key;
+    TALER_wallet_deposit_sign (&coin->amount,
+                               &denom_pub->fees.deposit,
+                               &h_wire,
+                               &h_contract_terms,
+                               NULL, /* wallet_data_hash */
+                               &cdd->h_age_commitment,
+                               NULL, /* hash of extensions */
+                               &denom_pub->h_key,
+                               ds->wallet_timestamp,
+                               &merchant_pub,
+                               ds->refund_deadline,
+                               coin_priv,
+                               &cdd->coin_sig);
+  }
+
+  GNUNET_assert (NULL == ds->dh);
+  {
+    struct TALER_EXCHANGE_DepositContractDetail dcd = {
+      .wire_deadline = ds->wire_deadline,
+      .merchant_payto_uri = payto_uri,
+      .wire_salt = wire_salt,
+      .h_contract_terms = h_contract_terms,
+      .policy_details = NULL /* FIXME #7270-OEC */,
+      .wallet_timestamp = ds->wallet_timestamp,
+      .merchant_pub = merchant_pub,
+      .refund_deadline = ds->refund_deadline
+    };
+
+    ds->dh = TALER_EXCHANGE_batch_deposit (
+      TALER_TESTING_interpreter_get_context (is),
+      exchange_url,
+      TALER_TESTING_get_keys (is),
+      &dcd,
+      ds->num_coins,
+      cdds,
+      &batch_deposit_cb,
+      ds,
+      &ec);
+  }
+  if (NULL == ds->dh)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not create deposit with EC %d\n",
+                (int) ec);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+/**
+ * Free the state of a "batch-deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct BatchDepositState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+batch_deposit_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct BatchDepositState *ds = cls;
+
+  if (NULL != ds->dh)
+  {
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
+    TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
+    ds->dh = NULL;
+  }
+  if (NULL != ds->retry_task)
+  {
+    GNUNET_SCHEDULER_cancel (ds->retry_task);
+    ds->retry_task = NULL;
+  }
+  for (unsigned int i = 0; i<ds->num_coins; i++)
+    GNUNET_free (ds->coins[i].coin_reference);
+  GNUNET_free (ds->coins);
+  GNUNET_free (ds->exchange_sigs);
+  json_decref (ds->wire_details);
+  json_decref (ds->contract_terms);
+  GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "batch-deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+batch_deposit_traits (void *cls,
+                      const void **ret,
+                      const char *trait,
+                      unsigned int index)
+{
+  struct BatchDepositState *ds = cls;
+  struct Coin *coin = &ds->coins[index];
+  /* Will point to coin cmd internals. */
+  const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+  const struct TALER_AgeCommitmentProof *age_commitment_proof;
+
+  if (index >= ds->num_coins)
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  if (NULL == coin->coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (ds->is);
+    return GNUNET_NO;
+  }
+  if ( (GNUNET_OK !=
+        TALER_TESTING_get_trait_coin_priv (coin->coin_cmd,
+                                           coin->coin_idx,
+                                           &coin_spent_priv)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_age_commitment_proof (coin->coin_cmd,
+                                                      coin->coin_idx,
+                                                      &age_commitment_proof)) )
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (ds->is);
+    return GNUNET_NO;
+  }
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      /* First two traits are only available if
+         ds->traits is #GNUNET_YES */
+      TALER_TESTING_make_trait_exchange_pub (index,
+                                             &ds->exchange_pub),
+      TALER_TESTING_make_trait_exchange_sig (index,
+                                             &ds->exchange_sigs[index]),
+      /* These traits are always available */
+      TALER_TESTING_make_trait_wire_details (ds->wire_details),
+      TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+      TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
+      TALER_TESTING_make_trait_age_commitment_proof (index,
+                                                     age_commitment_proof),
+      TALER_TESTING_make_trait_coin_priv (index,
+                                          coin_spent_priv),
+      TALER_TESTING_make_trait_deposit_amount (index,
+                                               &coin->amount),
+      TALER_TESTING_make_trait_deposit_fee_amount (index,
+                                                   &coin->deposit_fee),
+
+      TALER_TESTING_make_trait_timestamp (index,
+                                          &ds->exchange_timestamp),
+      TALER_TESTING_make_trait_wire_deadline (index,
+                                              &ds->wire_deadline),
+      TALER_TESTING_make_trait_refund_deadline (index,
+                                                &ds->refund_deadline),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait ((ds->deposit_succeeded)
+                                    ? traits
+                                    : &traits[2],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_deposit (const char *label,
+                                 const char *target_account_payto,
+                                 const char *contract_terms,
+                                 struct GNUNET_TIME_Relative refund_deadline,
+                                 unsigned int expected_response_code,
+                                 ...)
+{
+  struct BatchDepositState *ds;
+  va_list ap;
+  unsigned int num_coins = 0;
+  const char *ref;
+
+  va_start (ap,
+            expected_response_code);
+  while (NULL != (ref = va_arg (ap,
+                                const char *)))
+  {
+    GNUNET_assert (NULL != va_arg (ap,
+                                   const char *));
+    num_coins++;
+  }
+  va_end (ap);
+
+  ds = GNUNET_new (struct BatchDepositState);
+  ds->num_coins = num_coins;
+  ds->coins = GNUNET_new_array (num_coins,
+                                struct Coin);
+  num_coins = 0;
+  va_start (ap,
+            expected_response_code);
+  while (NULL != (ref = va_arg (ap,
+                                const char *)))
+  {
+    struct Coin *coin = &ds->coins[num_coins++];
+    const char *amount = va_arg (ap,
+                                 const char *);
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_parse_coin_reference (ref,
+                                                       &coin->coin_reference,
+                                                       &coin->coin_idx));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (amount,
+                                           &coin->amount));
+  }
+  va_end (ap);
+
+  ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+  GNUNET_assert (NULL != ds->wire_details);
+  ds->contract_terms = json_loads (contract_terms,
+                                   JSON_REJECT_DUPLICATES,
+                                   NULL);
+  GNUNET_CRYPTO_eddsa_key_create (&ds->merchant_priv.eddsa_priv);
+  if (NULL == ds->contract_terms)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse contract terms `%s' for CMD `%s'\n",
+                contract_terms,
+                label);
+    GNUNET_assert (0);
+  }
+  ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+  GNUNET_assert (0 ==
+                 json_object_set_new (ds->contract_terms,
+                                      "timestamp",
+                                      GNUNET_JSON_from_timestamp (
+                                        ds->wallet_timestamp)));
+  if (! GNUNET_TIME_relative_is_zero (refund_deadline))
+  {
+    ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+    GNUNET_assert (0 ==
+                   json_object_set_new (ds->contract_terms,
+                                        "refund_deadline",
+                                        GNUNET_JSON_from_timestamp (
+                                          ds->refund_deadline)));
+  }
+  ds->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ds,
+      .label = label,
+      .run = &batch_deposit_run,
+      .cleanup = &batch_deposit_cleanup,
+      .traits = &batch_deposit_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_batch_deposit.c */
diff --git a/src/testing/testing_api_cmd_batch_withdraw.c 
b/src/testing/testing_api_cmd_batch_withdraw.c
new file mode 100644
index 0000000..41c74c3
--- /dev/null
+++ b/src/testing/testing_api_cmd_batch_withdraw.c
@@ -0,0 +1,533 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-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 testing/testing_api_cmd_batch_withdraw.c
+ * @brief implements the batch withdraw command
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_exchange_service.h"
+#include "taler_json_lib.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "taler_testing_lib.h"
+
+/**
+ * Information we track per withdrawn coin.
+ */
+struct CoinState
+{
+
+  /**
+   * String describing the denomination value we should withdraw.
+   * A corresponding denomination key must exist in the exchange's
+   * offerings.  Can be NULL if @e pk is set instead.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * If @e amount is NULL, this specifies the denomination key to
+   * use.  Otherwise, this will be set (by the interpreter) to the
+   * denomination PK matching @e amount.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Blinding key used during the operation.
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Values contributed from the exchange during the
+   * withdraw protocol.
+   */
+  struct TALER_ExchangeWithdrawValues exchange_vals;
+
+  /**
+   * Set (by the interpreter) to the exchange's signature over the
+   * coin's public key.
+   */
+  struct TALER_DenominationSignature sig;
+
+  /**
+   * Private key material of the coin, set by the interpreter.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * If age > 0, put here the corresponding age commitment with its proof and
+   * its hash, respectivelly.
+   */
+  struct TALER_AgeCommitmentProof age_commitment_proof;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Reserve history entry that corresponds to this coin.
+   * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
+   */
+  struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+
+
+};
+
+
+/**
+ * State for a "batch withdraw" CMD.
+ */
+struct BatchWithdrawState
+{
+
+  /**
+   * Which reserve should we withdraw from?
+   */
+  const char *reserve_reference;
+
+  /**
+   * Exchange base URL.  Only used as offered trait.
+   */
+  char *exchange_url;
+
+  /**
+   * URI if the reserve we are withdrawing from.
+   */
+  char *reserve_payto_uri;
+
+  /**
+   * Private key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  /**
+   * Public key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Interpreter state (during command).
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Withdraw handle (while operation is running).
+   */
+  struct TALER_EXCHANGE_BatchWithdrawHandle *wsh;
+
+  /**
+   * Array of coin states.
+   */
+  struct CoinState *coins;
+
+  /**
+   * Set to the KYC requirement payto hash *if* the exchange replied with a
+   * request for KYC.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Set to the KYC requirement row *if* the exchange replied with
+   * a request for KYC.
+   */
+  uint64_t requirement_row;
+
+  /**
+   * Length of the @e coins array.
+   */
+  unsigned int num_coins;
+
+  /**
+   * Expected HTTP response code to the request.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * An age > 0 signifies age restriction is required.
+   * Same for all coins in the batch.
+   */
+  uint8_t age;
+};
+
+
+/**
+ * "batch withdraw" operation callback; checks that the
+ * response code is expected and store the exchange signature
+ * in the state.
+ *
+ * @param cls closure.
+ * @param wr withdraw response details
+ */
+static void
+reserve_batch_withdraw_cb (void *cls,
+                           const struct
+                           TALER_EXCHANGE_BatchWithdrawResponse *wr)
+{
+  struct BatchWithdrawState *ws = cls;
+  struct TALER_TESTING_Interpreter *is = ws->is;
+
+  ws->wsh = NULL;
+  if (ws->expected_response_code != wr->hr.http_status)
+  {
+    TALER_TESTING_unexpected_status (is,
+                                     wr->hr.http_status,
+                                     ws->expected_response_code);
+    return;
+  }
+  switch (wr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    for (unsigned int i = 0; i<ws->num_coins; i++)
+    {
+      struct CoinState *cs = &ws->coins[i];
+      const struct TALER_EXCHANGE_PrivateCoinDetails *pcd
+        = &wr->details.ok.coins[i];
+
+      TALER_denom_sig_deep_copy (&cs->sig,
+                                 &pcd->sig);
+      cs->coin_priv = pcd->coin_priv;
+      cs->bks = pcd->bks;
+      cs->exchange_vals = pcd->exchange_vals;
+    }
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* nothing to check */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* nothing to check */
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* TODO[oec]: Check if age-requirement is the reason */
+    break;
+  case MHD_HTTP_GONE:
+    /* theoretically could check that the key was actually */
+    break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* nothing to check */
+    ws->requirement_row
+      = wr->details.unavailable_for_legal_reasons.requirement_row;
+    ws->h_payto
+      = wr->details.unavailable_for_legal_reasons.h_payto;
+    break;
+  default:
+    /* Unsupported status code (by test harness) */
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Batch withdraw test command does not support status code 
%u\n",
+                wr->hr.http_status);
+    GNUNET_break (0);
+    break;
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ */
+static void
+batch_withdraw_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct BatchWithdrawState *ws = cls;
+  const struct TALER_EXCHANGE_Keys *keys =  TALER_TESTING_get_keys (is);
+  const struct TALER_ReservePrivateKeyP *rp;
+  const struct TALER_TESTING_Command *create_reserve;
+  const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+  struct TALER_EXCHANGE_WithdrawCoinInput wcis[ws->num_coins];
+
+  (void) cmd;
+  ws->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (
+        is,
+        ws->reserve_reference);
+
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &rp))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL == ws->exchange_url)
+    ws->exchange_url
+      = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+  ws->reserve_priv = *rp;
+  GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
+                                      &ws->reserve_pub.eddsa_pub);
+  ws->reserve_payto_uri
+    = TALER_reserve_make_payto (ws->exchange_url,
+                                &ws->reserve_pub);
+
+  for (unsigned int i = 0; i<ws->num_coins; i++)
+  {
+    struct CoinState *cs = &ws->coins[i];
+    struct TALER_EXCHANGE_WithdrawCoinInput *wci = &wcis[i];
+
+    TALER_planchet_master_setup_random (&cs->ps);
+    dpk = TALER_TESTING_find_pk (keys,
+                                 &cs->amount,
+                                 ws->age > 0);
+    if (NULL == dpk)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to determine denomination key at %s\n",
+                  (NULL != cmd) ? cmd->label : "<retried command>");
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    /* We copy the denomination key, as re-querying /keys
+     * would free the old one. */
+    cs->pk = TALER_EXCHANGE_copy_denomination_key (dpk);
+    cs->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&cs->reserve_history.amount,
+                                     &cs->amount,
+                                     &cs->pk->fees.withdraw));
+    cs->reserve_history.details.withdraw.fee = cs->pk->fees.withdraw;
+
+    wci->pk = cs->pk;
+    wci->ps = &cs->ps;
+    wci->ach = &cs->h_age_commitment;
+  }
+  ws->wsh = TALER_EXCHANGE_batch_withdraw (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    keys,
+    rp,
+    ws->num_coins,
+    wcis,
+    &reserve_batch_withdraw_cb,
+    ws);
+  if (NULL == ws->wsh)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+/**
+ * Free the state of a "withdraw" CMD, and possibly cancel
+ * a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+batch_withdraw_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct BatchWithdrawState *ws = cls;
+
+  if (NULL != ws->wsh)
+  {
+    TALER_TESTING_command_incomplete (ws->is,
+                                      cmd->label);
+    TALER_EXCHANGE_batch_withdraw_cancel (ws->wsh);
+    ws->wsh = NULL;
+  }
+  for (unsigned int i = 0; i<ws->num_coins; i++)
+  {
+    struct CoinState *cs = &ws->coins[i];
+
+    TALER_denom_sig_free (&cs->sig);
+    if (NULL != cs->pk)
+    {
+      TALER_EXCHANGE_destroy_denomination_key (cs->pk);
+      cs->pk = NULL;
+    }
+    if (0 < ws->age)
+      TALER_age_commitment_proof_free (&cs->age_commitment_proof);
+  }
+  GNUNET_free (ws->coins);
+  GNUNET_free (ws->exchange_url);
+  GNUNET_free (ws->reserve_payto_uri);
+  GNUNET_free (ws);
+}
+
+
+/**
+ * Offer internal data to a "withdraw" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+batch_withdraw_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct BatchWithdrawState *ws = cls;
+  struct CoinState *cs = &ws->coins[index];
+  struct TALER_TESTING_Trait traits[] = {
+    /* history entry MUST be first due to response code logic below! */
+    TALER_TESTING_make_trait_reserve_history (index,
+                                              &cs->reserve_history),
+    TALER_TESTING_make_trait_coin_priv (index,
+                                        &cs->coin_priv),
+    TALER_TESTING_make_trait_planchet_secrets (index,
+                                               &cs->ps),
+    TALER_TESTING_make_trait_blinding_key (index,
+                                           &cs->bks),
+    TALER_TESTING_make_trait_exchange_wd_value (index,
+                                                &cs->exchange_vals),
+    TALER_TESTING_make_trait_denom_pub (index,
+                                        cs->pk),
+    TALER_TESTING_make_trait_denom_sig (index,
+                                        &cs->sig),
+    TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
+    TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
+    TALER_TESTING_make_trait_amounts (index,
+                                      &cs->amount),
+    TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
+    TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+    TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+    TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
+    TALER_TESTING_make_trait_age_commitment_proof (index,
+                                                   ws->age > 0 ?
+                                                   &cs->age_commitment_proof:
+                                                   NULL),
+    TALER_TESTING_make_trait_h_age_commitment (index,
+                                               ws->age > 0 ?
+                                               &cs->h_age_commitment :
+                                               NULL),
+    TALER_TESTING_trait_end ()
+  };
+
+  if (index >= ws->num_coins)
+    return GNUNET_NO;
+  return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
+                                  ? &traits[0]   /* we have reserve history */
+                                  : &traits[1],  /* skip reserve history */
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_batch_withdraw (const char *label,
+                                  const char *reserve_reference,
+                                  uint8_t age,
+                                  unsigned int expected_response_code,
+                                  const char *amount,
+                                  ...)
+{
+  struct BatchWithdrawState *ws;
+  unsigned int cnt;
+  va_list ap;
+
+  ws = GNUNET_new (struct BatchWithdrawState);
+  ws->age = age;
+  ws->reserve_reference = reserve_reference;
+  ws->expected_response_code = expected_response_code;
+
+  cnt = 1;
+  va_start (ap, amount);
+  while (NULL != (va_arg (ap, const char *)))
+    cnt++;
+  ws->num_coins = cnt;
+  ws->coins = GNUNET_new_array (cnt,
+                                struct CoinState);
+  va_end (ap);
+  va_start (ap, amount);
+  for (unsigned int i = 0; i<ws->num_coins; i++)
+  {
+    struct CoinState *cs = &ws->coins[i];
+
+    if (0 < age)
+    {
+      struct GNUNET_HashCode seed;
+      struct TALER_AgeMask mask;
+
+      mask = TALER_extensions_get_age_restriction_mask ();
+      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                  &seed,
+                                  sizeof(seed));
+
+      if (GNUNET_OK !=
+          TALER_age_restriction_commit (
+            &mask,
+            age,
+            &seed,
+            &cs->age_commitment_proof))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Failed to generate age commitment for age %d at %s\n",
+                    age,
+                    label);
+        GNUNET_assert (0);
+      }
+
+      TALER_age_commitment_hash (&cs->age_commitment_proof.commitment,
+                                 &cs->h_age_commitment);
+    }
+
+    if (GNUNET_OK !=
+        TALER_string_to_amount (amount,
+                                &cs->amount))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to parse amount `%s' at %s\n",
+                  amount,
+                  label);
+      GNUNET_assert (0);
+    }
+    /* move on to next vararg! */
+    amount = va_arg (ap, const char *);
+  }
+  GNUNET_assert (NULL == amount);
+  va_end (ap);
+
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ws,
+      .label = label,
+      .run = &batch_withdraw_run,
+      .cleanup = &batch_withdraw_cleanup,
+      .traits = &batch_withdraw_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_batch_withdraw.c */
diff --git a/src/testing/testing_api_cmd_common.c 
b/src/testing/testing_api_cmd_common.c
new file mode 100644
index 0000000..2c29f4e
--- /dev/null
+++ b/src/testing/testing_api_cmd_common.c
@@ -0,0 +1,226 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-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 testing/testing_api_cmd_common.c
+ * @brief common functions for commands
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_testing_lib.h"
+
+
+int
+TALER_TESTING_history_entry_cmp (
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
+{
+  if (h1->type != h2->type)
+    return 1;
+  switch (h1->type)
+  {
+  case TALER_EXCHANGE_RTT_CREDIT:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 == strcasecmp (h1->details.in_details.sender_url,
+                           h2->details.in_details.sender_url)) &&
+         (h1->details.in_details.wire_reference ==
+          h2->details.in_details.wire_reference) &&
+         (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
+                                     ==,
+                                     h2->details.in_details.timestamp)) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_WITHDRAWAL:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 ==
+          TALER_amount_cmp (&h1->details.withdraw.fee,
+                            &h2->details.withdraw.fee)) )
+      /* testing_api_cmd_withdraw doesn't set the out_authorization_sig,
+         so we cannot test for it here. but if the amount matches,
+         that should be good enough. */
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_AGEWITHDRAWAL:
+    /* testing_api_cmd_age_withdraw doesn't set the out_authorization_sig,
+       so we cannot test for it here. but if the amount matches,
+       that should be good enough. */
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 ==
+          TALER_amount_cmp (&h1->details.age_withdraw.fee,
+                            &h2->details.age_withdraw.fee)) &&
+         (h1->details.age_withdraw.max_age ==
+          h2->details.age_withdraw.max_age))
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_RECOUP:
+    /* exchange_sig, exchange_pub and timestamp are NOT available
+       from the original recoup response, hence here NOT check(able/ed) */
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
+                         &h2->details.recoup_details.coin_pub)) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_CLOSING:
+    /* testing_api_cmd_exec_closer doesn't set the
+       receiver_account_details, exchange_sig, exchange_pub or wtid or 
timestamp
+       so we cannot test for it here. but if the amount matches,
+       that should be good enough. */
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 ==
+          TALER_amount_cmp (&h1->details.close_details.fee,
+                            &h2->details.close_details.fee)) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_HISTORY:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (GNUNET_TIME_timestamp_cmp (
+            h1->details.history_details.request_timestamp,
+            ==,
+            h2->details.history_details.request_timestamp)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.history_details.reserve_sig,
+                         &h2->details.history_details.reserve_sig)) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_MERGE:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (0 ==
+          TALER_amount_cmp (&h1->details.merge_details.purse_fee,
+                            &h2->details.merge_details.purse_fee)) &&
+         (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp,
+                                     ==,
+                                     
h2->details.merge_details.merge_timestamp))
+         &&
+         (GNUNET_TIME_timestamp_cmp 
(h1->details.merge_details.purse_expiration,
+                                     ==,
+                                     
h2->details.merge_details.purse_expiration))
+         &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.merge_details.merge_pub,
+                         &h2->details.merge_details.merge_pub)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.merge_details.h_contract_terms,
+                         &h2->details.merge_details.h_contract_terms)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.merge_details.purse_pub,
+                         &h2->details.merge_details.purse_pub)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.merge_details.reserve_sig,
+                         &h2->details.merge_details.reserve_sig)) &&
+         (h1->details.merge_details.min_age ==
+          h2->details.merge_details.min_age) &&
+         (h1->details.merge_details.flags ==
+          h2->details.merge_details.flags) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_OPEN:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (GNUNET_TIME_timestamp_cmp (
+            h1->details.open_request.request_timestamp,
+            ==,
+            h2->details.open_request.request_timestamp)) &&
+         (GNUNET_TIME_timestamp_cmp (
+            h1->details.open_request.reserve_expiration,
+            ==,
+            h2->details.open_request.reserve_expiration)) &&
+         (h1->details.open_request.purse_limit ==
+          h2->details.open_request.purse_limit) &&
+         (0 ==
+          TALER_amount_cmp (&h1->details.open_request.reserve_payment,
+                            &h2->details.open_request.reserve_payment)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.open_request.reserve_sig,
+                         &h2->details.open_request.reserve_sig)) )
+      return 0;
+    return 1;
+  case TALER_EXCHANGE_RTT_CLOSE:
+    if ( (0 ==
+          TALER_amount_cmp (&h1->amount,
+                            &h2->amount)) &&
+         (GNUNET_TIME_timestamp_cmp (
+            h1->details.close_request.request_timestamp,
+            ==,
+            h2->details.close_request.request_timestamp)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.close_request.target_account_h_payto,
+                         &h2->details.close_request.target_account_h_payto)) &&
+         (0 ==
+          GNUNET_memcmp (&h1->details.close_request.reserve_sig,
+                         &h2->details.close_request.reserve_sig)) )
+      return 0;
+    return 1;
+  }
+  GNUNET_assert (0);
+  return 1;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_parse_coin_reference (
+  const char *coin_reference,
+  char **cref,
+  unsigned int *idx)
+{
+  const char *index;
+  char dummy;
+
+  /* We allow command references of the form "$LABEL#$INDEX" or
+     just "$LABEL", which implies the index is 0. Figure out
+     which one it is. */
+  index = strchr (coin_reference, '#');
+  if (NULL == index)
+  {
+    *idx = 0;
+    *cref = GNUNET_strdup (coin_reference);
+    return GNUNET_OK;
+  }
+  *cref = GNUNET_strndup (coin_reference,
+                          index - coin_reference);
+  if (1 != sscanf (index + 1,
+                   "%u%c",
+                   idx,
+                   &dummy))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Numeric index (not `%s') required after `#' in command 
reference of command in %s:%u\n",
+                index,
+                __FILE__,
+                __LINE__);
+    GNUNET_free (*cref);
+    *cref = NULL;
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
diff --git a/src/testing/testing_api_cmd_deposit.c 
b/src/testing/testing_api_cmd_deposit.c
new file mode 100644
index 0000000..0ee6aa4
--- /dev/null
+++ b/src/testing/testing_api_cmd_deposit.c
@@ -0,0 +1,791 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-2021 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 testing/testing_api_cmd_deposit.c
+ * @brief command for testing /deposit.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_signatures.h"
+#include "backoff.h"
+
+
+/**
+ * How often do we retry before giving up?
+ */
+#define NUM_RETRIES 5
+
+/**
+ * How long do we wait AT MOST when retrying?
+ */
+#define MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MILLISECONDS, 100)
+
+
+/**
+ * State for a "deposit" CMD.
+ */
+struct DepositState
+{
+
+  /**
+   * Amount to deposit.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * Deposit fee.
+   */
+  struct TALER_Amount deposit_fee;
+
+  /**
+   * Reference to any command that is able to provide a coin.
+   */
+  const char *coin_reference;
+
+  /**
+   * If @e coin_reference refers to an operation that generated
+   * an array of coins, this value determines which coin to pick.
+   */
+  unsigned int coin_index;
+
+  /**
+   * Wire details of who is depositing -- this would be merchant
+   * wire details in a normal scenario.
+   */
+  json_t *wire_details;
+
+  /**
+   * JSON string describing what a proposal is about.
+   */
+  json_t *contract_terms;
+
+  /**
+   * Refund deadline. Zero for no refunds.
+   */
+  struct GNUNET_TIME_Timestamp refund_deadline;
+
+  /**
+   * Wire deadline.
+   */
+  struct GNUNET_TIME_Timestamp wire_deadline;
+
+  /**
+   * Set (by the interpreter) to a fresh private key.  This
+   * key will be used to sign the deposit request.
+   */
+  struct TALER_MerchantPrivateKeyP merchant_priv;
+
+  /**
+   * Deposit handle while operation is running.
+   */
+  struct TALER_EXCHANGE_BatchDepositHandle *dh;
+
+  /**
+   * Timestamp of the /deposit operation in the wallet (contract signing time).
+   */
+  struct GNUNET_TIME_Timestamp wallet_timestamp;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Task scheduled to try later.
+   */
+  struct GNUNET_SCHEDULER_Task *retry_task;
+
+  /**
+   * How long do we wait until we retry?
+   */
+  struct GNUNET_TIME_Relative backoff;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * How often should we retry on (transient) failures?
+   */
+  unsigned int do_retry;
+
+  /**
+   * Set to #GNUNET_YES if the /deposit succeeded
+   * and we now can provide the resulting traits.
+   */
+  int deposit_succeeded;
+
+  /**
+   * When did the exchange receive the deposit?
+   */
+  struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+  /**
+   * Signing key used by the exchange to sign the
+   * deposit confirmation.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature from the exchange on the
+   * deposit confirmation.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+  /**
+   * Reference to previous deposit operation.
+   * Only present if we're supposed to replay the previous deposit.
+   */
+  const char *deposit_reference;
+
+  /**
+   * Did we set the parameters for this deposit command?
+   *
+   * When we're referencing another deposit operation,
+   * this will only be set after the command has been started.
+   */
+  int command_initialized;
+
+  /**
+   * Reference to fetch the merchant private key from.
+   * If NULL, we generate our own, fresh merchant key.
+   */
+  const char *merchant_priv_reference;
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+             const struct TALER_TESTING_Command *cmd,
+             struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #deposit_run.
+ *
+ * @param cls a `struct DepositState`
+ */
+static void
+do_retry (void *cls)
+{
+  struct DepositState *ds = cls;
+
+  ds->retry_task = NULL;
+  TALER_TESTING_touch_cmd (ds->is);
+  deposit_run (ds,
+               NULL,
+               ds->is);
+}
+
+
+/**
+ * Callback to analyze the /deposit response, just used to
+ * check if the response code is acceptable.
+ *
+ * @param cls closure.
+ * @param dr deposit response details
+ */
+static void
+deposit_cb (void *cls,
+            const struct TALER_EXCHANGE_BatchDepositResult *dr)
+{
+  struct DepositState *ds = cls;
+
+  ds->dh = NULL;
+  if (ds->expected_response_code != dr->hr.http_status)
+  {
+    if (0 != ds->do_retry)
+    {
+      ds->do_retry--;
+      if ( (0 == dr->hr.http_status) ||
+           (TALER_EC_GENERIC_DB_SOFT_FAILURE == dr->hr.ec) ||
+           (MHD_HTTP_INTERNAL_SERVER_ERROR == dr->hr.http_status) )
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Retrying deposit failed with %u/%d\n",
+                    dr->hr.http_status,
+                    (int) dr->hr.ec);
+        /* on DB conflicts, do not use backoff */
+        if (TALER_EC_GENERIC_DB_SOFT_FAILURE == dr->hr.ec)
+          ds->backoff = GNUNET_TIME_UNIT_ZERO;
+        else
+          ds->backoff = GNUNET_TIME_randomized_backoff (ds->backoff,
+                                                        MAX_BACKOFF);
+        TALER_TESTING_inc_tries (ds->is);
+        GNUNET_assert (NULL == ds->retry_task);
+        ds->retry_task
+          = GNUNET_SCHEDULER_add_delayed (ds->backoff,
+                                          &do_retry,
+                                          ds);
+        return;
+      }
+    }
+    TALER_TESTING_unexpected_status_with_body (
+      ds->is,
+      dr->hr.http_status,
+      ds->expected_response_code,
+      dr->hr.reply);
+
+    return;
+  }
+  if (MHD_HTTP_OK == dr->hr.http_status)
+  {
+    GNUNET_assert (1 == dr->details.ok.num_signatures);
+    ds->deposit_succeeded = GNUNET_YES;
+    ds->exchange_timestamp = dr->details.ok.deposit_timestamp;
+    ds->exchange_pub = *dr->details.ok.exchange_pub;
+    ds->exchange_sig = dr->details.ok.exchange_sigs[0];
+  }
+  TALER_TESTING_interpreter_next (ds->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+deposit_run (void *cls,
+             const struct TALER_TESTING_Command *cmd,
+             struct TALER_TESTING_Interpreter *is)
+{
+  struct DepositState *ds = cls;
+  const struct TALER_TESTING_Command *coin_cmd;
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  const struct TALER_AgeCommitmentHash *phac;
+  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+  const struct TALER_DenominationSignature *denom_pub_sig;
+  struct TALER_CoinSpendSignatureP coin_sig;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  enum TALER_ErrorCode ec;
+  struct TALER_WireSaltP wire_salt;
+  const char *payto_uri;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("payto_uri",
+                             &payto_uri),
+    GNUNET_JSON_spec_fixed_auto ("salt",
+                                 &wire_salt),
+    GNUNET_JSON_spec_end ()
+  };
+  const char *exchange_url
+    = TALER_TESTING_get_exchange_url (is);
+
+  (void) cmd;
+  if (NULL == exchange_url)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  ds->is = is;
+  if (NULL != ds->deposit_reference)
+  {
+    /* We're copying another deposit operation, initialize here. */
+    const struct TALER_TESTING_Command *cmd;
+    struct DepositState *ods;
+
+    cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                    ds->deposit_reference);
+    if (NULL == cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    ods = cmd->cls;
+    ds->coin_reference = ods->coin_reference;
+    ds->coin_index = ods->coin_index;
+    ds->wire_details = json_incref (ods->wire_details);
+    GNUNET_assert (NULL != ds->wire_details);
+    ds->contract_terms = json_incref (ods->contract_terms);
+    ds->wallet_timestamp = ods->wallet_timestamp;
+    ds->refund_deadline = ods->refund_deadline;
+    ds->amount = ods->amount;
+    ds->merchant_priv = ods->merchant_priv;
+    ds->command_initialized = GNUNET_YES;
+  }
+  else if (NULL != ds->merchant_priv_reference)
+  {
+    /* We're copying the merchant key from another deposit operation */
+    const struct TALER_MerchantPrivateKeyP *merchant_priv;
+    const struct TALER_TESTING_Command *cmd;
+
+    cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                    
ds->merchant_priv_reference);
+    if (NULL == cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if ( (GNUNET_OK !=
+          TALER_TESTING_get_trait_merchant_priv (cmd,
+                                                 &merchant_priv)) )
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    ds->merchant_priv = *merchant_priv;
+  }
+  GNUNET_assert (NULL != ds->wire_details);
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (ds->wire_details,
+                         spec,
+                         NULL, NULL))
+  {
+    json_dumpf (ds->wire_details,
+                stderr,
+                JSON_INDENT (2));
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (ds->coin_reference);
+  coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                       ds->coin_reference);
+  if (NULL == coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+
+  if ( (GNUNET_OK !=
+        TALER_TESTING_get_trait_coin_priv (coin_cmd,
+                                           ds->coin_index,
+                                           &coin_priv)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+                                                  ds->coin_index,
+                                                  &phac)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_denom_pub (coin_cmd,
+                                           ds->coin_index,
+                                           &denom_pub)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_denom_sig (coin_cmd,
+                                           ds->coin_index,
+                                           &denom_pub_sig)) ||
+       (GNUNET_OK !=
+        TALER_JSON_contract_hash (ds->contract_terms,
+                                  &h_contract_terms)) )
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+
+  ds->deposit_fee = denom_pub->fees.deposit;
+  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+                                      &coin_pub.eddsa_pub);
+
+  if (! GNUNET_TIME_absolute_is_zero (ds->refund_deadline.abs_time))
+  {
+    struct GNUNET_TIME_Relative refund_deadline;
+
+    refund_deadline
+      = GNUNET_TIME_absolute_get_remaining (ds->refund_deadline.abs_time);
+    ds->wire_deadline
+      =
+        GNUNET_TIME_relative_to_timestamp (
+          GNUNET_TIME_relative_multiply (refund_deadline,
+                                         2));
+  }
+  else
+  {
+    ds->refund_deadline = ds->wallet_timestamp;
+    ds->wire_deadline = GNUNET_TIME_timestamp_get ();
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ds->merchant_priv.eddsa_priv,
+                                      &merchant_pub.eddsa_pub);
+  {
+    struct TALER_MerchantWireHashP h_wire;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_JSON_merchant_wire_signature_hash (ds->wire_details,
+                                                            &h_wire));
+    TALER_wallet_deposit_sign (&ds->amount,
+                               &denom_pub->fees.deposit,
+                               &h_wire,
+                               &h_contract_terms,
+                               NULL, /* wallet data hash */
+                               phac,
+                               NULL, /* hash of extensions */
+                               &denom_pub->h_key,
+                               ds->wallet_timestamp,
+                               &merchant_pub,
+                               ds->refund_deadline,
+                               coin_priv,
+                               &coin_sig);
+  }
+  GNUNET_assert (NULL == ds->dh);
+  {
+    struct TALER_EXCHANGE_CoinDepositDetail cdd = {
+      .amount = ds->amount,
+      .coin_pub = coin_pub,
+      .coin_sig = coin_sig,
+      .denom_sig = *denom_pub_sig,
+      .h_denom_pub = denom_pub->h_key,
+      .h_age_commitment = {{{0}}},
+    };
+    struct TALER_EXCHANGE_DepositContractDetail dcd = {
+      .wire_deadline = ds->wire_deadline,
+      .merchant_payto_uri = payto_uri,
+      .wire_salt = wire_salt,
+      .h_contract_terms = h_contract_terms,
+      .wallet_timestamp = ds->wallet_timestamp,
+      .merchant_pub = merchant_pub,
+      .refund_deadline = ds->refund_deadline
+    };
+
+    if (NULL != phac)
+      cdd.h_age_commitment = *phac;
+
+    ds->dh = TALER_EXCHANGE_batch_deposit (
+      TALER_TESTING_interpreter_get_context (is),
+      exchange_url,
+      TALER_TESTING_get_keys (is),
+      &dcd,
+      1,
+      &cdd,
+      &deposit_cb,
+      ds,
+      &ec);
+  }
+  if (NULL == ds->dh)
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not create deposit with EC %d\n",
+                (int) ec);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+/**
+ * Free the state of a "deposit" CMD, and possibly cancel a
+ * pending operation thereof.
+ *
+ * @param cls closure, must be a `struct DepositState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+deposit_cleanup (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
+{
+  struct DepositState *ds = cls;
+
+  if (NULL != ds->dh)
+  {
+    TALER_TESTING_command_incomplete (ds->is,
+                                      cmd->label);
+    TALER_EXCHANGE_batch_deposit_cancel (ds->dh);
+    ds->dh = NULL;
+  }
+  if (NULL != ds->retry_task)
+  {
+    GNUNET_SCHEDULER_cancel (ds->retry_task);
+    ds->retry_task = NULL;
+  }
+  json_decref (ds->wire_details);
+  json_decref (ds->contract_terms);
+  GNUNET_free (ds);
+}
+
+
+/**
+ * Offer internal data from a "deposit" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+deposit_traits (void *cls,
+                const void **ret,
+                const char *trait,
+                unsigned int index)
+{
+  struct DepositState *ds = cls;
+  const struct TALER_TESTING_Command *coin_cmd;
+  /* Will point to coin cmd internals. */
+  const struct TALER_CoinSpendPrivateKeyP *coin_spent_priv;
+  const struct TALER_AgeCommitmentProof *age_commitment_proof;
+  const struct TALER_AgeCommitmentHash *h_age_commitment;
+
+  if (GNUNET_YES != ds->command_initialized)
+  {
+    /* No access to traits yet. */
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+
+  coin_cmd
+    = TALER_TESTING_interpreter_lookup_command (ds->is,
+                                                ds->coin_reference);
+  if (NULL == coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (ds->is);
+    return GNUNET_NO;
+  }
+  if ( (GNUNET_OK !=
+        TALER_TESTING_get_trait_coin_priv (coin_cmd,
+                                           ds->coin_index,
+                                           &coin_spent_priv)) ||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_age_commitment_proof (coin_cmd,
+                                                      ds->coin_index,
+                                                      &age_commitment_proof)) 
||
+       (GNUNET_OK !=
+        TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
+                                                  ds->coin_index,
+                                                  &h_age_commitment)) )
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (ds->is);
+    return GNUNET_NO;
+  }
+
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      /* First two traits are only available if
+         ds->traits is #GNUNET_YES */
+      TALER_TESTING_make_trait_exchange_pub (0,
+                                             &ds->exchange_pub),
+      TALER_TESTING_make_trait_exchange_sig (0,
+                                             &ds->exchange_sig),
+      /* These traits are always available */
+      TALER_TESTING_make_trait_coin_priv (0,
+                                          coin_spent_priv),
+      TALER_TESTING_make_trait_age_commitment_proof (0,
+                                                     age_commitment_proof),
+      TALER_TESTING_make_trait_h_age_commitment (0,
+                                                 h_age_commitment),
+      TALER_TESTING_make_trait_wire_details (ds->wire_details),
+      TALER_TESTING_make_trait_contract_terms (ds->contract_terms),
+      TALER_TESTING_make_trait_merchant_priv (&ds->merchant_priv),
+      TALER_TESTING_make_trait_deposit_amount (0,
+                                               &ds->amount),
+      TALER_TESTING_make_trait_deposit_fee_amount (0,
+                                                   &ds->deposit_fee),
+      TALER_TESTING_make_trait_timestamp (0,
+                                          &ds->exchange_timestamp),
+      TALER_TESTING_make_trait_wire_deadline (0,
+                                              &ds->wire_deadline),
+      TALER_TESTING_make_trait_refund_deadline (0,
+                                                &ds->refund_deadline),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait ((ds->deposit_succeeded)
+                                    ? traits
+                                    : &traits[2],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit (const char *label,
+                           const char *coin_reference,
+                           unsigned int coin_index,
+                           const char *target_account_payto,
+                           const char *contract_terms,
+                           struct GNUNET_TIME_Relative refund_deadline,
+                           const char *amount,
+                           unsigned int expected_response_code)
+{
+  struct DepositState *ds;
+
+  ds = GNUNET_new (struct DepositState);
+  ds->coin_reference = coin_reference;
+  ds->coin_index = coin_index;
+  ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+  GNUNET_assert (NULL != ds->wire_details);
+  ds->contract_terms = json_loads (contract_terms,
+                                   JSON_REJECT_DUPLICATES,
+                                   NULL);
+  GNUNET_CRYPTO_eddsa_key_create (&ds->merchant_priv.eddsa_priv);
+  if (NULL == ds->contract_terms)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse contract terms `%s' for CMD `%s'\n",
+                contract_terms,
+                label);
+    GNUNET_assert (0);
+  }
+  ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+  GNUNET_assert (0 ==
+                 json_object_set_new (ds->contract_terms,
+                                      "timestamp",
+                                      GNUNET_JSON_from_timestamp (
+                                        ds->wallet_timestamp)));
+  if (! GNUNET_TIME_relative_is_zero (refund_deadline))
+  {
+    ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+    GNUNET_assert (0 ==
+                   json_object_set_new (ds->contract_terms,
+                                        "refund_deadline",
+                                        GNUNET_JSON_from_timestamp (
+                                          ds->refund_deadline)));
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &ds->amount));
+  ds->expected_response_code = expected_response_code;
+  ds->command_initialized = GNUNET_YES;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ds,
+      .label = label,
+      .run = &deposit_run,
+      .cleanup = &deposit_cleanup,
+      .traits = &deposit_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_with_ref (const char *label,
+                                    const char *coin_reference,
+                                    unsigned int coin_index,
+                                    const char *target_account_payto,
+                                    const char *contract_terms,
+                                    struct GNUNET_TIME_Relative 
refund_deadline,
+                                    const char *amount,
+                                    unsigned int expected_response_code,
+                                    const char *merchant_priv_reference)
+{
+  struct DepositState *ds;
+
+  ds = GNUNET_new (struct DepositState);
+  ds->merchant_priv_reference = merchant_priv_reference;
+  ds->coin_reference = coin_reference;
+  ds->coin_index = coin_index;
+  ds->wire_details = TALER_TESTING_make_wire_details (target_account_payto);
+  GNUNET_assert (NULL != ds->wire_details);
+  ds->contract_terms = json_loads (contract_terms,
+                                   JSON_REJECT_DUPLICATES,
+                                   NULL);
+  if (NULL == ds->contract_terms)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse contract terms `%s' for CMD `%s'\n",
+                contract_terms,
+                label);
+    GNUNET_assert (0);
+  }
+  ds->wallet_timestamp = GNUNET_TIME_timestamp_get ();
+  GNUNET_assert (0 ==
+                 json_object_set_new (ds->contract_terms,
+                                      "timestamp",
+                                      GNUNET_JSON_from_timestamp (
+                                        ds->wallet_timestamp)));
+  if (0 != refund_deadline.rel_value_us)
+  {
+    ds->refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_deadline);
+    GNUNET_assert (0 ==
+                   json_object_set_new (ds->contract_terms,
+                                        "refund_deadline",
+                                        GNUNET_JSON_from_timestamp (
+                                          ds->refund_deadline)));
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (amount,
+                                         &ds->amount));
+  ds->expected_response_code = expected_response_code;
+  ds->command_initialized = GNUNET_YES;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ds,
+      .label = label,
+      .run = &deposit_run,
+      .cleanup = &deposit_cleanup,
+      .traits = &deposit_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_replay (const char *label,
+                                  const char *deposit_reference,
+                                  unsigned int expected_response_code)
+{
+  struct DepositState *ds;
+
+  ds = GNUNET_new (struct DepositState);
+  ds->deposit_reference = deposit_reference;
+  ds->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ds,
+      .label = label,
+      .run = &deposit_run,
+      .cleanup = &deposit_cleanup,
+      .traits = &deposit_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_deposit_with_retry (struct TALER_TESTING_Command cmd)
+{
+  struct DepositState *ds;
+
+  GNUNET_assert (&deposit_run == cmd.run);
+  ds = cmd.cls;
+  ds->do_retry = NUM_RETRIES;
+  return cmd;
+}
+
+
+/* end of testing_api_cmd_deposit.c */
diff --git a/src/testing/testing_api_cmd_deposits_get.c 
b/src/testing/testing_api_cmd_deposits_get.c
new file mode 100644
index 0000000..5d4436e
--- /dev/null
+++ b/src/testing/testing_api_cmd_deposits_get.c
@@ -0,0 +1,375 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2021 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU 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 testing/testing_api_cmd_deposits_get.c
+ * @brief Implement the testing CMDs for the /deposits/ GET operations.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "track transaction" CMD.
+ */
+struct TrackTransactionState
+{
+
+  /**
+   * If non NULL, will provide a WTID to be compared against
+   * the one returned by the "track transaction" operation.
+   */
+  const char *bank_transfer_reference;
+
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
+  /**
+   * The WTID associated by the transaction being tracked.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Set to the KYC requirement payto hash *if* the exchange replied with a
+   * request for KYC (#MHD_HTTP_ACCEPTED).
+   * Note: set based on our @e merchant_payto_uri, as
+   * the exchange does not respond with the payto hash.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Set to the KYC requirement row *if* the exchange replied with
+   * a request for KYC (#MHD_HTTP_ACCEPTED).
+   */
+  uint64_t requirement_row;
+
+  /**
+   * Reference to any operation that can provide a transaction.
+   * Will be the transaction to track.
+   */
+  const char *transaction_reference;
+
+  /**
+   * Payto URI of the merchant receiving the deposit.
+   */
+  char *merchant_payto_uri;
+
+  /**
+   * Index of the coin involved in the transaction.  Recall:
+   * at the exchange, the tracking is done _per coin_.
+   */
+  unsigned int coin_index;
+
+  /**
+   * Handle to the "track transaction" pending operation.
+   */
+  struct TALER_EXCHANGE_DepositGetHandle *tth;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Checks what is returned by the "track transaction" operation.
+ * Checks that the HTTP response code is acceptable, and - if the
+ * right reference is non NULL - that the wire transfer subject
+ * line matches our expectations.
+ *
+ * @param cls closure.
+ * @param dr GET deposit response details
+ */
+static void
+deposit_wtid_cb (void *cls,
+                 const struct TALER_EXCHANGE_GetDepositResponse *dr)
+{
+  struct TrackTransactionState *tts = cls;
+  struct TALER_TESTING_Interpreter *is = tts->is;
+
+  tts->tth = NULL;
+  if (tts->expected_response_code != dr->hr.http_status)
+  {
+    TALER_TESTING_unexpected_status (is,
+                                     dr->hr.http_status,
+                                     tts->expected_response_code);
+    return;
+  }
+  switch (dr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    tts->wtid = dr->details.ok.wtid;
+    if (NULL != tts->bank_transfer_reference)
+    {
+      const struct TALER_TESTING_Command *bank_transfer_cmd;
+      const struct TALER_WireTransferIdentifierRawP *wtid_want;
+
+      /* _this_ wire transfer subject line.  */
+      bank_transfer_cmd
+        = TALER_TESTING_interpreter_lookup_command (is,
+                                                    
tts->bank_transfer_reference);
+      if (NULL == bank_transfer_cmd)
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_wtid (bank_transfer_cmd,
+                                        &wtid_want))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      /* Compare that expected and gotten subjects match.  */
+      if (0 != GNUNET_memcmp (&dr->details.ok.wtid,
+                              wtid_want))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (tts->is);
+        return;
+      }
+    }
+    break;
+  case MHD_HTTP_ACCEPTED:
+    /* allowed, nothing to check here */
+    TALER_payto_hash (tts->merchant_payto_uri,
+                      &tts->h_payto);
+    tts->requirement_row
+      = dr->details.accepted.requirement_row;
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* allowed, nothing to check here */
+    break;
+  default:
+    GNUNET_break (0);
+    break;
+  }
+  TALER_TESTING_interpreter_next (tts->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+track_transaction_run (void *cls,
+                       const struct TALER_TESTING_Command *cmd,
+                       struct TALER_TESTING_Interpreter *is)
+{
+  struct TrackTransactionState *tts = cls;
+  const struct TALER_TESTING_Command *transaction_cmd;
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  const json_t *contract_terms;
+  const json_t *wire_details;
+  struct TALER_MerchantWireHashP h_wire_details;
+  struct TALER_PrivateContractHashP h_contract_terms;
+  const struct TALER_MerchantPrivateKeyP *merchant_priv;
+
+  tts->cmd = cmd;
+  tts->is = is;
+  transaction_cmd
+    = TALER_TESTING_interpreter_lookup_command (tts->is,
+                                                tts->transaction_reference);
+  if (NULL == transaction_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_coin_priv (transaction_cmd,
+                                         tts->coin_index,
+                                         &coin_priv))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+
+  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
+                                      &coin_pub.eddsa_pub);
+
+  /* Get the strings.. */
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_wire_details (transaction_cmd,
+                                            &wire_details))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+  tts->merchant_payto_uri
+    = GNUNET_strdup (json_string_value (json_object_get (wire_details,
+                                                         "payto_uri")));
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_contract_terms (transaction_cmd,
+                                              &contract_terms))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+
+  if ( (NULL == wire_details) ||
+       (NULL == contract_terms) )
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+
+  /* Should not fail here, json has been parsed already */
+  GNUNET_assert
+    ( (GNUNET_OK ==
+       TALER_JSON_merchant_wire_signature_hash (wire_details,
+                                                &h_wire_details)) &&
+    (GNUNET_OK ==
+     TALER_JSON_contract_hash (contract_terms,
+                               &h_contract_terms)) );
+
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_merchant_priv (transaction_cmd,
+                                             &merchant_priv))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+
+  tts->tth = TALER_EXCHANGE_deposits_get (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    TALER_TESTING_get_keys (is),
+    merchant_priv,
+    &h_wire_details,
+    &h_contract_terms,
+    &coin_pub,
+    GNUNET_TIME_UNIT_ZERO,
+    &deposit_wtid_cb,
+    tts);
+  GNUNET_assert (NULL != tts->tth);
+}
+
+
+/**
+ * Cleanup the state from a "track transaction" CMD, and possibly
+ * cancel a operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+track_transaction_cleanup (void *cls,
+                           const struct TALER_TESTING_Command *cmd)
+{
+  struct TrackTransactionState *tts = cls;
+
+  if (NULL != tts->tth)
+  {
+    TALER_TESTING_command_incomplete (tts->is,
+                                      cmd->label);
+    TALER_EXCHANGE_deposits_get_cancel (tts->tth);
+    tts->tth = NULL;
+  }
+  GNUNET_free (tts->merchant_payto_uri);
+  GNUNET_free (tts);
+}
+
+
+/**
+ * Offer internal data from a "track transaction" CMD.
+ *
+ * @param cls closure.
+ * @param[out] ret result (could be anything).
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+track_transaction_traits (void *cls,
+                          const void **ret,
+                          const char *trait,
+                          unsigned int index)
+{
+  struct TrackTransactionState *tts = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_wtid (&tts->wtid),
+    TALER_TESTING_make_trait_legi_requirement_row (
+      &tts->requirement_row),
+    TALER_TESTING_make_trait_h_payto (&tts->h_payto),
+    TALER_TESTING_make_trait_payto_uri (tts->merchant_payto_uri),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transaction (const char *label,
+                                     const char *transaction_reference,
+                                     unsigned int coin_index,
+                                     unsigned int expected_response_code,
+                                     const char *bank_transfer_reference)
+{
+  struct TrackTransactionState *tts;
+
+  tts = GNUNET_new (struct TrackTransactionState);
+  tts->transaction_reference = transaction_reference;
+  tts->expected_response_code = expected_response_code;
+  tts->bank_transfer_reference = bank_transfer_reference;
+  tts->coin_index = coin_index;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tts,
+      .label = label,
+      .run = &track_transaction_run,
+      .cleanup = &track_transaction_cleanup,
+      .traits = &track_transaction_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_deposits_get.c */
diff --git a/src/testing/testing_api_cmd_get_exchange.c 
b/src/testing/testing_api_cmd_get_exchange.c
new file mode 100644
index 0000000..69a6e82
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_exchange.c
@@ -0,0 +1,411 @@
+/*
+  This file is part of TALER
+  (C) 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_get_exchange.c
+ * @brief Command to get an exchange handle
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "get exchange" CMD.
+ */
+struct GetExchangeState
+{
+
+  /**
+   * Master private key of the exchange.
+   */
+  struct TALER_MasterPrivateKeyP master_priv;
+
+  /**
+   * Our interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Exchange handle we produced.
+   */
+  struct TALER_EXCHANGE_GetKeysHandle *exchange;
+
+  /**
+   * Keys of the exchange.
+   */
+  struct TALER_EXCHANGE_Keys *keys;
+
+  /**
+   * URL of the exchange.
+   */
+  char *exchange_url;
+
+  /**
+   * Filename of the master private key of the exchange.
+   */
+  char *master_priv_file;
+
+  /**
+   * Label of a command to use to obtain existing
+   * keys.
+   */
+  const char *last_keys_ref;
+
+  /**
+   * Last denomination date we received when doing this request.
+   */
+  struct GNUNET_TIME_Timestamp my_denom_date;
+
+  /**
+   * Are we waiting for /keys before continuing?
+   */
+  bool wait_for_keys;
+};
+
+
+/**
+ * Function called with information about who is auditing
+ * a particular exchange and what keys the exchange is using.
+ *
+ * @param cls closure
+ * @param kr response from /keys
+ * @param[in] keys the keys of the exchange
+ */
+static void
+cert_cb (void *cls,
+         const struct TALER_EXCHANGE_KeysResponse *kr,
+         struct TALER_EXCHANGE_Keys *keys)
+{
+  struct GetExchangeState *ges = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &kr->hr;
+  struct TALER_TESTING_Interpreter *is = ges->is;
+
+  ges->exchange = NULL;
+  ges->keys = keys;
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    if (ges->wait_for_keys)
+    {
+      ges->wait_for_keys = false;
+      TALER_TESTING_interpreter_next (is);
+      return;
+    }
+    ges->my_denom_date = kr->details.ok.keys->last_denom_issue_date;
+    return;
+  default:
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/keys responded with HTTP status %u\n",
+                hr->http_status);
+    if (ges->wait_for_keys)
+    {
+      ges->wait_for_keys = false;
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    return;
+  }
+}
+
+
+/**
+ * Run the "get_exchange" command.
+ *
+ * @param cls closure.
+ * @param cmd the command currently being executed.
+ * @param is the interpreter state.
+ */
+static void
+get_exchange_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  struct GetExchangeState *ges = cls;
+  struct TALER_EXCHANGE_Keys *xkeys = NULL;
+
+  (void) cmd;
+  if (NULL == ges->exchange_url)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL != ges->last_keys_ref)
+  {
+    const struct TALER_TESTING_Command *state_cmd;
+    struct TALER_EXCHANGE_Keys *old_keys;
+    const char *exchange_url;
+    json_t *s_keys;
+
+    state_cmd
+      = TALER_TESTING_interpreter_lookup_command (is,
+                                                  ges->last_keys_ref);
+    if (NULL == state_cmd)
+    {
+      /* Command providing serialized keys not found.  */
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_keys (state_cmd,
+                                      &old_keys))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (NULL == old_keys)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_exchange_url (state_cmd,
+                                              &exchange_url))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if (0 != strcmp (exchange_url,
+                     ges->exchange_url))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    s_keys = TALER_EXCHANGE_keys_to_json (old_keys);
+    if (NULL == s_keys)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    xkeys = TALER_EXCHANGE_keys_from_json (s_keys);
+    if (NULL == xkeys)
+    {
+      GNUNET_break (0);
+      json_dumpf (s_keys,
+                  stderr,
+                  JSON_INDENT (2));
+      json_decref (s_keys);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    json_decref (s_keys);
+  }
+  if (NULL != ges->master_priv_file)
+  {
+    if (GNUNET_SYSERR ==
+        GNUNET_CRYPTO_eddsa_key_from_file (ges->master_priv_file,
+                                           GNUNET_YES,
+                                           &ges->master_priv.eddsa_priv))
+    {
+      GNUNET_break (0);
+      TALER_EXCHANGE_keys_decref (xkeys);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
+  ges->is = is;
+  ges->exchange
+    = TALER_EXCHANGE_get_keys (TALER_TESTING_interpreter_get_context (is),
+                               ges->exchange_url,
+                               xkeys,
+                               &cert_cb,
+                               ges);
+  TALER_EXCHANGE_keys_decref (xkeys);
+  if (NULL == ges->exchange)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (! ges->wait_for_keys)
+    TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+get_exchange_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  struct GetExchangeState *ges = cls;
+
+  if (NULL != ges->exchange)
+  {
+    TALER_EXCHANGE_get_keys_cancel (ges->exchange);
+    ges->exchange = NULL;
+  }
+  TALER_EXCHANGE_keys_decref (ges->keys);
+  ges->keys = NULL;
+  GNUNET_free (ges->master_priv_file);
+  GNUNET_free (ges->exchange_url);
+  GNUNET_free (ges);
+}
+
+
+/**
+ * Offer internal data to a "get_exchange" CMD state to other commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+get_exchange_traits (void *cls,
+                     const void **ret,
+                     const char *trait,
+                     unsigned int index)
+{
+  struct GetExchangeState *ges = cls;
+  unsigned int off = (NULL == ges->master_priv_file) ? 1 : 0;
+
+  if (NULL != ges->keys)
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+      TALER_TESTING_make_trait_master_pub (&ges->keys->master_pub),
+      TALER_TESTING_make_trait_keys (ges->keys),
+      TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+      TALER_TESTING_make_trait_timestamp (0,
+                                          &ges->my_denom_date),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait (&traits[off],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+  else
+  {
+    struct TALER_TESTING_Trait traits[] = {
+      TALER_TESTING_make_trait_master_priv (&ges->master_priv),
+      TALER_TESTING_make_trait_exchange_url (ges->exchange_url),
+      TALER_TESTING_make_trait_timestamp (0,
+                                          &ges->my_denom_date),
+      TALER_TESTING_trait_end ()
+    };
+
+    return TALER_TESTING_get_trait (&traits[off],
+                                    ret,
+                                    trait,
+                                    index);
+  }
+}
+
+
+/**
+ * Get the base URL of the exchange from @a cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_base_url (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *exchange_url;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &exchange_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    return NULL;
+  }
+  return exchange_url;
+}
+
+
+/**
+ * Get the file name of the master private key file of the exchange from @a
+ * cfg.
+ *
+ * @param cfg configuration to evaluate
+ * @return base URL of the exchange according to @a cfg
+ */
+static char *
+get_exchange_master_priv_file (
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *fn;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "exchange-offline",
+                                               "MASTER_PRIV_FILE",
+                                               &fn))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "MASTER_PRIV_FILE");
+    return NULL;
+  }
+  return fn;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_get_exchange (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *cfg,
+  const char *last_keys_ref,
+  bool wait_for_keys,
+  bool load_private_key)
+{
+  struct GetExchangeState *ges;
+
+  ges = GNUNET_new (struct GetExchangeState);
+  ges->exchange_url = get_exchange_base_url (cfg);
+  ges->last_keys_ref = last_keys_ref;
+  if (load_private_key)
+    ges->master_priv_file = get_exchange_master_priv_file (cfg);
+  ges->wait_for_keys = wait_for_keys;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ges,
+      .label = label,
+      .run = &get_exchange_run,
+      .cleanup = &get_exchange_cleanup,
+      .traits = &get_exchange_traits,
+      .name = "exchange"
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_insert_deposit.c 
b/src/testing/testing_api_cmd_insert_deposit.c
new file mode 100644
index 0000000..dd89a48
--- /dev/null
+++ b/src/testing/testing_api_cmd_insert_deposit.c
@@ -0,0 +1,388 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018 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 testing/testing_api_cmd_insert_deposit.c
+ * @brief deposit a coin directly into the database.
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_exchangedb_lib.h"
+
+
+/**
+ * State for a "insert-deposit" CMD.
+ */
+struct InsertDepositState
+{
+  /**
+   * Database connection we use.
+   */
+  struct TALER_EXCHANGEDB_Plugin *plugin;
+
+  /**
+   * Human-readable name of the shop.
+   */
+  const char *merchant_name;
+
+  /**
+   * Merchant account name (NOT a payto-URI).
+   */
+  const char *merchant_account;
+
+  /**
+   * Deadline before which the aggregator should
+   * send the payment to the merchant.
+   */
+  struct GNUNET_TIME_Relative wire_deadline;
+
+  /**
+   * When did the exchange receive the deposit?
+   */
+  struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+  /**
+   * Amount to deposit, inclusive of deposit fee.
+   */
+  const char *amount_with_fee;
+
+  /**
+   * Deposit fee.
+   */
+  const char *deposit_fee;
+
+  /**
+   * Do we used a cached @e plugin?
+   */
+  bool cached;
+};
+
+/**
+ * Setup (fake) information about a coin used in deposit.
+ *
+ * @param[out] issue information to initialize with "valid" data
+ */
+static void
+fake_issue (struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+  struct GNUNET_TIME_Timestamp now;
+
+  memset (issue,
+          0,
+          sizeof (*issue));
+  now = GNUNET_TIME_timestamp_get ();
+  issue->start
+    = now;
+  issue->expire_withdraw
+    = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES);
+  issue->expire_deposit
+    = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS);
+  issue->expire_legal
+    = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_DAYS);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:1",
+                                         &issue->value));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.1",
+                                         &issue->fees.withdraw));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.1",
+                                         &issue->fees.deposit));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.1",
+                                         &issue->fees.refresh));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount ("EUR:0.1",
+                                         &issue->fees.refund));
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+insert_deposit_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct InsertDepositState *ids = cls;
+  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)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      ids->plugin->preflight (ids->plugin->cls))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  fake_issue (&issue);
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_denom_priv_create (&denom_priv,
+                                          &dpk,
+                                          TALER_DENOMINATION_RSA,
+                                          1024));
+  TALER_denom_pub_hash (&dpk,
+                        &issue.denom_hash);
+
+  if ( (GNUNET_OK !=
+        ids->plugin->start (ids->plugin->cls,
+                            "talertestinglib: denomination insertion")) ||
+       (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+        ids->plugin->insert_denomination_info (ids->plugin->cls,
+                                               &dpk,
+                                               &issue)) ||
+       (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+        ids->plugin->commit (ids->plugin->cls)) )
+  {
+    TALER_TESTING_interpreter_fail (is);
+    TALER_denom_pub_free (&dpk);
+    TALER_denom_priv_free (&denom_priv);
+    return;
+  }
+
+  /* prepare and store deposit now. */
+  memset (&deposit,
+          0,
+          sizeof (deposit));
+  memset (&bd,
+          0,
+          sizeof (bd));
+  bd.cdis = &deposit;
+  bd.num_cdis = 1;
+
+  GNUNET_assert (
+    GNUNET_YES ==
+    GNUNET_CRYPTO_kdf (&merchant_priv,
+                       sizeof (struct TALER_MerchantPrivateKeyP),
+                       "merchant-priv",
+                       strlen ("merchant-priv"),
+                       ids->merchant_name,
+                       strlen (ids->merchant_name),
+                       NULL,
+                       0));
+  GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv.eddsa_priv,
+                                      &bd.merchant_pub.eddsa_pub);
+  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_WEAK,
+                                    &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);
+    TALER_denom_priv_free (&denom_priv);
+    return;
+  }
+
+  TALER_denom_pub_hash (&dpk,
+                        &deposit.coin.denom_pub_hash);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &deposit.coin.coin_pub,
+                              sizeof (deposit.coin.coin_pub));
+  {
+    struct TALER_CoinPubHashP c_hash;
+    struct TALER_PlanchetDetail pd;
+    struct TALER_BlindedDenominationSignature bds;
+    struct TALER_PlanchetMasterSecretP ps;
+    struct TALER_ExchangeWithdrawValues alg_values;
+    union TALER_DenominationBlindingKeyP bks;
+
+    alg_values.cipher = TALER_DENOMINATION_RSA;
+    TALER_planchet_blinding_secret_create (&ps,
+                                           &alg_values,
+                                           &bks);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_denom_blind (&dpk,
+                                      &bks,
+                                      NULL, /* no age restriction active */
+                                      &deposit.coin.coin_pub,
+                                      &alg_values,
+                                      &c_hash,
+                                      &pd.blinded_planchet));
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_denom_sign_blinded (&bds,
+                                             &denom_priv,
+                                             false,
+                                             &pd.blinded_planchet));
+    TALER_blinded_planchet_free (&pd.blinded_planchet);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+                                            &bds,
+                                            &bks,
+                                            &c_hash,
+                                            &alg_values,
+                                            &dpk));
+    TALER_blinded_denom_sig_free (&bds);
+  }
+  GNUNET_asprintf (&receiver_wire_account,
+                   "payto://x-taler-bank/localhost/%s?receiver-name=%s",
+                   ids->merchant_account,
+                   ids->merchant_account);
+  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 (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,
+                              "libtalertesting: insert deposit")) ||
+         (0 >
+          ids->plugin->ensure_coin_known (ids->plugin->cls,
+                                          &deposit.coin,
+                                          &known_coin_id,
+                                          &dph,
+                                          &agh)) ||
+         (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+          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 (receiver_wire_account);
+      TALER_denom_pub_free (&dpk);
+      TALER_denom_priv_free (&denom_priv);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+  }
+
+  TALER_denom_sig_free (&deposit.coin.denom_sig);
+  TALER_denom_pub_free (&dpk);
+  TALER_denom_priv_free (&denom_priv);
+  GNUNET_free (receiver_wire_account);
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Free the state of a "auditor-dbinit" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+insert_deposit_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct InsertDepositState *ids = cls;
+
+  (void) cmd;
+  if ( (NULL != ids->plugin) &&
+       (! ids->cached) )
+  {
+    // FIXME: historically, we also did:
+    // ids->plugin->drop_tables (ids->plugin->cls);
+    TALER_EXCHANGEDB_plugin_unload (ids->plugin);
+    ids->plugin = NULL;
+  }
+  GNUNET_free (ids);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_insert_deposit (
+  const char *label,
+  const struct GNUNET_CONFIGURATION_Handle *db_cfg,
+  const char *merchant_name,
+  const char *merchant_account,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Relative wire_deadline,
+  const char *amount_with_fee,
+  const char *deposit_fee)
+{
+  static struct TALER_EXCHANGEDB_Plugin *pluginc;
+  static const struct GNUNET_CONFIGURATION_Handle *db_cfgc;
+  struct InsertDepositState *ids;
+
+  ids = GNUNET_new (struct InsertDepositState);
+  if (db_cfgc == db_cfg)
+  {
+    ids->plugin = pluginc;
+    ids->cached = true;
+  }
+  else
+  {
+    ids->plugin = TALER_EXCHANGEDB_plugin_load (db_cfg);
+    pluginc = ids->plugin;
+    db_cfgc = db_cfg;
+  }
+  ids->merchant_name = merchant_name;
+  ids->merchant_account = merchant_account;
+  ids->exchange_timestamp = exchange_timestamp;
+  ids->wire_deadline = wire_deadline;
+  ids->amount_with_fee = amount_with_fee;
+  ids->deposit_fee = deposit_fee;
+
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ids,
+      .label = label,
+      .run = &insert_deposit_run,
+      .cleanup = &insert_deposit_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_insert_deposit.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c 
b/src/testing/testing_api_cmd_offline_sign_extensions.c
new file mode 100644
index 0000000..f39679f
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_extensions.c
@@ -0,0 +1,164 @@
+/*
+  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 testing/testing_api_cmd_offline_sign_extensions.c
+ * @brief run the taler-exchange-offline command to sign extensions (and 
therefore activate them)
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "extensionssign" CMD.
+ */
+struct ExtensionsSignState
+{
+
+  /**
+   * Process for the "extensionssign" command.
+   */
+  struct GNUNET_OS_Process *extensionssign_proc;
+
+  /**
+   * Configuration file used by the command.
+   */
+  const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+extensionssign_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct ExtensionsSignState *ks = cls;
+
+  ks->extensionssign_proc
+    = GNUNET_OS_start_process (
+        GNUNET_OS_INHERIT_STD_ALL,
+        NULL, NULL, NULL,
+        "taler-exchange-offline",
+        "taler-exchange-offline",
+        "-c", ks->config_filename,
+        "-L", "INFO",
+        "extensions",
+        "sign",
+        "upload",
+        NULL);
+  if (NULL == ks->extensionssign_proc)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "extensionssign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+extensionssign_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct ExtensionsSignState *ks = cls;
+
+  (void) cmd;
+  if (NULL != ks->extensionssign_proc)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (ks->extensionssign_proc,
+                                          SIGKILL));
+    GNUNET_OS_process_wait (ks->extensionssign_proc);
+    GNUNET_OS_process_destroy (ks->extensionssign_proc);
+    ks->extensionssign_proc = NULL;
+  }
+  GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "extensionssign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+extensionssign_traits (void *cls,
+                       const void **ret,
+                       const char *trait,
+                       unsigned int index)
+{
+  struct ExtensionsSignState *ks = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_process (&ks->extensionssign_proc),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label,
+                                                const char *config_filename)
+{
+  struct ExtensionsSignState *ks;
+
+  ks = GNUNET_new (struct ExtensionsSignState);
+  ks->config_filename = config_filename;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ks,
+      .label = label,
+      .run = &extensionssign_run,
+      .cleanup = &extensionssign_cleanup,
+      .traits = &extensionssign_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_extensions.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_global_fees.c 
b/src/testing/testing_api_cmd_offline_sign_global_fees.c
new file mode 100644
index 0000000..db39162
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_global_fees.c
@@ -0,0 +1,230 @@
+/*
+  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 testing/testing_api_cmd_offline_sign_global_fees.c
+ * @brief run the taler-exchange-offline command to download, sign and upload 
global fees
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "offlinesign" CMD.
+ */
+struct OfflineSignState
+{
+
+  /**
+   * Process for the "offlinesign" command.
+   */
+  struct GNUNET_OS_Process *offlinesign_proc;
+
+  /**
+   * Configuration file used by the command.
+   */
+  const char *config_filename;
+
+  /**
+   * The history fee to sign.
+   */
+  const char *history_fee_s;
+
+  /**
+   * The account fee to sign.
+   */
+  const char *account_fee_s;
+
+  /**
+   * The purse fee to sign.
+   */
+  const char *purse_fee_s;
+
+  /**
+   * When MUST purses time out?
+   */
+  struct GNUNET_TIME_Relative purse_timeout;
+
+  /**
+   * How long do we keep the history?
+   */
+  struct GNUNET_TIME_Relative history_expiration;
+
+  /**
+   * Number of (free) purses per account.
+   */
+  unsigned int num_purses;
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+offlinesign_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
+{
+  struct OfflineSignState *ks = cls;
+  char num_purses[12];
+  char history_expiration[32];
+  char purse_timeout[32];
+
+  GNUNET_snprintf (num_purses,
+                   sizeof (num_purses),
+                   "%u",
+                   ks->num_purses);
+  GNUNET_snprintf (history_expiration,
+                   sizeof (history_expiration),
+                   "%s",
+                   GNUNET_TIME_relative2s (ks->history_expiration,
+                                           false));
+  GNUNET_snprintf (purse_timeout,
+                   sizeof (purse_timeout),
+                   "%s",
+                   GNUNET_TIME_relative2s (ks->purse_timeout,
+                                           false));
+  ks->offlinesign_proc
+    = GNUNET_OS_start_process (
+        GNUNET_OS_INHERIT_STD_ALL,
+        NULL, NULL, NULL,
+        "taler-exchange-offline",
+        "taler-exchange-offline",
+        "-c", ks->config_filename,
+        "-L", "INFO",
+        "global-fee",
+        "now",
+        ks->history_fee_s,
+        ks->account_fee_s,
+        ks->purse_fee_s,
+        purse_timeout,
+        history_expiration,
+        num_purses,
+        "upload",
+        NULL);
+  if (NULL == ks->offlinesign_proc)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "offlinesign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+offlinesign_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
+{
+  struct OfflineSignState *ks = cls;
+
+  (void) cmd;
+  if (NULL != ks->offlinesign_proc)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (ks->offlinesign_proc,
+                                          SIGKILL));
+    GNUNET_OS_process_wait (ks->offlinesign_proc);
+    GNUNET_OS_process_destroy (ks->offlinesign_proc);
+    ks->offlinesign_proc = NULL;
+  }
+  GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "offlinesign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+offlinesign_traits (void *cls,
+                    const void **ret,
+                    const char *trait,
+                    unsigned int index)
+{
+  struct OfflineSignState *ks = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_process (&ks->offlinesign_proc),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_global_fees (
+  const char *label,
+  const char *config_filename,
+  const char *history_fee,
+  const char *account_fee,
+  const char *purse_fee,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  unsigned int num_purses)
+{
+  struct OfflineSignState *ks;
+
+  ks = GNUNET_new (struct OfflineSignState);
+  ks->config_filename = config_filename;
+  ks->history_fee_s = history_fee;
+  ks->account_fee_s = account_fee;
+  ks->purse_fee_s = purse_fee;
+  ks->purse_timeout = purse_timeout;
+  ks->history_expiration = history_expiration;
+  ks->num_purses = num_purses;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ks,
+      .label = label,
+      .run = &offlinesign_run,
+      .cleanup = &offlinesign_cleanup,
+      .traits = &offlinesign_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_global_fees.c */
diff --git a/src/testing/testing_api_cmd_offline_sign_keys.c 
b/src/testing/testing_api_cmd_offline_sign_keys.c
new file mode 100644
index 0000000..2c99219
--- /dev/null
+++ b/src/testing/testing_api_cmd_offline_sign_keys.c
@@ -0,0 +1,165 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 testing/testing_api_cmd_offline_sign_keys.c
+ * @brief run the taler-exchange-offline command to download, sign and upload 
keys
+ * @author Marcello Stanisci
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "offlinesign" CMD.
+ */
+struct OfflineSignState
+{
+
+  /**
+   * Process for the "offlinesign" command.
+   */
+  struct GNUNET_OS_Process *offlinesign_proc;
+
+  /**
+   * Configuration file used by the command.
+   */
+  const char *config_filename;
+
+};
+
+
+/**
+ * Run the command; calls the `taler-exchange-offline' program.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+offlinesign_run (void *cls,
+                 const struct TALER_TESTING_Command *cmd,
+                 struct TALER_TESTING_Interpreter *is)
+{
+  struct OfflineSignState *ks = cls;
+
+  ks->offlinesign_proc
+    = GNUNET_OS_start_process (
+        GNUNET_OS_INHERIT_STD_ALL,
+        NULL, NULL, NULL,
+        "taler-exchange-offline",
+        "taler-exchange-offline",
+        "-c", ks->config_filename,
+        "-L", "INFO",
+        "download",
+        "sign",
+        "upload",
+        NULL);
+  if (NULL == ks->offlinesign_proc)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+/**
+ * Free the state of a "offlinesign" CMD, and possibly kills its
+ * process if it did not terminate correctly.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+offlinesign_cleanup (void *cls,
+                     const struct TALER_TESTING_Command *cmd)
+{
+  struct OfflineSignState *ks = cls;
+
+  (void) cmd;
+  if (NULL != ks->offlinesign_proc)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (ks->offlinesign_proc,
+                                          SIGKILL));
+    GNUNET_OS_process_wait (ks->offlinesign_proc);
+    GNUNET_OS_process_destroy (ks->offlinesign_proc);
+    ks->offlinesign_proc = NULL;
+  }
+  GNUNET_free (ks);
+}
+
+
+/**
+ * Offer "offlinesign" CMD internal data to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+offlinesign_traits (void *cls,
+                    const void **ret,
+                    const char *trait,
+                    unsigned int index)
+{
+  struct OfflineSignState *ks = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_process (&ks->offlinesign_proc),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_exec_offline_sign_keys (const char *label,
+                                          const char *config_filename)
+{
+  struct OfflineSignState *ks;
+
+  ks = GNUNET_new (struct OfflineSignState);
+  ks->config_filename = config_filename;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ks,
+      .label = label,
+      .run = &offlinesign_run,
+      .cleanup = &offlinesign_cleanup,
+      .traits = &offlinesign_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_exec_offline_sign_keys.c */
diff --git a/src/testing/testing_api_cmd_reserve_close.c 
b/src/testing/testing_api_cmd_reserve_close.c
new file mode 100644
index 0000000..8e272f5
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_close.c
@@ -0,0 +1,260 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_close.c
+ * @brief Implement the /reserve/$RID/close test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "close" CMD.
+ */
+struct CloseState
+{
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Handle to the "reserve close" operation.
+   */
+  struct TALER_EXCHANGE_ReservesCloseHandle *rsh;
+
+  /**
+   * payto://-URI where to wire the funds.
+   */
+  const char *target_account;
+
+  /**
+   * Private key of the reserve being analyzed.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Public key of the reserve being analyzed.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Set to the KYC requirement payto hash *if* the exchange replied with a
+   * request for KYC.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Set to the KYC requirement row *if* the exchange replied with
+   * a request for KYC.
+   */
+  uint64_t requirement_row;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_close_cb (void *cls,
+                  const struct TALER_EXCHANGE_ReserveCloseResult *rs)
+{
+  struct CloseState *ss = cls;
+  struct TALER_TESTING_Interpreter *is = ss->is;
+
+  ss->rsh = NULL;
+  if (ss->expected_response_code != rs->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected HTTP response code: %d in %s:%u\n",
+                rs->hr.http_status,
+                __FILE__,
+                __LINE__);
+    json_dumpf (rs->hr.reply,
+                stderr,
+                JSON_INDENT (2));
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  switch (rs->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* nothing to check */
+    ss->requirement_row
+      = rs->details.unavailable_for_legal_reasons.requirement_row;
+    ss->h_payto
+      = rs->details.unavailable_for_legal_reasons.h_payto;
+    break;
+  default:
+    break;
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+close_run (void *cls,
+           const struct TALER_TESTING_Command *cmd,
+           struct TALER_TESTING_Interpreter *is)
+{
+  struct CloseState *ss = cls;
+  const struct TALER_TESTING_Command *create_reserve;
+
+  ss->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ss->reserve_reference);
+
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &ss->reserve_priv))
+  {
+    GNUNET_break (0);
+    TALER_LOG_ERROR ("Failed to find reserve_priv for close query\n");
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+                                      &ss->reserve_pub.eddsa_pub);
+  ss->rsh = TALER_EXCHANGE_reserves_close (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    ss->reserve_priv,
+    ss->target_account,
+    &reserve_close_cb,
+    ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve close" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+close_cleanup (void *cls,
+               const struct TALER_TESTING_Command *cmd)
+{
+  struct CloseState *ss = cls;
+
+  if (NULL != ss->rsh)
+  {
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
+    TALER_EXCHANGE_reserves_close_cancel (ss->rsh);
+    ss->rsh = NULL;
+  }
+  GNUNET_free (ss);
+}
+
+
+/**
+ * Offer internal data to a "close" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+close_traits (void *cls,
+              const void **ret,
+              const char *trait,
+              unsigned int index)
+{
+  struct CloseState *cs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_legi_requirement_row (
+      &cs->requirement_row),
+    TALER_TESTING_make_trait_h_payto (
+      &cs->h_payto),
+    TALER_TESTING_trait_end ()
+  };
+
+  if (cs->expected_response_code != MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS)
+    return GNUNET_NO;
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_close (const char *label,
+                                 const char *reserve_reference,
+                                 const char *target_account,
+                                 unsigned int expected_response_code)
+{
+  struct CloseState *ss;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct CloseState);
+  ss->reserve_reference = reserve_reference;
+  ss->target_account = target_account;
+  ss->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &close_run,
+      .cleanup = &close_cleanup,
+      .traits = &close_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_reserve_get.c 
b/src/testing/testing_api_cmd_reserve_get.c
new file mode 100644
index 0000000..9a938cf
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_get.c
@@ -0,0 +1,390 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_get.c
+ * @brief Implement the GET /reserve/$RID test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "poll" CMD.
+ */
+struct PollState
+{
+
+  /**
+   * How long do we give the exchange to respond?
+   */
+  struct GNUNET_TIME_Relative timeout;
+
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *poll_reference;
+
+  /**
+   * Timeout to wait for at most.
+   */
+  struct GNUNET_SCHEDULER_Task *tt;
+
+  /**
+   * The interpreter we are using.
+   */
+  struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * State for a "status" CMD.
+ */
+struct StatusState
+{
+
+  /**
+   * How long do we give the exchange to respond?
+   */
+  struct GNUNET_TIME_Relative timeout;
+
+  /**
+   * Poller waiting for us.
+   */
+  struct PollState *ps;
+
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Handle to the "reserve status" operation.
+   */
+  struct TALER_EXCHANGE_ReservesGetHandle *rsh;
+
+  /**
+   * Expected reserve balance.
+   */
+  const char *expected_balance;
+
+  /**
+   * Public key of the reserve being analyzed.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pubp;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_status_cb (void *cls,
+                   const struct TALER_EXCHANGE_ReserveSummary *rs)
+{
+  struct StatusState *ss = cls;
+  struct TALER_TESTING_Interpreter *is = ss->is;
+  struct TALER_Amount eb;
+
+  ss->rsh = NULL;
+  if (ss->expected_response_code != rs->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected HTTP response code: %d in %s:%u\n",
+                rs->hr.http_status,
+                __FILE__,
+                __LINE__);
+    json_dumpf (rs->hr.reply,
+                stderr,
+                0);
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  if (MHD_HTTP_OK == ss->expected_response_code)
+  {
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (ss->expected_balance,
+                                           &eb));
+    if (0 != TALER_amount_cmp (&eb,
+                               &rs->details.ok.balance))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected amount %s in reserve, wanted %s\n",
+                  TALER_amount_to_string (&rs->details.ok.balance),
+                  ss->expected_balance);
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
+  }
+  if (NULL != ss->ps)
+  {
+    /* force continuation on long poller */
+    GNUNET_SCHEDULER_cancel (ss->ps->tt);
+    ss->ps->tt = NULL;
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  if (GNUNET_TIME_relative_is_zero (ss->timeout))
+    TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+status_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct StatusState *ss = cls;
+  const struct TALER_TESTING_Command *create_reserve;
+  const char *exchange_url;
+
+  ss->is = is;
+  exchange_url = TALER_TESTING_get_exchange_url (is);
+  if (NULL == exchange_url)
+  {
+    GNUNET_break (0);
+    return;
+  }
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ss->reserve_reference);
+  GNUNET_assert (NULL != create_reserve);
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_pub (create_reserve,
+                                           &ss->reserve_pubp))
+  {
+    GNUNET_break (0);
+    TALER_LOG_ERROR ("Failed to find reserve_pub for status query\n");
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  ss->rsh = TALER_EXCHANGE_reserves_get (
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
+    ss->reserve_pubp,
+    ss->timeout,
+    &reserve_status_cb,
+    ss);
+  if (! GNUNET_TIME_relative_is_zero (ss->timeout))
+  {
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+status_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct StatusState *ss = cls;
+
+  if (NULL != ss->rsh)
+  {
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
+    TALER_EXCHANGE_reserves_get_cancel (ss->rsh);
+    ss->rsh = NULL;
+  }
+  GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_status (const char *label,
+                          const char *reserve_reference,
+                          const char *expected_balance,
+                          unsigned int expected_response_code)
+{
+  struct StatusState *ss;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct StatusState);
+  ss->reserve_reference = reserve_reference;
+  ss->expected_balance = expected_balance;
+  ss->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &status_run,
+      .cleanup = &status_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll (const char *label,
+                                const char *reserve_reference,
+                                const char *expected_balance,
+                                struct GNUNET_TIME_Relative timeout,
+                                unsigned int expected_response_code)
+{
+  struct StatusState *ss;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct StatusState);
+  ss->reserve_reference = reserve_reference;
+  ss->expected_balance = expected_balance;
+  ss->expected_response_code = expected_response_code;
+  ss->timeout = timeout;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &status_run,
+      .cleanup = &status_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/**
+ * Long poller timed out. Fail the test.
+ *
+ * @param cls a `struct PollState`
+ */
+static void
+finish_timeout (void *cls)
+{
+  struct PollState *ps = cls;
+
+  ps->tt = NULL;
+  GNUNET_break (0);
+  TALER_TESTING_interpreter_fail (ps->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+finish_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct PollState *ps = cls;
+  const struct TALER_TESTING_Command *poll_reserve;
+  struct StatusState *ss;
+
+  ps->is = is;
+  poll_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ps->poll_reference);
+  GNUNET_assert (NULL != poll_reserve);
+  GNUNET_assert (poll_reserve->run == &status_run);
+  ss = poll_reserve->cls;
+  if (NULL == ss->rsh)
+  {
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  GNUNET_assert (NULL == ss->ps);
+  ss->ps = ps;
+  ps->tt = GNUNET_SCHEDULER_add_delayed (ps->timeout,
+                                         &finish_timeout,
+                                         ps);
+}
+
+
+/**
+ * Cleanup the state from a "reserve finish" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+finish_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct PollState *ps = cls;
+
+  if (NULL != ps->tt)
+  {
+    GNUNET_SCHEDULER_cancel (ps->tt);
+    ps->tt = NULL;
+  }
+  GNUNET_free (ps);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_poll_finish (const char *label,
+                                       struct GNUNET_TIME_Relative timeout,
+                                       const char *poll_reference)
+{
+  struct PollState *ps;
+
+  GNUNET_assert (NULL != poll_reference);
+  ps = GNUNET_new (struct PollState);
+  ps->timeout = timeout;
+  ps->poll_reference = poll_reference;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ps,
+      .label = label,
+      .run = &finish_run,
+      .cleanup = &finish_cleanup
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_reserve_history.c 
b/src/testing/testing_api_cmd_reserve_history.c
new file mode 100644
index 0000000..ff0a8a5
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_history.c
@@ -0,0 +1,455 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_history.c
+ * @brief Implement the /reserve/history test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "history" CMD.
+ */
+struct HistoryState
+{
+
+  /**
+   * Public key of the reserve being analyzed.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Handle to the "reserve history" operation.
+   */
+  struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
+
+  /**
+   * Expected reserve balance.
+   */
+  const char *expected_balance;
+
+  /**
+   * Private key of the reserve being analyzed.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Reserve history entry that corresponds to this operation.
+   * Will be of type #TALER_EXCHANGE_RTT_HISTORY.
+   */
+  struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+};
+
+
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+  /**
+   * Reserve public key we are looking at.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Length of the @e history array.
+   */
+  unsigned int history_length;
+
+  /**
+   * Array of history items to match.
+   */
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+  /**
+   * Array of @e history_length of matched entries.
+   */
+  bool *found;
+
+  /**
+   * Set to true if an entry could not be found.
+   */
+  bool failure;
+};
+
+
+/**
+ * Check if @a cmd changed the reserve, if so, find the
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
+ *
+ * @param cls our `struct AnalysisContext *`
+ * @param cmd command to analyze for impact on history
+ */
+static void
+analyze_command (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
+{
+  struct AnalysisContext *ac = cls;
+  const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
+  unsigned int history_length = ac->history_length;
+  bool *found = ac->found;
+
+  if (TALER_TESTING_cmd_is_batch (cmd))
+  {
+    struct TALER_TESTING_Command *cur;
+    struct TALER_TESTING_Command *bcmd;
+
+    cur = TALER_TESTING_cmd_batch_get_current (cmd);
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_batch_cmds (cmd,
+                                            &bcmd))
+    {
+      GNUNET_break (0);
+      ac->failure = true;
+      return;
+    }
+    for (unsigned int i = 0; NULL != bcmd[i].label; i++)
+    {
+      struct TALER_TESTING_Command *step = &bcmd[i];
+
+      analyze_command (ac,
+                       step);
+      if (ac->failure)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Entry for batch step `%s' missing in history\n",
+                    step->label);
+        return;
+      }
+      if (step == cur)
+        break; /* if *we* are in a batch, make sure not to analyze commands 
past 'now' */
+    }
+    return;
+  }
+
+  {
+    const struct TALER_ReservePublicKeyP *rp;
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_reserve_pub (cmd,
+                                             &rp))
+      return; /* command does nothing for reserves */
+    if (0 !=
+        GNUNET_memcmp (rp,
+                       reserve_pub))
+      return; /* command affects some _other_ reserve */
+    for (unsigned int j = 0; true; j++)
+    {
+      const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
+      bool matched = false;
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_reserve_history (cmd,
+                                                   j,
+                                                   &he))
+      {
+        /* NOTE: only for debugging... */
+        if (0 == j)
+          GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                      "Command `%s' has the reserve_pub, but lacks reserve 
history trait\n",
+                      cmd->label);
+        return; /* command does nothing for reserves */
+      }
+      for (unsigned int i = 0; i<history_length; i++)
+      {
+        if (found[i])
+          continue; /* already found, skip */
+        if (0 ==
+            TALER_TESTING_history_entry_cmp (he,
+                                             &history[i]))
+        {
+          found[i] = true;
+          matched = true;
+          break;
+        }
+      }
+      if (! matched)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Command `%s' reserve history entry #%u not found\n",
+                    cmd->label,
+                    j);
+        ac->failure = true;
+        return;
+      }
+    }
+  }
+}
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_history_cb (void *cls,
+                    const struct TALER_EXCHANGE_ReserveHistory *rs)
+{
+  struct HistoryState *ss = cls;
+  struct TALER_TESTING_Interpreter *is = ss->is;
+  struct TALER_Amount eb;
+
+  ss->rsh = NULL;
+  if (MHD_HTTP_OK == rs->hr.http_status)
+  {
+    struct TALER_EXCHANGE_Keys *keys;
+    const struct TALER_EXCHANGE_GlobalFee *gf;
+
+    ss->reserve_history.type = TALER_EXCHANGE_RTT_HISTORY;
+    keys = TALER_TESTING_get_keys (is);
+    GNUNET_assert (NULL != keys);
+    gf = TALER_EXCHANGE_get_global_fee (keys,
+                                        rs->ts);
+    GNUNET_assert (NULL != gf);
+    ss->reserve_history.amount = gf->fees.history;
+    ss->reserve_history.details.history_details.request_timestamp = rs->ts;
+    ss->reserve_history.details.history_details.reserve_sig = *rs->reserve_sig;
+  }
+  if (ss->expected_response_code != rs->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected HTTP response code: %d in %s:%u\n",
+                rs->hr.http_status,
+                __FILE__,
+                __LINE__);
+    json_dumpf (rs->hr.reply,
+                stderr,
+                0);
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  if (MHD_HTTP_OK != rs->hr.http_status)
+  {
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (ss->expected_balance,
+                                         &eb));
+
+  if (0 != TALER_amount_cmp (&eb,
+                             &rs->details.ok.balance))
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected amount in reserve: %s\n",
+                TALER_amount_to_string (&rs->details.ok.balance));
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected balance of: %s\n",
+                TALER_amount_to_string (&eb));
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  {
+    bool found[rs->details.ok.history_len];
+    struct AnalysisContext ac = {
+      .reserve_pub = &ss->reserve_pub,
+      .history = rs->details.ok.history,
+      .history_length = rs->details.ok.history_len,
+      .found = found
+    };
+
+    memset (found,
+            0,
+            sizeof (found));
+    TALER_TESTING_iterate (is,
+                           true,
+                           &analyze_command,
+                           &ac);
+    if (ac.failure)
+    {
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
+    for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
+    {
+      if (found[i])
+        continue;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "History entry at index %u of type %d not justified by 
command history\n",
+                  i,
+                  rs->details.ok.history[i].type);
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+history_run (void *cls,
+             const struct TALER_TESTING_Command *cmd,
+             struct TALER_TESTING_Interpreter *is)
+{
+  struct HistoryState *ss = cls;
+  const struct TALER_TESTING_Command *create_reserve;
+
+  ss->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ss->reserve_reference);
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &ss->reserve_priv))
+  {
+    GNUNET_break (0);
+    TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+                                      &ss->reserve_pub.eddsa_pub);
+  ss->rsh = TALER_EXCHANGE_reserves_history (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    TALER_TESTING_get_keys (is),
+    ss->reserve_priv,
+    &reserve_history_cb,
+    ss);
+}
+
+
+/**
+ * Offer internal data from a "history" CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+history_traits (void *cls,
+                const void **ret,
+                const char *trait,
+                unsigned int index)
+{
+  struct HistoryState *hs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    /* history entry MUST be first due to response code logic below! */
+    TALER_TESTING_make_trait_reserve_history (0,
+                                              &hs->reserve_history),
+    TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait ((hs->expected_response_code == MHD_HTTP_OK)
+                                  ? &traits[0]   /* we have reserve history */
+                                  : &traits[1],  /* skip reserve history */
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Cleanup the state from a "reserve history" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+history_cleanup (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
+{
+  struct HistoryState *ss = cls;
+
+  if (NULL != ss->rsh)
+  {
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
+    TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
+    ss->rsh = NULL;
+  }
+  GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_history (const char *label,
+                                   const char *reserve_reference,
+                                   const char *expected_balance,
+                                   unsigned int expected_response_code)
+{
+  struct HistoryState *ss;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct HistoryState);
+  ss->reserve_reference = reserve_reference;
+  ss->expected_balance = expected_balance;
+  ss->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &history_run,
+      .cleanup = &history_cleanup,
+      .traits = &history_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_reserve_open.c 
b/src/testing/testing_api_cmd_reserve_open.c
new file mode 100644
index 0000000..189d06b
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_open.c
@@ -0,0 +1,349 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2022 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_reserve_open.c
+ * @brief Implement the /reserve/$RID/open test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * Information we track per coin used to pay for opening the
+ * reserve.
+ */
+struct CoinDetail
+{
+  /**
+   * Name of the command and index of the coin to use.
+   */
+  const char *name;
+
+  /**
+   * Amount to charge to this coin.
+   */
+  struct TALER_Amount amount;
+};
+
+
+/**
+ * State for a "open" CMD.
+ */
+struct OpenState
+{
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Requested expiration time.
+   */
+  struct GNUNET_TIME_Relative req_expiration_time;
+
+  /**
+   * Requested minimum number of purses.
+   */
+  uint32_t min_purses;
+
+  /**
+   * Amount to pay for the opening from the reserve balance.
+   */
+  struct TALER_Amount reserve_pay;
+
+  /**
+   * Handle to the "reserve open" operation.
+   */
+  struct TALER_EXCHANGE_ReservesOpenHandle *rsh;
+
+  /**
+   * Expected reserve balance.
+   */
+  const char *expected_balance;
+
+  /**
+   * Length of the @e cd array.
+   */
+  unsigned int cpl;
+
+  /**
+   * Coin details, array of length @e cpl.
+   */
+  struct CoinDetail *cd;
+
+  /**
+   * Private key of the reserve being analyzed.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Public key of the reserve being analyzed.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_open_cb (void *cls,
+                 const struct TALER_EXCHANGE_ReserveOpenResult *rs)
+{
+  struct OpenState *ss = cls;
+  struct TALER_TESTING_Interpreter *is = ss->is;
+
+  ss->rsh = NULL;
+  if (ss->expected_response_code != rs->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected HTTP response code: %d in %s:%u\n",
+                rs->hr.http_status,
+                __FILE__,
+                __LINE__);
+    json_dumpf (rs->hr.reply,
+                stderr,
+                JSON_INDENT (2));
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  if (MHD_HTTP_OK != rs->hr.http_status)
+  {
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+open_run (void *cls,
+          const struct TALER_TESTING_Command *cmd,
+          struct TALER_TESTING_Interpreter *is)
+{
+  struct OpenState *ss = cls;
+  const struct TALER_TESTING_Command *create_reserve;
+  struct TALER_EXCHANGE_PurseDeposit cp[GNUNET_NZL (ss->cpl)];
+
+  ss->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ss->reserve_reference);
+
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &ss->reserve_priv))
+  {
+    GNUNET_break (0);
+    TALER_LOG_ERROR ("Failed to find reserve_priv for open query\n");
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+                                      &ss->reserve_pub.eddsa_pub);
+  for (unsigned int i = 0; i<ss->cpl; i++)
+  {
+    struct TALER_EXCHANGE_PurseDeposit *cpi = &cp[i];
+    const struct TALER_TESTING_Command *cmdi;
+    const struct TALER_AgeCommitmentProof *age_commitment_proof;
+    const struct TALER_CoinSpendPrivateKeyP *coin_priv;
+    const struct TALER_DenominationSignature *denom_sig;
+    const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+    char *cref;
+    unsigned int cidx;
+
+    if (GNUNET_OK !=
+        TALER_TESTING_parse_coin_reference (ss->cd[i].name,
+                                            &cref,
+                                            &cidx))
+    {
+      GNUNET_break (0);
+      TALER_LOG_ERROR ("Failed to parse coin reference `%s'\n",
+                       ss->cd[i].name);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    cmdi = TALER_TESTING_interpreter_lookup_command (is,
+                                                     cref);
+    GNUNET_free (cref);
+    if (NULL == cmdi)
+    {
+      GNUNET_break (0);
+      TALER_LOG_ERROR ("Command `%s' not found\n",
+                       ss->cd[i].name);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    if ( (GNUNET_OK !=
+          TALER_TESTING_get_trait_age_commitment_proof (cmdi,
+                                                        cidx,
+                                                        &age_commitment_proof))
+         ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_coin_priv (cmdi,
+                                             cidx,
+                                             &coin_priv)) ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_denom_sig (cmdi,
+                                             cidx,
+                                             &denom_sig)) ||
+         (GNUNET_OK !=
+          TALER_TESTING_get_trait_denom_pub (cmdi,
+                                             cidx,
+                                             &denom_pub)) )
+    {
+      GNUNET_break (0);
+      TALER_LOG_ERROR ("Coin trait not found in `%s'\n",
+                       ss->cd[i].name);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    cpi->age_commitment_proof = age_commitment_proof;
+    cpi->coin_priv = *coin_priv;
+    cpi->denom_sig = *denom_sig;
+    cpi->amount = ss->cd[i].amount;
+    cpi->h_denom_pub = denom_pub->h_key;
+  }
+  ss->rsh = TALER_EXCHANGE_reserves_open (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    TALER_TESTING_get_keys (is),
+    ss->reserve_priv,
+    &ss->reserve_pay,
+    ss->cpl,
+    cp,
+    GNUNET_TIME_relative_to_timestamp (ss->req_expiration_time),
+    ss->min_purses,
+    &reserve_open_cb,
+    ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve open" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+open_cleanup (void *cls,
+              const struct TALER_TESTING_Command *cmd)
+{
+  struct OpenState *ss = cls;
+
+  if (NULL != ss->rsh)
+  {
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
+    TALER_EXCHANGE_reserves_open_cancel (ss->rsh);
+    ss->rsh = NULL;
+  }
+  GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_open (const char *label,
+                                const char *reserve_reference,
+                                const char *reserve_pay,
+                                struct GNUNET_TIME_Relative expiration_time,
+                                uint32_t min_purses,
+                                unsigned int expected_response_code,
+                                ...)
+{
+  struct OpenState *ss;
+  va_list ap;
+  const char *name;
+  unsigned int i;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct OpenState);
+  ss->reserve_reference = reserve_reference;
+  ss->req_expiration_time = expiration_time;
+  ss->min_purses = min_purses;
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (reserve_pay,
+                                         &ss->reserve_pay));
+  ss->expected_response_code = expected_response_code;
+  va_start (ap,
+            expected_response_code);
+  while (NULL != (name = va_arg (ap, const char *)))
+    ss->cpl++;
+  va_end (ap);
+  GNUNET_assert (0 == (ss->cpl % 2));
+  ss->cpl /= 2; /* name and amount per coin */
+  ss->cd = GNUNET_new_array (ss->cpl,
+                             struct CoinDetail);
+  i = 0;
+  va_start (ap,
+            expected_response_code);
+  while (NULL != (name = va_arg (ap, const char *)))
+  {
+    struct CoinDetail *cd = &ss->cd[i];
+    cd->name = name;
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (va_arg (ap,
+                                                   const char *),
+                                           &cd->amount));
+    i++;
+  }
+  va_end (ap);
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &open_run,
+      .cleanup = &open_cleanup
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_reserve_status.c 
b/src/testing/testing_api_cmd_reserve_status.c
new file mode 100644
index 0000000..2438b2c
--- /dev/null
+++ b/src/testing/testing_api_cmd_reserve_status.c
@@ -0,0 +1,397 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 testing/testing_api_cmd_reserve_status.c
+ * @brief Implement the /reserve/$RID/status test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "status" CMD.
+ */
+struct StatusState
+{
+  /**
+   * Label to the command which created the reserve to check,
+   * needed to resort the reserve key.
+   */
+  const char *reserve_reference;
+
+  /**
+   * Handle to the "reserve status" operation.
+   */
+  struct TALER_EXCHANGE_ReservesStatusHandle *rsh;
+
+  /**
+   * Expected reserve balance.
+   */
+  const char *expected_balance;
+
+  /**
+   * Private key of the reserve being analyzed.
+   */
+  const struct TALER_ReservePrivateKeyP *reserve_priv;
+
+  /**
+   * Public key of the reserve being analyzed.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+};
+
+/**
+ * Closure for analysis_cb().
+ */
+struct AnalysisContext
+{
+  /**
+   * Reserve public key we are looking at.
+   */
+  const struct TALER_ReservePublicKeyP *reserve_pub;
+
+  /**
+   * Length of the @e history array.
+   */
+  unsigned int history_length;
+
+  /**
+   * Array of history items to match.
+   */
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
+
+  /**
+   * Array of @e history_length of matched entries.
+   */
+  bool *found;
+
+  /**
+   * Set to true if an entry could not be found.
+   */
+  bool failure;
+};
+
+
+/**
+ * Check if @a cmd changed the reserve, if so, find the
+ * entry in our history and set the respective index in found
+ * to true. If the entry is not found, set failure.
+ *
+ * @param cls our `struct AnalysisContext *`
+ * @param cmd command to analyze for impact on history
+ */
+static void
+analyze_command (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
+{
+  struct AnalysisContext *ac = cls;
+  const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
+  const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
+  unsigned int history_length = ac->history_length;
+  bool *found = ac->found;
+
+  if (TALER_TESTING_cmd_is_batch (cmd))
+  {
+    struct TALER_TESTING_Command *cur;
+    struct TALER_TESTING_Command *bcmd;
+
+    cur = TALER_TESTING_cmd_batch_get_current (cmd);
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_batch_cmds (cmd,
+                                            &bcmd))
+    {
+      GNUNET_break (0);
+      ac->failure = true;
+      return;
+    }
+    for (unsigned int i = 0; NULL != bcmd[i].label; i++)
+    {
+      struct TALER_TESTING_Command *step = &bcmd[i];
+
+      if (step == cur)
+        break; /* if *we* are in a batch, make sure not to analyze commands 
past 'now' */
+      analyze_command (ac,
+                       step);
+      if (ac->failure)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Entry for batch step `%s' missing in history\n",
+                    step->label);
+        return;
+      }
+    }
+    return;
+  }
+
+  {
+    const struct TALER_ReservePublicKeyP *rp;
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_reserve_pub (cmd,
+                                             &rp))
+      return; /* command does nothing for reserves */
+    if (0 !=
+        GNUNET_memcmp (rp,
+                       reserve_pub))
+      return; /* command affects some _other_ reserve */
+    for (unsigned int j = 0; true; j++)
+    {
+      const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
+      bool matched = false;
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_reserve_history (cmd,
+                                                   j,
+                                                   &he))
+      {
+        /* NOTE: only for debugging... */
+        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                    "Command `%s' has the reserve_pub trait, but does not 
reserve history trait\n",
+                    cmd->label);
+        return; /* command does nothing for reserves */
+      }
+      for (unsigned int i = 0; i<history_length; i++)
+      {
+        if (found[i])
+          continue; /* already found, skip */
+        if (0 ==
+            TALER_TESTING_history_entry_cmp (he,
+                                             &history[i]))
+        {
+          found[i] = true;
+          matched = true;
+          break;
+        }
+      }
+      if (! matched)
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Command `%s' reserve history entry #%u not found\n",
+                    cmd->label,
+                    j);
+        ac->failure = true;
+        return;
+      }
+    }
+  }
+}
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param rs HTTP response details
+ */
+static void
+reserve_status_cb (void *cls,
+                   const struct TALER_EXCHANGE_ReserveStatus *rs)
+{
+  struct StatusState *ss = cls;
+  struct TALER_TESTING_Interpreter *is = ss->is;
+  struct TALER_Amount eb;
+
+  ss->rsh = NULL;
+  if (ss->expected_response_code != rs->hr.http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected HTTP response code: %d in %s:%u\n",
+                rs->hr.http_status,
+                __FILE__,
+                __LINE__);
+    json_dumpf (rs->hr.reply,
+                stderr,
+                JSON_INDENT (2));
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  if (MHD_HTTP_OK != rs->hr.http_status)
+  {
+    TALER_TESTING_interpreter_next (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_string_to_amount (ss->expected_balance,
+                                         &eb));
+
+  if (0 != TALER_amount_cmp (&eb,
+                             &rs->details.ok.balance))
+  {
+    GNUNET_break (0);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected amount in reserve: %s\n",
+                TALER_amount_to_string (&rs->details.ok.balance));
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected balance of: %s\n",
+                TALER_amount_to_string (&eb));
+    TALER_TESTING_interpreter_fail (ss->is);
+    return;
+  }
+  {
+    bool found[rs->details.ok.history_len];
+    struct AnalysisContext ac = {
+      .reserve_pub = &ss->reserve_pub,
+      .history = rs->details.ok.history,
+      .history_length = rs->details.ok.history_len,
+      .found = found
+    };
+
+    memset (found,
+            0,
+            sizeof (found));
+    TALER_TESTING_iterate (is,
+                           true,
+                           &analyze_command,
+                           &ac);
+    if (ac.failure)
+    {
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
+    for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
+    {
+      if (found[i])
+        continue;
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "History entry at index %u of type %d not justified by 
command status\n",
+                  i,
+                  rs->details.ok.history[i].type);
+      json_dumpf (rs->hr.reply,
+                  stderr,
+                  JSON_INDENT (2));
+      TALER_TESTING_interpreter_fail (ss->is);
+      return;
+    }
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+status_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct StatusState *ss = cls;
+  const struct TALER_TESTING_Command *create_reserve;
+
+  ss->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (is,
+                                                ss->reserve_reference);
+
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &ss->reserve_priv))
+  {
+    GNUNET_break (0);
+    TALER_LOG_ERROR ("Failed to find reserve_priv for status query\n");
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
+                                      &ss->reserve_pub.eddsa_pub);
+  ss->rsh = TALER_EXCHANGE_reserves_status (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    TALER_TESTING_get_keys (is),
+    ss->reserve_priv,
+    &reserve_status_cb,
+    ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+status_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct StatusState *ss = cls;
+
+  if (NULL != ss->rsh)
+  {
+    TALER_TESTING_command_incomplete (ss->is,
+                                      cmd->label);
+    TALER_EXCHANGE_reserves_status_cancel (ss->rsh);
+    ss->rsh = NULL;
+  }
+  GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_reserve_status (const char *label,
+                                  const char *reserve_reference,
+                                  const char *expected_balance,
+                                  unsigned int expected_response_code)
+{
+  struct StatusState *ss;
+
+  GNUNET_assert (NULL != reserve_reference);
+  ss = GNUNET_new (struct StatusState);
+  ss->reserve_reference = reserve_reference;
+  ss->expected_balance = expected_balance;
+  ss->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ss,
+      .label = label,
+      .run = &status_run,
+      .cleanup = &status_cleanup
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_revoke.c 
b/src/testing/testing_api_cmd_revoke.c
new file mode 100644
index 0000000..f734be1
--- /dev/null
+++ b/src/testing/testing_api_cmd_revoke.c
@@ -0,0 +1,207 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 testing/testing_api_cmd_revoke.c
+ * @brief Implement the revoke test command.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+  /**
+   * Expected HTTP status code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Command that offers a denomination to revoke.
+   */
+  const char *coin_reference;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * The revoke process handle.
+   */
+  struct GNUNET_OS_Process *revoke_proc;
+
+  /**
+   * Configuration file name.
+   */
+  const char *config_filename;
+
+  /**
+   * Encoding of the denomination (to revoke) public key hash.
+   */
+  char *dhks;
+
+};
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, must be a `struct RevokeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct RevokeState *rs = cls;
+
+  if (NULL != rs->revoke_proc)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (rs->revoke_proc,
+                                          SIGKILL));
+    GNUNET_OS_process_wait (rs->revoke_proc);
+    GNUNET_OS_process_destroy (rs->revoke_proc);
+    rs->revoke_proc = NULL;
+  }
+  GNUNET_free (rs->dhks);
+  GNUNET_free (rs);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+revoke_traits (void *cls,
+               const void **ret,
+               const char *trait,
+               unsigned int index)
+{
+  struct RevokeState *rs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    /* Needed by the handler which waits the proc'
+     * death and calls the next command */
+    TALER_TESTING_make_trait_process (&rs->revoke_proc),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Run the "revoke" command.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct RevokeState *rs = cls;
+  const struct TALER_TESTING_Command *coin_cmd;
+  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+
+  rs->is = is;
+  /* Get denom pub from trait */
+  coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                       rs->coin_reference);
+  if (NULL == coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_denom_pub (coin_cmd,
+                                                    0,
+                                                    &denom_pub));
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Trying to revoke denom '%s..'\n",
+              TALER_B2S (&denom_pub->h_key));
+
+  rs->dhks = GNUNET_STRINGS_data_to_string_alloc (
+    &denom_pub->h_key,
+    sizeof (struct GNUNET_HashCode));
+  rs->revoke_proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ALL,
+                                             NULL, NULL, NULL,
+                                             "taler-exchange-offline",
+                                             "taler-exchange-offline",
+                                             "-c", rs->config_filename,
+                                             "revoke-denomination", rs->dhks,
+                                             "upload",
+                                             NULL);
+
+  if (NULL == rs->revoke_proc)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Revoke is ongoing..\n");
+  TALER_TESTING_wait_for_sigchld (is);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke (const char *label,
+                          unsigned int expected_response_code,
+                          const char *coin_reference,
+                          const char *config_filename)
+{
+
+  struct RevokeState *rs;
+
+  rs = GNUNET_new (struct RevokeState);
+  rs->expected_response_code = expected_response_code;
+  rs->coin_reference = coin_reference;
+  rs->config_filename = config_filename;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = rs,
+      .label = label,
+      .run = &revoke_run,
+      .cleanup = &revoke_cleanup,
+      .traits = &revoke_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_revoke_denom_key.c 
b/src/testing/testing_api_cmd_revoke_denom_key.c
new file mode 100644
index 0000000..2663c53
--- /dev/null
+++ b/src/testing/testing_api_cmd_revoke_denom_key.c
@@ -0,0 +1,256 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 testing/testing_api_cmd_revoke_denom_key.c
+ * @brief Implement the revoke test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+  /**
+   * Expected HTTP status code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Command that offers a denomination to revoke.
+   */
+  const char *coin_reference;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Handle for the operation.
+   */
+  struct TALER_EXCHANGE_ManagementRevokeDenominationKeyHandle *kh;
+
+  /**
+   * Should we use a bogus signature?
+   */
+  bool bad_sig;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct RevokeState *`
+ * @param rdr response data
+ */
+static void
+success_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeDenominationResponse *rdr)
+{
+  struct RevokeState *rs = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &rdr->hr;
+
+  rs->kh = NULL;
+  if (rs->expected_response_code != hr->http_status)
+  {
+    TALER_TESTING_unexpected_status (rs->is,
+                                     hr->http_status,
+                                     rs->expected_response_code);
+    return;
+  }
+  TALER_TESTING_interpreter_next (rs->is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, must be a `struct RevokeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct RevokeState *rs = cls;
+
+  if (NULL != rs->kh)
+  {
+    TALER_EXCHANGE_management_revoke_denomination_key_cancel (rs->kh);
+    rs->kh = NULL;
+  }
+  GNUNET_free (rs);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+revoke_traits (void *cls,
+               const void **ret,
+               const char *trait,
+               unsigned int index)
+{
+  struct RevokeState *rs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_trait_end ()
+  };
+
+  (void) rs;
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Run the "revoke" command for a denomination key.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct RevokeState *rs = cls;
+  const struct TALER_TESTING_Command *coin_cmd;
+  const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+  struct TALER_MasterSignatureP master_sig;
+  const char *exchange_url;
+
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
+  rs->is = is;
+  /* Get denom pub from trait */
+  coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                       rs->coin_reference);
+
+  if (NULL == coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_denom_pub (coin_cmd,
+                                                    0,
+                                                    &denom_pub));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Trying to revoke denom '%s..'\n",
+              TALER_B2S (&denom_pub->h_key));
+  if (rs->bad_sig)
+  {
+    memset (&master_sig,
+            42,
+            sizeof (master_sig));
+  }
+  else
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
+    TALER_exchange_offline_denomination_revoke_sign (&denom_pub->h_key,
+                                                     master_priv,
+                                                     &master_sig);
+  }
+  rs->kh = TALER_EXCHANGE_management_revoke_denomination_key (
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
+    &denom_pub->h_key,
+    &master_sig,
+    &success_cb,
+    rs);
+  if (NULL == rs->kh)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_denom_key (
+  const char *label,
+  unsigned int expected_response_code,
+  bool bad_sig,
+  const char *denom_ref)
+{
+  struct RevokeState *rs;
+
+  rs = GNUNET_new (struct RevokeState);
+  rs->expected_response_code = expected_response_code;
+  rs->coin_reference = denom_ref;
+  rs->bad_sig = bad_sig;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = rs,
+      .label = label,
+      .run = &revoke_run,
+      .cleanup = &revoke_cleanup,
+      .traits = &revoke_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_revoke_sign_key.c 
b/src/testing/testing_api_cmd_revoke_sign_key.c
new file mode 100644
index 0000000..65b80b4
--- /dev/null
+++ b/src/testing/testing_api_cmd_revoke_sign_key.c
@@ -0,0 +1,256 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_cmd_revoke_sign_key.c
+ * @brief Implement the revoke test command.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_testing_lib.h"
+
+
+/**
+ * State for a "revoke" CMD.
+ */
+struct RevokeState
+{
+  /**
+   * Expected HTTP status code.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Command that offers a signination to revoke.
+   */
+  const char *coin_reference;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Handle for the operation.
+   */
+  struct TALER_EXCHANGE_ManagementRevokeSigningKeyHandle *kh;
+
+  /**
+   * Should we use a bogus signature?
+   */
+  bool bad_sig;
+
+};
+
+
+/**
+ * Function called with information about the post revocation operation result.
+ *
+ * @param cls closure with a `struct RevokeState *`
+ * @param rsr response data
+ */
+static void
+success_cb (
+  void *cls,
+  const struct TALER_EXCHANGE_ManagementRevokeSigningKeyResponse *rsr)
+{
+  struct RevokeState *rs = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &rsr->hr;
+
+  rs->kh = NULL;
+  if (rs->expected_response_code != hr->http_status)
+  {
+    TALER_TESTING_unexpected_status (rs->is,
+                                     hr->http_status,
+                                     rs->expected_response_code);
+    return;
+  }
+  TALER_TESTING_interpreter_next (rs->is);
+}
+
+
+/**
+ * Cleanup the state.
+ *
+ * @param cls closure, must be a `struct RevokeState`.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+revoke_cleanup (void *cls,
+                const struct TALER_TESTING_Command *cmd)
+{
+  struct RevokeState *rs = cls;
+
+  if (NULL != rs->kh)
+  {
+    TALER_EXCHANGE_management_revoke_signing_key_cancel (rs->kh);
+    rs->kh = NULL;
+  }
+  GNUNET_free (rs);
+}
+
+
+/**
+ * Offer internal data from a "revoke" CMD to other CMDs.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static int
+revoke_traits (void *cls,
+               const void **ret,
+               const char *trait,
+               unsigned int index)
+{
+  struct RevokeState *rs = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_trait_end ()
+  };
+
+  (void) rs;
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+/**
+ * Run the "revoke" command for a signing key.
+ *
+ * @param cls closure.
+ * @param cmd the command to execute.
+ * @param is the interpreter state.
+ */
+static void
+revoke_run (void *cls,
+            const struct TALER_TESTING_Command *cmd,
+            struct TALER_TESTING_Interpreter *is)
+{
+  struct RevokeState *rs = cls;
+  const struct TALER_TESTING_Command *coin_cmd;
+  const struct TALER_ExchangePublicKeyP *exchange_pub;
+  struct TALER_MasterSignatureP master_sig;
+  const char *exchange_url;
+
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_exchange_url (exchange_cmd,
+                                                         &exchange_url));
+  }
+  rs->is = is;
+  /* Get sign pub from trait */
+  coin_cmd = TALER_TESTING_interpreter_lookup_command (is,
+                                                       rs->coin_reference);
+
+  if (NULL == coin_cmd)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_TESTING_get_trait_exchange_pub (coin_cmd,
+                                                       0,
+                                                       &exchange_pub));
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Trying to revoke sign '%s..'\n",
+              TALER_B2S (exchange_pub));
+  if (rs->bad_sig)
+  {
+    memset (&master_sig,
+            42,
+            sizeof (master_sig));
+  }
+  else
+  {
+    const struct TALER_TESTING_Command *exchange_cmd;
+    const struct TALER_MasterPrivateKeyP *master_priv;
+
+    exchange_cmd = TALER_TESTING_interpreter_get_command (is,
+                                                          "exchange");
+    if (NULL == exchange_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_master_priv (exchange_cmd,
+                                                        &master_priv));
+    TALER_exchange_offline_signkey_revoke_sign (exchange_pub,
+                                                master_priv,
+                                                &master_sig);
+  }
+  rs->kh = TALER_EXCHANGE_management_revoke_signing_key (
+    TALER_TESTING_interpreter_get_context (is),
+    exchange_url,
+    exchange_pub,
+    &master_sig,
+    &success_cb,
+    rs);
+  if (NULL == rs->kh)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_revoke_sign_key (
+  const char *label,
+  unsigned int expected_response_code,
+  bool bad_sig,
+  const char *sign_ref)
+{
+  struct RevokeState *rs;
+
+  rs = GNUNET_new (struct RevokeState);
+  rs->expected_response_code = expected_response_code;
+  rs->coin_reference = sign_ref;
+  rs->bad_sig = bad_sig;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = rs,
+      .label = label,
+      .run = &revoke_run,
+      .cleanup = &revoke_cleanup,
+      .traits = &revoke_traits
+    };
+
+    return cmd;
+  }
+}
diff --git a/src/testing/testing_api_cmd_stat.c 
b/src/testing/testing_api_cmd_stat.c
new file mode 100644
index 0000000..8723aac
--- /dev/null
+++ b/src/testing/testing_api_cmd_stat.c
@@ -0,0 +1,168 @@
+/*
+  This file is part of TALER
+  (C) 2018 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 testing/testing_api_cmd_stat.c
+ * @brief command(s) to get performance statistics on other commands
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * Run a "stat" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
+ */
+static void
+stat_run (void *cls,
+          const struct TALER_TESTING_Command *cmd,
+          struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Add the time @a cmd took to the respective duration in @a timings.
+ *
+ * @param timings where to add up times
+ * @param cmd command to evaluate
+ */
+static void
+stat_cmd (struct TALER_TESTING_Timer *timings,
+          const struct TALER_TESTING_Command *cmd)
+{
+  struct GNUNET_TIME_Relative duration;
+  struct GNUNET_TIME_Relative lat;
+
+  if (GNUNET_TIME_absolute_cmp (cmd->start_time,
+                                >,
+                                cmd->finish_time))
+  {
+    /* This is a problem, except of course for
+       this command itself, as we clearly did not yet
+       finish... */
+    if (cmd->run != &stat_run)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Bad timings for `%s'\n",
+                  cmd->label);
+      GNUNET_break (0);
+    }
+    return;
+  }
+  duration = GNUNET_TIME_absolute_get_difference (cmd->start_time,
+                                                  cmd->finish_time);
+  lat = GNUNET_TIME_absolute_get_difference (cmd->last_req_time,
+                                             cmd->finish_time);
+  for (unsigned int i = 0;
+       NULL != timings[i].prefix;
+       i++)
+  {
+    if (0 == strncmp (timings[i].prefix,
+                      cmd->label,
+                      strlen (timings[i].prefix)))
+    {
+      timings[i].total_duration
+        = GNUNET_TIME_relative_add (duration,
+                                    timings[i].total_duration);
+      timings[i].success_latency
+        = GNUNET_TIME_relative_add (lat,
+                                    timings[i].success_latency);
+      timings[i].num_commands++;
+      timings[i].num_retries += cmd->num_tries;
+      break;
+    }
+  }
+}
+
+
+/**
+ * Obtain statistics for @a timings of @a cmd
+ *
+ * @param[in,out] cls what timings to get
+ * @param cmd command to process
+ */
+static void
+do_stat (void *cls,
+         const struct TALER_TESTING_Command *cmd)
+{
+  struct TALER_TESTING_Timer *timings = cls;
+
+  if (TALER_TESTING_cmd_is_batch (cmd))
+  {
+    struct TALER_TESTING_Command *bcmd;
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_batch_cmds (cmd,
+                                            &bcmd))
+    {
+      GNUNET_break (0);
+      return;
+    }
+    for (unsigned int j = 0;
+         NULL != bcmd[j].label;
+         j++)
+      do_stat (timings,
+               &bcmd[j]);
+    return;
+  }
+  stat_cmd (timings,
+            cmd);
+}
+
+
+/**
+ * Run a "stat" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
+ */
+static void
+stat_run (void *cls,
+          const struct TALER_TESTING_Command *cmd,
+          struct TALER_TESTING_Interpreter *is)
+{
+  struct TALER_TESTING_Timer *timings = cls;
+
+  TALER_TESTING_iterate (is,
+                         true,
+                         &do_stat,
+                         timings);
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_stat (struct TALER_TESTING_Timer *timers)
+{
+  struct TALER_TESTING_Command cmd = {
+    .label = "stat",
+    .run = &stat_run,
+    .cls = (void *) timers
+  };
+
+  return cmd;
+}
+
+
+/* end of testing_api_cmd_stat.c  */
diff --git a/src/testing/testing_api_cmd_transfer_get.c 
b/src/testing/testing_api_cmd_transfer_get.c
new file mode 100644
index 0000000..405c8b7
--- /dev/null
+++ b/src/testing/testing_api_cmd_transfer_get.c
@@ -0,0 +1,406 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2020 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 testing/testing_api_cmd_transfer_get.c
+ * @brief Implement the testing CMDs for the /transfer GET operation.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+/**
+ * State for a "track transfer" CMD.
+ */
+struct TrackTransferState
+{
+
+  /**
+   * Expected amount for the WTID being tracked.
+   */
+  const char *expected_total_amount;
+
+  /**
+   * Expected fee for this WTID.
+   */
+  const char *expected_wire_fee;
+
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
+  /**
+   * Reference to any operation that can provide a WTID.
+   * Will be the WTID to track.
+   */
+  const char *wtid_reference;
+
+  /**
+   * Reference to any operation that can provide wire details.
+   * Those wire details will then be matched against the credit
+   * bank account of the tracked WTID.  This way we can test that
+   * a wire transfer paid back one particular bank account.
+   */
+  const char *wire_details_reference;
+
+  /**
+   * Reference to any operation that can provide an amount.
+   * This way we can check that the transferred amount matches
+   * our expectations.
+   */
+  const char *total_amount_reference;
+
+  /**
+   * Handle to a pending "track transfer" operation.
+   */
+  struct TALER_EXCHANGE_TransfersGetHandle *tth;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int expected_response_code;
+
+};
+
+
+/**
+ * Cleanup the state for a "track transfer" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+track_transfer_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+
+  struct TrackTransferState *tts = cls;
+
+  if (NULL != tts->tth)
+  {
+    TALER_TESTING_command_incomplete (tts->is,
+                                      cmd->label);
+    TALER_EXCHANGE_transfers_get_cancel (tts->tth);
+    tts->tth = NULL;
+  }
+  GNUNET_free (tts);
+}
+
+
+/**
+ * Check whether the HTTP response code from a "track transfer"
+ * operation is acceptable, and all other values like total amount,
+ * wire fees and hashed wire details as well.
+ *
+ * @param cls closure.
+ * @param tgr response details
+ */
+static void
+track_transfer_cb (void *cls,
+                   const struct TALER_EXCHANGE_TransfersGetResponse *tgr)
+{
+  struct TrackTransferState *tts = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr;
+  struct TALER_TESTING_Interpreter *is = tts->is;
+  struct TALER_Amount expected_amount;
+
+  tts->tth = NULL;
+  if (tts->expected_response_code != hr->http_status)
+  {
+    TALER_TESTING_unexpected_status (is,
+                                     hr->http_status,
+                                     tts->expected_response_code);
+    return;
+  }
+
+  switch (hr->http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      const struct TALER_EXCHANGE_TransferData *ta
+        = &tgr->details.ok.td;
+
+      if (NULL == tts->expected_total_amount)
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+      if (NULL == tts->expected_wire_fee)
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      if (GNUNET_OK !=
+          TALER_string_to_amount (tts->expected_total_amount,
+                                  &expected_amount))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+      if (0 != TALER_amount_cmp (&ta->total_amount,
+                                 &expected_amount))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Total amount mismatch to command %s - "
+                    "%s vs %s\n",
+                    tts->cmd->label,
+                    TALER_amount_to_string (&ta->total_amount),
+                    TALER_amount_to_string (&expected_amount));
+        json_dumpf (hr->reply,
+                    stderr,
+                    0);
+        fprintf (stderr, "\n");
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      if (GNUNET_OK !=
+          TALER_string_to_amount (tts->expected_wire_fee,
+                                  &expected_amount))
+      {
+        GNUNET_break (0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      if (0 != TALER_amount_cmp (&ta->wire_fee,
+                                 &expected_amount))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Wire fee mismatch to command %s\n",
+                    tts->cmd->label);
+        json_dumpf (hr->reply,
+                    stderr,
+                    0);
+        TALER_TESTING_interpreter_fail (is);
+        return;
+      }
+
+      /**
+       * Optionally checking: (1) wire-details for this transfer
+       * match the ones from a referenced "deposit" operation -
+       * or any operation that could provide wire-details.  (2)
+       * Total amount for this transfer matches the one from any
+       * referenced command that could provide one.
+       */
+      if (NULL != tts->wire_details_reference)
+      {
+        const struct TALER_TESTING_Command *wire_details_cmd;
+        const char *payto_uri;
+        struct TALER_PaytoHashP h_payto;
+
+        wire_details_cmd
+          = TALER_TESTING_interpreter_lookup_command (is,
+                                                      tts->
+                                                      wire_details_reference);
+        if (NULL == wire_details_cmd)
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_payto_uri (wire_details_cmd,
+                                               &payto_uri))
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+        TALER_payto_hash (payto_uri,
+                          &h_payto);
+        if (0 != GNUNET_memcmp (&h_payto,
+                                &ta->h_payto))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Wire hash missmath to command %s\n",
+                      tts->cmd->label);
+          json_dumpf (hr->reply,
+                      stderr,
+                      0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+      }
+      if (NULL != tts->total_amount_reference)
+      {
+        const struct TALER_TESTING_Command *total_amount_cmd;
+        const struct TALER_Amount *total_amount_from_reference;
+
+        total_amount_cmd
+          = TALER_TESTING_interpreter_lookup_command (is,
+                                                      tts->
+                                                      total_amount_reference);
+        if (NULL == total_amount_cmd)
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+        if (GNUNET_OK !=
+            TALER_TESTING_get_trait_amount (total_amount_cmd,
+                                            &total_amount_from_reference))
+        {
+          GNUNET_break (0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+        if (0 != TALER_amount_cmp (&ta->total_amount,
+                                   total_amount_from_reference))
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Amount mismatch in command %s\n",
+                      tts->cmd->label);
+          json_dumpf (hr->reply,
+                      stderr,
+                      0);
+          TALER_TESTING_interpreter_fail (is);
+          return;
+        }
+      }
+      break;
+    } /* case OK */
+  } /* switch on status */
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command under execution.
+ * @param is the interpreter state.
+ */
+static void
+track_transfer_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  /* looking for a wtid to track .. */
+  struct TrackTransferState *tts = cls;
+  struct TALER_WireTransferIdentifierRawP wtid;
+  const struct TALER_WireTransferIdentifierRawP *wtid_ptr;
+
+  tts->cmd = cmd;
+  /* If no reference is given, we'll use a all-zeros
+   * WTID */
+  memset (&wtid,
+          0,
+          sizeof (wtid));
+  wtid_ptr = &wtid;
+  tts->is = is;
+  if (NULL != tts->wtid_reference)
+  {
+    const struct TALER_TESTING_Command *wtid_cmd;
+
+    wtid_cmd = TALER_TESTING_interpreter_lookup_command (tts->is,
+                                                         tts->wtid_reference);
+    if (NULL == wtid_cmd)
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (tts->is);
+      return;
+    }
+
+    if (GNUNET_OK !=
+        TALER_TESTING_get_trait_wtid (wtid_cmd,
+                                      &wtid_ptr))
+    {
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (tts->is);
+      return;
+    }
+    GNUNET_assert (NULL != wtid_ptr);
+  }
+  tts->tth = TALER_EXCHANGE_transfers_get (
+    TALER_TESTING_interpreter_get_context (is),
+    TALER_TESTING_get_exchange_url (is),
+    TALER_TESTING_get_keys (is),
+    wtid_ptr,
+    &track_transfer_cb,
+    tts);
+  GNUNET_assert (NULL != tts->tth);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transfer_empty (const char *label,
+                                        const char *wtid_reference,
+                                        unsigned int expected_response_code)
+{
+  struct TrackTransferState *tts;
+
+  tts = GNUNET_new (struct TrackTransferState);
+  tts->wtid_reference = wtid_reference;
+  tts->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tts,
+      .label = label,
+      .run = &track_transfer_run,
+      .cleanup = &track_transfer_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_track_transfer (const char *label,
+                                  const char *wtid_reference,
+                                  unsigned int expected_response_code,
+                                  const char *expected_total_amount,
+                                  const char *expected_wire_fee)
+{
+  struct TrackTransferState *tts;
+
+  tts = GNUNET_new (struct TrackTransferState);
+  tts->wtid_reference = wtid_reference;
+  tts->expected_response_code = expected_response_code;
+  tts->expected_total_amount = expected_total_amount;
+  tts->expected_wire_fee = expected_wire_fee;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = tts,
+      .label = label,
+      .run = &track_transfer_run,
+      .cleanup = &track_transfer_cleanup
+    };
+
+    return cmd;
+  }
+}
+
+
+/* end of testing_api_cmd_gransfer_get.c */
diff --git a/src/testing/testing_api_cmd_wait.c 
b/src/testing/testing_api_cmd_wait.c
new file mode 100644
index 0000000..40f4d36
--- /dev/null
+++ b/src/testing/testing_api_cmd_wait.c
@@ -0,0 +1,134 @@
+/*
+  This file is part of TALER
+  (C) 2018 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 testing/testing_api_cmd_wait.c
+ * @brief command(s) to wait on some process
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_testing_lib.h"
+
+
+/**
+ * Cleanup the state from a "wait service" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+wait_service_cleanup (void *cls,
+                      const struct TALER_TESTING_Command *cmd)
+{
+  (void) cls;
+  (void) cmd;
+  /* nothing to clean.  */
+  return;
+}
+
+
+/**
+ * No traits to offer, just provide a stub to be called when
+ * some CMDs iterates through the list of all the commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the trait to return.
+ * @return #GNUNET_OK on success.
+ */
+static int
+wait_service_traits (void *cls,
+                     const void **ret,
+                     const char *trait,
+                     unsigned int index)
+{
+  (void) cls;
+  (void) ret;
+  (void) trait;
+  (void) index;
+  return GNUNET_NO;
+}
+
+
+/**
+ * Run a "wait service" CMD.
+ *
+ * @param cls closure.
+ * @param cmd the command being run.
+ * @param is the interpreter state.
+ */
+static void
+wait_service_run (void *cls,
+                  const struct TALER_TESTING_Command *cmd,
+                  struct TALER_TESTING_Interpreter *is)
+{
+  unsigned int iter = 0;
+  const char *url = cmd->cls;
+  char *wget_cmd;
+
+  (void) cls;
+  GNUNET_asprintf (&wget_cmd,
+                   "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null",
+                   url);
+  do
+  {
+    fprintf (stderr, ".");
+
+    if (10 == iter++)
+    {
+      TALER_LOG_ERROR ("Could not reach the proxied service\n");
+      TALER_TESTING_interpreter_fail (is);
+      GNUNET_free (wget_cmd);
+      return;
+    }
+  }
+  while (0 != system (wget_cmd));
+
+  GNUNET_free (wget_cmd);
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * This CMD simply tries to connect via HTTP to the
+ * service addressed by @a url.  It attempts 10 times
+ * before giving up and make the test fail.
+ *
+ * @param label label for the command.
+ * @param url complete URL to connect to.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_wait_service (const char *label,
+                                const char *url)
+{
+  struct TALER_TESTING_Command cmd = {
+    .label = label,
+    .run = wait_service_run,
+    .cleanup = wait_service_cleanup,
+    .traits = wait_service_traits,
+    .cls = (void *) url
+  };
+
+  return cmd;
+}
+
+
+/* end of testing_api_cmd_sleep.c  */
diff --git a/src/testing/testing_api_cmd_withdraw.c 
b/src/testing/testing_api_cmd_withdraw.c
new file mode 100644
index 0000000..8873c24
--- /dev/null
+++ b/src/testing/testing_api_cmd_withdraw.c
@@ -0,0 +1,696 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-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 testing/testing_api_cmd_withdraw.c
+ * @brief main interpreter loop for testcases
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "taler_json_lib.h"
+#include <microhttpd.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_signatures.h"
+#include "taler_extensions.h"
+#include "taler_testing_lib.h"
+#include "backoff.h"
+
+
+/**
+ * How often do we retry before giving up?
+ */
+#define NUM_RETRIES 15
+
+/**
+ * How long do we wait AT LEAST if the exchange says the reserve is unknown?
+ */
+#define UNKNOWN_MIN_BACKOFF GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MILLISECONDS, 10)
+
+/**
+ * How long do we wait AT MOST if the exchange says the reserve is unknown?
+ */
+#define UNKNOWN_MAX_BACKOFF GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MILLISECONDS, 100)
+
+/**
+ * State for a "withdraw" CMD.
+ */
+struct WithdrawState
+{
+
+  /**
+   * Which reserve should we withdraw from?
+   */
+  const char *reserve_reference;
+
+  /**
+   * Reference to a withdraw or reveal operation from which we should
+   * re-use the private coin key, or NULL for regular withdrawal.
+   */
+  const char *reuse_coin_key_ref;
+
+  /**
+   * Our command.
+   */
+  const struct TALER_TESTING_Command *cmd;
+
+  /**
+   * String describing the denomination value we should withdraw.
+   * A corresponding denomination key must exist in the exchange's
+   * offerings.  Can be NULL if @e pk is set instead.
+   */
+  struct TALER_Amount amount;
+
+  /**
+   * If @e amount is NULL, this specifies the denomination key to
+   * use.  Otherwise, this will be set (by the interpreter) to the
+   * denomination PK matching @e amount.
+   */
+  struct TALER_EXCHANGE_DenomPublicKey *pk;
+
+  /**
+   * Exchange base URL.  Only used as offered trait.
+   */
+  char *exchange_url;
+
+  /**
+   * URI if the reserve we are withdrawing from.
+   */
+  char *reserve_payto_uri;
+
+  /**
+   * Private key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePrivateKeyP reserve_priv;
+
+  /**
+   * Public key of the reserve we are withdrawing from.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Private key of the coin.
+   */
+  struct TALER_CoinSpendPrivateKeyP coin_priv;
+
+  /**
+   * Blinding key used during the operation.
+   */
+  union TALER_DenominationBlindingKeyP bks;
+
+  /**
+   * Values contributed from the exchange during the
+   * withdraw protocol.
+   */
+  struct TALER_ExchangeWithdrawValues exchange_vals;
+
+  /**
+   * Interpreter state (during command).
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Set (by the interpreter) to the exchange's signature over the
+   * coin's public key.
+   */
+  struct TALER_DenominationSignature sig;
+
+  /**
+   * Private key material of the coin, set by the interpreter.
+   */
+  struct TALER_PlanchetMasterSecretP ps;
+
+  /**
+   * An age > 0 signifies age restriction is required
+   */
+  uint8_t age;
+
+  /**
+   * If age > 0, put here the corresponding age commitment with its proof and
+   * its hash, respectivelly.
+   */
+  struct TALER_AgeCommitmentProof age_commitment_proof;
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Reserve history entry that corresponds to this operation.
+   * Will be of type #TALER_EXCHANGE_RTT_WITHDRAWAL.
+   */
+  struct TALER_EXCHANGE_ReserveHistoryEntry reserve_history;
+
+  /**
+   * Withdraw handle (while operation is running).
+   */
+  struct TALER_EXCHANGE_WithdrawHandle *wsh;
+
+  /**
+   * Task scheduled to try later.
+   */
+  struct GNUNET_SCHEDULER_Task *retry_task;
+
+  /**
+   * How long do we wait until we retry?
+   */
+  struct GNUNET_TIME_Relative backoff;
+
+  /**
+   * Total withdraw backoff applied.
+   */
+  struct GNUNET_TIME_Relative total_backoff;
+
+  /**
+   * Set to the KYC requirement payto hash *if* the exchange replied with a
+   * request for KYC.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Set to the KYC requirement row *if* the exchange replied with
+   * a request for KYC.
+   */
+  uint64_t requirement_row;
+
+  /**
+   * Expected HTTP response code to the request.
+   */
+  unsigned int expected_response_code;
+
+  /**
+   * Was this command modified via
+   * #TALER_TESTING_cmd_withdraw_with_retry to
+   * enable retries? How often should we still retry?
+   */
+  unsigned int do_retry;
+};
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the commaind being run.
+ * @param is interpreter state.
+ */
+static void
+withdraw_run (void *cls,
+              const struct TALER_TESTING_Command *cmd,
+              struct TALER_TESTING_Interpreter *is);
+
+
+/**
+ * Task scheduled to re-try #withdraw_run.
+ *
+ * @param cls a `struct WithdrawState`
+ */
+static void
+do_retry (void *cls)
+{
+  struct WithdrawState *ws = cls;
+
+  ws->retry_task = NULL;
+  TALER_TESTING_touch_cmd (ws->is);
+  withdraw_run (ws,
+                NULL,
+                ws->is);
+}
+
+
+/**
+ * "reserve withdraw" operation callback; checks that the
+ * response code is expected and store the exchange signature
+ * in the state.
+ *
+ * @param cls closure.
+ * @param wr withdraw response details
+ */
+static void
+reserve_withdraw_cb (void *cls,
+                     const struct TALER_EXCHANGE_WithdrawResponse *wr)
+{
+  struct WithdrawState *ws = cls;
+  struct TALER_TESTING_Interpreter *is = ws->is;
+
+  ws->wsh = NULL;
+  if (ws->expected_response_code != wr->hr.http_status)
+  {
+    if (0 != ws->do_retry)
+    {
+      if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
+        ws->do_retry--; /* we don't count reserve unknown as failures here */
+      if ( (0 == wr->hr.http_status) ||
+           (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec) ||
+           (TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS == wr->hr.ec) ||
+           (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN == wr->hr.ec) ||
+           (MHD_HTTP_INTERNAL_SERVER_ERROR == wr->hr.http_status) )
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                    "Retrying withdraw failed with %u/%d\n",
+                    wr->hr.http_status,
+                    (int) wr->hr.ec);
+        /* on DB conflicts, do not use backoff */
+        if (TALER_EC_GENERIC_DB_SOFT_FAILURE == wr->hr.ec)
+          ws->backoff = GNUNET_TIME_UNIT_ZERO;
+        else if (TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN != wr->hr.ec)
+          ws->backoff = EXCHANGE_LIB_BACKOFF (ws->backoff);
+        else
+          ws->backoff = GNUNET_TIME_relative_max (UNKNOWN_MIN_BACKOFF,
+                                                  ws->backoff);
+        ws->backoff = GNUNET_TIME_relative_min (ws->backoff,
+                                                UNKNOWN_MAX_BACKOFF);
+        ws->total_backoff = GNUNET_TIME_relative_add (ws->total_backoff,
+                                                      ws->backoff);
+        TALER_TESTING_inc_tries (ws->is);
+        ws->retry_task = GNUNET_SCHEDULER_add_delayed (ws->backoff,
+                                                       &do_retry,
+                                                       ws);
+        return;
+      }
+    }
+    TALER_TESTING_unexpected_status_with_body (is,
+                                               wr->hr.http_status,
+                                               ws->expected_response_code,
+                                               wr->hr.reply);
+    return;
+  }
+  switch (wr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    TALER_denom_sig_deep_copy (&ws->sig,
+                               &wr->details.ok.sig);
+    ws->coin_priv = wr->details.ok.coin_priv;
+    ws->bks = wr->details.ok.bks;
+    ws->exchange_vals = wr->details.ok.exchange_vals;
+    if (0 != ws->total_backoff.rel_value_us)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Total withdraw backoff for %s was %s\n",
+                  ws->cmd->label,
+                  GNUNET_STRINGS_relative_time_to_string (ws->total_backoff,
+                                                          true));
+    }
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    /* nothing to check */
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    /* nothing to check */
+    break;
+  case MHD_HTTP_CONFLICT:
+    /* nothing to check */
+    break;
+  case MHD_HTTP_GONE:
+    /* theoretically could check that the key was actually */
+    break;
+  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+    /* KYC required */
+    ws->requirement_row =
+      wr->details.unavailable_for_legal_reasons.requirement_row;
+    ws->h_payto
+      = wr->details.unavailable_for_legal_reasons.h_payto;
+    break;
+  default:
+    /* Unsupported status code (by test harness) */
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Withdraw test command does not support status code %u\n",
+                wr->hr.http_status);
+    GNUNET_break (0);
+    break;
+  }
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Run the command.
+ */
+static void
+withdraw_run (void *cls,
+              const struct TALER_TESTING_Command *cmd,
+              struct TALER_TESTING_Interpreter *is)
+{
+  struct WithdrawState *ws = cls;
+  const struct TALER_ReservePrivateKeyP *rp;
+  const struct TALER_TESTING_Command *create_reserve;
+  const struct TALER_EXCHANGE_DenomPublicKey *dpk;
+
+  if (NULL != cmd)
+    ws->cmd = cmd;
+  ws->is = is;
+  create_reserve
+    = TALER_TESTING_interpreter_lookup_command (
+        is,
+        ws->reserve_reference);
+  if (NULL == create_reserve)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_TESTING_get_trait_reserve_priv (create_reserve,
+                                            &rp))
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL == ws->exchange_url)
+    ws->exchange_url
+      = GNUNET_strdup (TALER_TESTING_get_exchange_url (is));
+  ws->reserve_priv = *rp;
+  GNUNET_CRYPTO_eddsa_key_get_public (&ws->reserve_priv.eddsa_priv,
+                                      &ws->reserve_pub.eddsa_pub);
+  ws->reserve_payto_uri
+    = TALER_reserve_make_payto (ws->exchange_url,
+                                &ws->reserve_pub);
+
+  if (NULL == ws->reuse_coin_key_ref)
+  {
+    TALER_planchet_master_setup_random (&ws->ps);
+  }
+  else
+  {
+    const struct TALER_PlanchetMasterSecretP *ps;
+    const struct TALER_TESTING_Command *cref;
+    char *cstr;
+    unsigned int index;
+
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_parse_coin_reference (
+                     ws->reuse_coin_key_ref,
+                     &cstr,
+                     &index));
+    cref = TALER_TESTING_interpreter_lookup_command (is,
+                                                     cstr);
+    GNUNET_assert (NULL != cref);
+    GNUNET_free (cstr);
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_TESTING_get_trait_planchet_secret (cref,
+                                                            &ps));
+    ws->ps = *ps;
+  }
+
+  if (NULL == ws->pk)
+  {
+    dpk = TALER_TESTING_find_pk (TALER_TESTING_get_keys (is),
+                                 &ws->amount,
+                                 ws->age > 0);
+    if (NULL == dpk)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to determine denomination key at %s\n",
+                  (NULL != cmd) ? cmd->label : "<retried command>");
+      GNUNET_break (0);
+      TALER_TESTING_interpreter_fail (is);
+      return;
+    }
+    /* We copy the denomination key, as re-querying /keys
+     * would free the old one. */
+    ws->pk = TALER_EXCHANGE_copy_denomination_key (dpk);
+  }
+  else
+  {
+    ws->amount = ws->pk->value;
+  }
+
+  ws->reserve_history.type = TALER_EXCHANGE_RTT_WITHDRAWAL;
+  GNUNET_assert (0 <=
+                 TALER_amount_add (&ws->reserve_history.amount,
+                                   &ws->amount,
+                                   &ws->pk->fees.withdraw));
+  ws->reserve_history.details.withdraw.fee = ws->pk->fees.withdraw;
+  {
+    struct TALER_EXCHANGE_WithdrawCoinInput wci = {
+      .pk = ws->pk,
+      .ps = &ws->ps,
+      .ach = 0 < ws->age ? &ws->h_age_commitment : NULL
+    };
+    ws->wsh = TALER_EXCHANGE_withdraw (
+      TALER_TESTING_interpreter_get_context (is),
+      TALER_TESTING_get_exchange_url (is),
+      TALER_TESTING_get_keys (is),
+      rp,
+      &wci,
+      &reserve_withdraw_cb,
+      ws);
+  }
+  if (NULL == ws->wsh)
+  {
+    GNUNET_break (0);
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+}
+
+
+/**
+ * Free the state of a "withdraw" CMD, and possibly cancel
+ * a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command being freed.
+ */
+static void
+withdraw_cleanup (void *cls,
+                  const struct TALER_TESTING_Command *cmd)
+{
+  struct WithdrawState *ws = cls;
+
+  if (NULL != ws->wsh)
+  {
+    TALER_TESTING_command_incomplete (ws->is,
+                                      cmd->label);
+    TALER_EXCHANGE_withdraw_cancel (ws->wsh);
+    ws->wsh = NULL;
+  }
+  if (NULL != ws->retry_task)
+  {
+    GNUNET_SCHEDULER_cancel (ws->retry_task);
+    ws->retry_task = NULL;
+  }
+  TALER_denom_sig_free (&ws->sig);
+  if (NULL != ws->pk)
+  {
+    TALER_EXCHANGE_destroy_denomination_key (ws->pk);
+    ws->pk = NULL;
+  }
+  if (ws->age > 0)
+    TALER_age_commitment_proof_free (&ws->age_commitment_proof);
+  GNUNET_free (ws->exchange_url);
+  GNUNET_free (ws->reserve_payto_uri);
+  GNUNET_free (ws);
+}
+
+
+/**
+ * Offer internal data to a "withdraw" CMD state to other
+ * commands.
+ *
+ * @param cls closure
+ * @param[out] ret result (could be anything)
+ * @param trait name of the trait
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+withdraw_traits (void *cls,
+                 const void **ret,
+                 const char *trait,
+                 unsigned int index)
+{
+  struct WithdrawState *ws = cls;
+  struct TALER_TESTING_Trait traits[] = {
+    /* history entry MUST be first due to response code logic below! */
+    TALER_TESTING_make_trait_reserve_history (0,
+                                              &ws->reserve_history),
+    TALER_TESTING_make_trait_coin_priv (0 /* only one coin */,
+                                        &ws->coin_priv),
+    TALER_TESTING_make_trait_planchet_secret (&ws->ps),
+    TALER_TESTING_make_trait_blinding_key (0 /* only one coin */,
+                                           &ws->bks),
+    TALER_TESTING_make_trait_exchange_wd_value (0 /* only one coin */,
+                                                &ws->exchange_vals),
+    TALER_TESTING_make_trait_denom_pub (0 /* only one coin */,
+                                        ws->pk),
+    TALER_TESTING_make_trait_denom_sig (0 /* only one coin */,
+                                        &ws->sig),
+    TALER_TESTING_make_trait_reserve_priv (&ws->reserve_priv),
+    TALER_TESTING_make_trait_reserve_pub (&ws->reserve_pub),
+    TALER_TESTING_make_trait_amount (&ws->amount),
+    TALER_TESTING_make_trait_legi_requirement_row (&ws->requirement_row),
+    TALER_TESTING_make_trait_h_payto (&ws->h_payto),
+    TALER_TESTING_make_trait_payto_uri (ws->reserve_payto_uri),
+    TALER_TESTING_make_trait_exchange_url (ws->exchange_url),
+    TALER_TESTING_make_trait_age_commitment_proof (0,
+                                                   0 < ws->age
+                                                   ? &ws->age_commitment_proof
+                                                   : NULL),
+    TALER_TESTING_make_trait_h_age_commitment (0,
+                                               0 < ws->age
+                                               ? &ws->h_age_commitment
+                                               : NULL),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait ((ws->expected_response_code == MHD_HTTP_OK)
+                                  ? &traits[0]   /* we have reserve history */
+                                  : &traits[1],  /* skip reserve history */
+                                  ret,
+                                  trait,
+                                  index);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount (const char *label,
+                                   const char *reserve_reference,
+                                   const char *amount,
+                                   uint8_t age,
+                                   unsigned int expected_response_code)
+{
+  struct WithdrawState *ws;
+
+  ws = GNUNET_new (struct WithdrawState);
+  ws->age = age;
+  if (0 < age)
+  {
+    struct GNUNET_HashCode seed;
+    struct TALER_AgeMask mask;
+
+    mask = TALER_extensions_get_age_restriction_mask ();
+    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                                &seed,
+                                sizeof(seed));
+
+    if (GNUNET_OK !=
+        TALER_age_restriction_commit (
+          &mask,
+          age,
+          &seed,
+          &ws->age_commitment_proof))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Failed to generate age commitment for age %d at %s\n",
+                  age,
+                  label);
+      GNUNET_assert (0);
+    }
+    TALER_age_commitment_hash (&ws->age_commitment_proof.commitment,
+                               &ws->h_age_commitment);
+  }
+
+  ws->reserve_reference = reserve_reference;
+  if (GNUNET_OK !=
+      TALER_string_to_amount (amount,
+                              &ws->amount))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to parse amount `%s' at %s\n",
+                amount,
+                label);
+    GNUNET_assert (0);
+  }
+  ws->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ws,
+      .label = label,
+      .run = &withdraw_run,
+      .cleanup = &withdraw_cleanup,
+      .traits = &withdraw_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_amount_reuse_key (
+  const char *label,
+  const char *reserve_reference,
+  const char *amount,
+  uint8_t age,
+  const char *coin_ref,
+  unsigned int expected_response_code)
+{
+  struct TALER_TESTING_Command cmd;
+
+  cmd = TALER_TESTING_cmd_withdraw_amount (label,
+                                           reserve_reference,
+                                           amount,
+                                           age,
+                                           expected_response_code);
+  {
+    struct WithdrawState *ws = cmd.cls;
+
+    ws->reuse_coin_key_ref = coin_ref;
+  }
+  return cmd;
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_denomination (
+  const char *label,
+  const char *reserve_reference,
+  const struct TALER_EXCHANGE_DenomPublicKey *dk,
+  unsigned int expected_response_code)
+{
+  struct WithdrawState *ws;
+
+  if (NULL == dk)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Denomination key not specified at %s\n",
+                label);
+    GNUNET_assert (0);
+  }
+  ws = GNUNET_new (struct WithdrawState);
+  ws->reserve_reference = reserve_reference;
+  ws->pk = TALER_EXCHANGE_copy_denomination_key (dk);
+  ws->expected_response_code = expected_response_code;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = ws,
+      .label = label,
+      .run = &withdraw_run,
+      .cleanup = &withdraw_cleanup,
+      .traits = &withdraw_traits
+    };
+
+    return cmd;
+  }
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd)
+{
+  struct WithdrawState *ws;
+
+  GNUNET_assert (&withdraw_run == cmd.run);
+  ws = cmd.cls;
+  ws->do_retry = NUM_RETRIES;
+  return cmd;
+}
+
+
+/* end of testing_api_cmd_withdraw.c */
diff --git a/src/testing/testing_api_misc.c b/src/testing/testing_api_misc.c
new file mode 100644
index 0000000..665402f
--- /dev/null
+++ b/src/testing/testing_api_misc.c
@@ -0,0 +1,377 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as
+  published by the Free Software Foundation; either version 3, or
+  (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public
+  License along with TALER; see the file COPYING.  If not, see
+  <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file testing/testing_api_misc.c
+ * @brief non-command functions useful for writing tests
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_testing_lib.h"
+#include "taler_fakebank_lib.h"
+
+
+bool
+TALER_TESTING_has_in_name (const char *prog,
+                           const char *marker)
+{
+  size_t name_pos;
+  size_t pos;
+
+  if (! prog || ! marker)
+    return false;
+
+  pos = 0;
+  name_pos = 0;
+  while (prog[pos])
+  {
+    if ('/' == prog[pos])
+      name_pos = pos + 1;
+    pos++;
+  }
+  if (name_pos == pos)
+    return true;
+  return (NULL != strstr (prog + name_pos,
+                          marker));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_get_credentials (
+  const char *cfg_file,
+  const char *exchange_account_section,
+  enum TALER_TESTING_BankSystem bs,
+  struct TALER_TESTING_Credentials *ua)
+{
+  unsigned long long port;
+  char *exchange_payto_uri;
+
+  ua->cfg = GNUNET_CONFIGURATION_create ();
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_load (ua->cfg,
+                                 cfg_file))
+  {
+    GNUNET_break (0);
+    GNUNET_CONFIGURATION_destroy (ua->cfg);
+    return GNUNET_SYSERR;
+  }
+  if (0 !=
+      strncasecmp (exchange_account_section,
+                   "exchange-account-",
+                   strlen ("exchange-account-")))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             exchange_account_section,
+                                             "PAYTO_URI",
+                                             &exchange_payto_uri))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               exchange_account_section,
+                               "PAYTO_URI");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_number (ua->cfg,
+                                             "bank",
+                                             "HTTP_PORT",
+                                             &port))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "bank",
+                               "HTTP_PORT");
+    return GNUNET_SYSERR;
+  }
+  {
+    char *csn;
+
+    GNUNET_asprintf (&csn,
+                     "exchange-accountcredentials-%s",
+                     &exchange_account_section[strlen ("exchange-account-")]);
+    if (GNUNET_OK !=
+        TALER_BANK_auth_parse_cfg (ua->cfg,
+                                   csn,
+                                   &ua->ba))
+    {
+      GNUNET_break (0);
+      GNUNET_free (csn);
+      return GNUNET_SYSERR;
+    }
+    GNUNET_free (csn);
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             "exchange",
+                                             "BASE_URL",
+                                             &ua->exchange_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange",
+                               "BASE_URL");
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_string (ua->cfg,
+                                             "auditor",
+                                             "BASE_URL",
+                                             &ua->auditor_url))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "auditor",
+                               "BASE_URL");
+    return GNUNET_SYSERR;
+  }
+
+  switch (bs)
+  {
+  case TALER_TESTING_BS_FAKEBANK:
+    ua->exchange_payto
+      = exchange_payto_uri;
+    ua->user42_payto
+      = GNUNET_strdup ("payto://x-taler-bank/localhost/42?receiver-name=42");
+    ua->user43_payto
+      = GNUNET_strdup ("payto://x-taler-bank/localhost/43?receiver-name=43");
+    break;
+  case TALER_TESTING_BS_IBAN:
+    ua->exchange_payto
+      = exchange_payto_uri;
+    ua->user42_payto
+      = GNUNET_strdup (
+          
"payto://iban/SANDBOXX/FR7630006000011234567890189?receiver-name=User42");
+    ua->user43_payto
+      = GNUNET_strdup (
+          "payto://iban/SANDBOXX/GB33BUKB20201555555555?receiver-name=User43");
+    break;
+  }
+  return GNUNET_OK;
+}
+
+
+json_t *
+TALER_TESTING_make_wire_details (const char *payto)
+{
+  struct TALER_WireSaltP salt;
+
+  /* salt must be constant for aggregation tests! */
+  memset (&salt,
+          47,
+          sizeof (salt));
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("payto_uri",
+                             payto),
+    GNUNET_JSON_pack_data_auto ("salt",
+                                &salt));
+}
+
+
+/**
+ * Remove @a option directory from @a section in @a cfg.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+remove_dir (const struct GNUNET_CONFIGURATION_Handle *cfg,
+            const char *section,
+            const char *option)
+{
+  char *dir;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               section,
+                                               option,
+                                               &dir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               section,
+                               option);
+    return GNUNET_SYSERR;
+  }
+  if (GNUNET_YES ==
+      GNUNET_DISK_directory_test (dir,
+                                  GNUNET_NO))
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_directory_remove (dir));
+  GNUNET_free (dir);
+  return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_cleanup_files_cfg (
+  void *cls,
+  const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+  char *dir;
+
+  (void) cls;
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (cfg,
+                                               "exchange-offline",
+                                               "SECM_TOFU_FILE",
+                                               &dir))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "exchange-offline",
+                               "SECM_TOFU_FILE");
+    return GNUNET_SYSERR;
+  }
+  if ( (0 != unlink (dir)) &&
+       (ENOENT != errno) )
+  {
+    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+                              "unlink",
+                              dir);
+    GNUNET_free (dir);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_free (dir);
+  if (GNUNET_OK !=
+      remove_dir (cfg,
+                  "taler-exchange-secmod-eddsa",
+                  "KEY_DIR"))
+    return GNUNET_SYSERR;
+  if (GNUNET_OK !=
+      remove_dir (cfg,
+                  "taler-exchange-secmod-rsa",
+                  "KEY_DIR"))
+    return GNUNET_SYSERR;
+  return GNUNET_OK;
+}
+
+
+const struct TALER_EXCHANGE_DenomPublicKey *
+TALER_TESTING_find_pk (
+  const struct TALER_EXCHANGE_Keys *keys,
+  const struct TALER_Amount *amount,
+  bool age_restricted)
+{
+  struct GNUNET_TIME_Timestamp now;
+  struct TALER_EXCHANGE_DenomPublicKey *pk;
+  char *str;
+
+  now = GNUNET_TIME_timestamp_get ();
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+  {
+    pk = &keys->denom_keys[i];
+    if ( (0 == TALER_amount_cmp (amount,
+                                 &pk->value)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     >=,
+                                     pk->valid_from)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     <,
+                                     pk->withdraw_valid_until)) &&
+         (age_restricted == (0 != pk->key.age_mask.bits)) )
+      return pk;
+  }
+  /* do 2nd pass to check if expiration times are to blame for
+   * failure */
+  str = TALER_amount_to_string (amount);
+  for (unsigned int i = 0; i<keys->num_denom_keys; i++)
+  {
+    pk = &keys->denom_keys[i];
+    if ( (0 == TALER_amount_cmp (amount,
+                                 &pk->value)) &&
+         (GNUNET_TIME_timestamp_cmp (now,
+                                     <,
+                                     pk->valid_from) ||
+          GNUNET_TIME_timestamp_cmp (now,
+                                     >,
+                                     pk->withdraw_valid_until) ) &&
+         (age_restricted == (0 != pk->key.age_mask.bits)) )
+    {
+      GNUNET_log
+        (GNUNET_ERROR_TYPE_WARNING,
+        "Have denomination key for `%s', but with wrong"
+        " expiration range %llu vs [%llu,%llu)\n",
+        str,
+        (unsigned long long) now.abs_time.abs_value_us,
+        (unsigned long long) pk->valid_from.abs_time.abs_value_us,
+        (unsigned long long) pk->withdraw_valid_until.abs_time.abs_value_us);
+      GNUNET_free (str);
+      return NULL;
+    }
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+              "No denomination key for amount %s found\n",
+              str);
+  GNUNET_free (str);
+  return NULL;
+}
+
+
+int
+TALER_TESTING_wait_httpd_ready (const char *base_url)
+{
+  char *wget_cmd;
+  unsigned int iter;
+
+  GNUNET_asprintf (&wget_cmd,
+                   "wget -q -t 1 -T 1 %s -o /dev/null -O /dev/null",
+                   base_url); // make sure ends with '/'
+  /* give child time to start and bind against the socket */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Waiting for HTTP service to be ready (check with: %s)\n",
+              wget_cmd);
+  iter = 0;
+  do
+  {
+    if (10 == iter)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to launch HTTP service (or `wget')\n");
+      GNUNET_free (wget_cmd);
+      return 77;
+    }
+    sleep (1);
+    iter++;
+  }
+  while (0 != system (wget_cmd));
+  GNUNET_free (wget_cmd);
+  return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_TESTING_url_port_free (const char *url)
+{
+  const char *port;
+  long pnum;
+
+  port = strrchr (url,
+                  (unsigned char) ':');
+  if (NULL == port)
+    pnum = 80;
+  else
+    pnum = strtol (port + 1, NULL, 10);
+  if (GNUNET_OK !=
+      GNUNET_NETWORK_test_port_free (IPPROTO_TCP,
+                                     pnum))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Port %u not available.\n",
+                (unsigned int) pnum);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
diff --git a/src/util/.gitignore b/src/util/.gitignore
new file mode 100644
index 0000000..d79786e
--- /dev/null
+++ b/src/util/.gitignore
@@ -0,0 +1,12 @@
+taler-config
+test_payto
+taler-exchange-secmod-rsa
+taler-exchange-secmod-cs
+taler-exchange-secmod-eddsa
+test_helper_rsa
+test_helper_rsa_home/
+test_helper_cs
+test_helper_cs_home/
+test_helper_eddsa
+test_helper_eddsa_home/
+test_conversion
diff --git a/src/util/Makefile.am b/src/util/Makefile.am
new file mode 100644
index 0000000..9d1ec9d
--- /dev/null
+++ b/src/util/Makefile.am
@@ -0,0 +1,196 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include $(LIBGCRYPT_CFLAGS)
+
+if USE_COVERAGE
+  AM_CFLAGS = --coverage -O0
+  XLIB = -lgcov
+endif
+
+
+pkgcfgdir = $(prefix)/share/taler/config.d/
+
+pkgcfg_DATA = \
+  paths.conf \
+  taler-exchange-secmod-eddsa.conf \
+  taler-exchange-secmod-rsa.conf \
+  taler-exchange-secmod-cs.conf
+
+EXTRA_DIST = \
+  $(pkgcfg_DATA) \
+  taler-config.in \
+  test_helper_eddsa.conf \
+  test_helper_rsa.conf \
+  test_helper_cs.conf \
+  test_conversion.sh
+
+bin_PROGRAMS = \
+  taler-exchange-secmod-eddsa \
+  taler-exchange-secmod-rsa \
+  taler-exchange-secmod-cs
+
+bin_SCRIPTS = \
+  taler-config
+
+edit_script = $(SED) -e 's,%libdir%,$(libdir),'g $(NULL)
+
+taler-config: taler-config.in
+       rm -f $@ $@.tmp && \
+       $(edit_script) $< >$@.tmp && \
+       chmod a-w+x $@.tmp && \
+       mv $@.tmp $@
+
+CLEANFILES = \
+  taler-config
+
+taler_exchange_secmod_rsa_SOURCES = \
+  taler-exchange-secmod-rsa.c taler-exchange-secmod-rsa.h \
+  secmod_common.c secmod_common.h
+taler_exchange_secmod_rsa_LDADD = \
+  libtalerutil.la \
+  -lgnunetutil \
+  -lpthread \
+  $(LIBGCRYPT_LIBS) \
+  $(XLIB)
+
+taler_exchange_secmod_cs_SOURCES = \
+  taler-exchange-secmod-cs.c taler-exchange-secmod-cs.h \
+  secmod_common.c secmod_common.h
+taler_exchange_secmod_cs_LDADD = \
+  libtalerutil.la \
+  -lgnunetutil \
+  -lpthread \
+  $(LIBGCRYPT_LIBS) \
+  $(XLIB)
+
+taler_exchange_secmod_eddsa_SOURCES = \
+  taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \
+  secmod_common.c secmod_common.h
+taler_exchange_secmod_eddsa_LDADD = \
+  libtalerutil.la \
+  -lgnunetutil \
+  -lpthread \
+  $(LIBGCRYPT_LIBS) \
+  $(XLIB)
+
+lib_LTLIBRARIES = \
+  libtalerutil.la
+
+libtalerutil_la_SOURCES = \
+  age_restriction.c \
+  amount.c \
+  aml_signatures.c \
+  auditor_signatures.c \
+  config.c \
+  conversion.c \
+  crypto.c \
+  crypto_confirmation.c \
+  crypto_contract.c \
+  crypto_helper_common.c crypto_helper_common.h \
+  crypto_helper_rsa.c \
+  crypto_helper_cs.c \
+  crypto_helper_esign.c \
+  crypto_wire.c \
+  denom.c \
+  exchange_signatures.c \
+  getopt.c \
+  lang.c \
+  iban.c \
+  merchant_signatures.c \
+  mhd.c \
+  offline_signatures.c \
+  payto.c \
+  secmod_signatures.c \
+  taler_error_codes.c \
+  url.c \
+  util.c \
+  wallet_signatures.c \
+  yna.c \
+  os_installation.c
+
+libtalerutil_la_LIBADD = \
+  -lgnunetutil \
+  -lgnunetjson \
+  -lsodium \
+  -ljansson \
+  $(LIBGCRYPT_LIBS) \
+  -lmicrohttpd $(XLIB) \
+  -lz \
+  -lm
+
+libtalerutil_la_LDFLAGS = \
+  -version-info 0:0:0 \
+  -no-undefined
+
+
+AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export 
PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
+
+check_PROGRAMS = \
+ test_age_restriction \
+ test_amount \
+ test_conversion \
+ test_crypto \
+ test_helper_eddsa \
+ test_helper_rsa \
+ test_helper_cs \
+ test_payto \
+ test_url
+
+TESTS = \
+ $(check_PROGRAMS)
+
+test_age_restriction_SOURCES = \
+  test_age_restriction.c
+test_age_restriction_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_conversion_SOURCES = \
+  test_conversion.c
+test_conversion_LDADD = \
+  -lgnunetjson \
+  -lgnunetutil \
+  -ljansson \
+  libtalerutil.la
+
+test_amount_SOURCES = \
+  test_amount.c
+test_amount_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_crypto_SOURCES = \
+  test_crypto.c
+test_crypto_LDADD = \
+  libtalerutil.la \
+  -lgnunetutil \
+  -ljansson
+
+test_payto_SOURCES = \
+  test_payto.c
+test_payto_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_helper_eddsa_SOURCES = \
+  test_helper_eddsa.c
+test_helper_eddsa_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_helper_rsa_SOURCES = \
+  test_helper_rsa.c
+test_helper_rsa_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_helper_cs_SOURCES = \
+  test_helper_cs.c
+test_helper_cs_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
+
+test_url_SOURCES = \
+  test_url.c
+test_url_LDADD = \
+  -lgnunetutil \
+  libtalerutil.la
diff --git a/src/util/conversion.c b/src/util/conversion.c
new file mode 100644
index 0000000..fdeffba
--- /dev/null
+++ b/src/util/conversion.c
@@ -0,0 +1,405 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file conversion.c
+ * @brief helper routines to run some external JSON-to-JSON converter
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <gnunet/gnunet_util_lib.h>
+
+
+struct TALER_JSON_ExternalConversion
+{
+  /**
+   * Callback to call with the result.
+   */
+  TALER_JSON_JsonCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Handle to the helper process.
+   */
+  struct GNUNET_OS_Process *helper;
+
+  /**
+   * Pipe for the stdin of the @e helper.
+   */
+  struct GNUNET_DISK_FileHandle *chld_stdin;
+
+  /**
+   * Pipe for the stdout of the @e helper.
+   */
+  struct GNUNET_DISK_FileHandle *chld_stdout;
+
+  /**
+   * Handle to wait on the child to terminate.
+   */
+  struct GNUNET_ChildWaitHandle *cwh;
+
+  /**
+   * Task to read JSON output from the child.
+   */
+  struct GNUNET_SCHEDULER_Task *read_task;
+
+  /**
+   * Task to send JSON input to the child.
+   */
+  struct GNUNET_SCHEDULER_Task *write_task;
+
+  /**
+   * Buffer with data we need to send to the helper.
+   */
+  void *write_buf;
+
+  /**
+   * Buffer for reading data from the helper.
+   */
+  void *read_buf;
+
+  /**
+   * Total length of @e write_buf.
+   */
+  size_t write_size;
+
+  /**
+   * Current write position in @e write_buf.
+   */
+  size_t write_pos;
+
+  /**
+   * Current size of @a read_buf.
+   */
+  size_t read_size;
+
+  /**
+   * Current offset in @a read_buf.
+   */
+  size_t read_pos;
+
+};
+
+
+/**
+ * Function called when we can read more data from
+ * the child process.
+ *
+ * @param cls our `struct TALER_JSON_ExternalConversion *`
+ */
+static void
+read_cb (void *cls)
+{
+  struct TALER_JSON_ExternalConversion *ec = cls;
+
+  ec->read_task = NULL;
+  while (1)
+  {
+    ssize_t ret;
+
+    if (ec->read_size == ec->read_pos)
+    {
+      /* Grow input buffer */
+      size_t ns;
+      void *tmp;
+
+      ns = GNUNET_MAX (2 * ec->read_size,
+                       1024);
+      if (ns > GNUNET_MAX_MALLOC_CHECKED)
+        ns = GNUNET_MAX_MALLOC_CHECKED;
+      if (ec->read_size == ns)
+      {
+        /* Helper returned more than 40 MB of data! Stop reading! */
+        GNUNET_break (0);
+        GNUNET_break (GNUNET_OK ==
+                      GNUNET_DISK_file_close (ec->chld_stdin));
+        return;
+      }
+      tmp = GNUNET_malloc_large (ns);
+      if (NULL == tmp)
+      {
+        /* out of memory, also stop reading */
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+                             "malloc");
+        GNUNET_break (GNUNET_OK ==
+                      GNUNET_DISK_file_close (ec->chld_stdin));
+        return;
+      }
+      GNUNET_memcpy (tmp,
+                     ec->read_buf,
+                     ec->read_pos);
+      GNUNET_free (ec->read_buf);
+      ec->read_buf = tmp;
+      ec->read_size = ns;
+    }
+    ret = GNUNET_DISK_file_read (ec->chld_stdout,
+                                 ec->read_buf,
+                                 ec->read_size - ec->read_pos);
+    if (ret < 0)
+    {
+      if ( (EAGAIN != errno) &&
+           (EWOULDBLOCK != errno) &&
+           (EINTR != errno) )
+      {
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                             "read");
+        return;
+      }
+      break;
+    }
+    if (0 == ret)
+    {
+      /* regular end of stream, good! */
+      return;
+    }
+    GNUNET_assert (ec->read_size >= ec->read_pos + ret);
+    ec->read_pos += ret;
+  }
+  ec->read_task
+    = GNUNET_SCHEDULER_add_read_file (
+        GNUNET_TIME_UNIT_FOREVER_REL,
+        ec->chld_stdout,
+        &read_cb,
+        ec);
+}
+
+
+/**
+ * Function called when we can write more data to
+ * the child process.
+ *
+ * @param cls our `struct TALER_JSON_ExternalConversion *`
+ */
+static void
+write_cb (void *cls)
+{
+  struct TALER_JSON_ExternalConversion *ec = cls;
+  ssize_t ret;
+
+  ec->write_task = NULL;
+  while (ec->write_size > ec->write_pos)
+  {
+    ret = GNUNET_DISK_file_write (ec->chld_stdin,
+                                  ec->write_buf + ec->write_pos,
+                                  ec->write_size - ec->write_pos);
+    if (ret < 0)
+    {
+      if ( (EAGAIN != errno) &&
+           (EINTR != errno) )
+        GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
+                             "write");
+      break;
+    }
+    if (0 == ret)
+    {
+      GNUNET_break (0);
+      break;
+    }
+    GNUNET_assert (ec->write_size >= ec->write_pos + ret);
+    ec->write_pos += ret;
+  }
+  if ( (ec->write_size > ec->write_pos) &&
+       ( (EAGAIN == errno) ||
+         (EWOULDBLOCK == errno) ||
+         (EINTR == errno) ) )
+  {
+    ec->write_task
+      = GNUNET_SCHEDULER_add_write_file (
+          GNUNET_TIME_UNIT_FOREVER_REL,
+          ec->chld_stdin,
+          &write_cb,
+          ec);
+  }
+  else
+  {
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_file_close (ec->chld_stdin));
+    ec->chld_stdin = NULL;
+  }
+}
+
+
+/**
+ * Defines a GNUNET_ChildCompletedCallback which is sent back
+ * upon death or completion of a child process.
+ *
+ * @param cls handle for the callback
+ * @param type type of the process
+ * @param exit_code status code of the process
+ *
+ */
+static void
+child_done_cb (void *cls,
+               enum GNUNET_OS_ProcessStatusType type,
+               long unsigned int exit_code)
+{
+  struct TALER_JSON_ExternalConversion *ec = cls;
+  json_t *j = NULL;
+  json_error_t err;
+
+  ec->cwh = NULL;
+  if (NULL != ec->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (ec->read_task);
+    /* We could get the process termination notification before having drained
+       the read buffer. So drain it now, just in case. */
+    read_cb (ec);
+  }
+  if (NULL != ec->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (ec->read_task);
+    ec->read_task = NULL;
+  }
+  GNUNET_OS_process_destroy (ec->helper);
+  ec->helper = NULL;
+  if (0 != ec->read_pos)
+  {
+    j = json_loadb (ec->read_buf,
+                    ec->read_pos,
+                    JSON_REJECT_DUPLICATES,
+                    &err);
+    if (NULL == j)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Failed to parse JSON from helper at %d: %s\n",
+                  err.position,
+                  err.text);
+    }
+  }
+  ec->cb (ec->cb_cls,
+          type,
+          exit_code,
+          j);
+  json_decref (j);
+  TALER_JSON_external_conversion_stop (ec);
+}
+
+
+struct TALER_JSON_ExternalConversion *
+TALER_JSON_external_conversion_start (const json_t *input,
+                                      TALER_JSON_JsonCallback cb,
+                                      void *cb_cls,
+                                      const char *binary,
+                                      ...)
+{
+  struct TALER_JSON_ExternalConversion *ec;
+  struct GNUNET_DISK_PipeHandle *pipe_stdin;
+  struct GNUNET_DISK_PipeHandle *pipe_stdout;
+  va_list ap;
+
+  ec = GNUNET_new (struct TALER_JSON_ExternalConversion);
+  ec->cb = cb;
+  ec->cb_cls = cb_cls;
+  pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
+  GNUNET_assert (NULL != pipe_stdin);
+  pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
+  GNUNET_assert (NULL != pipe_stdout);
+  va_start (ap,
+            binary);
+  ec->helper = GNUNET_OS_start_process_va (GNUNET_OS_INHERIT_STD_ERR,
+                                           pipe_stdin,
+                                           pipe_stdout,
+                                           NULL,
+                                           binary,
+                                           ap);
+  va_end (ap);
+  if (NULL == ec->helper)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to run conversion helper `%s'\n",
+                binary);
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_pipe_close (pipe_stdin));
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_pipe_close (pipe_stdout));
+    GNUNET_free (ec);
+    return NULL;
+  }
+  ec->chld_stdin =
+    GNUNET_DISK_pipe_detach_end (pipe_stdin,
+                                 GNUNET_DISK_PIPE_END_WRITE);
+  ec->chld_stdout =
+    GNUNET_DISK_pipe_detach_end (pipe_stdout,
+                                 GNUNET_DISK_PIPE_END_READ);
+  GNUNET_break (GNUNET_OK ==
+                GNUNET_DISK_pipe_close (pipe_stdin));
+  GNUNET_break (GNUNET_OK ==
+                GNUNET_DISK_pipe_close (pipe_stdout));
+  ec->write_buf = json_dumps (input, JSON_COMPACT);
+  ec->write_size = strlen (ec->write_buf);
+  ec->read_task
+    = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
+                                      ec->chld_stdout,
+                                      &read_cb,
+                                      ec);
+  ec->write_task
+    = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
+                                       ec->chld_stdin,
+                                       &write_cb,
+                                       ec);
+  ec->cwh = GNUNET_wait_child (ec->helper,
+                               &child_done_cb,
+                               ec);
+  return ec;
+}
+
+
+void
+TALER_JSON_external_conversion_stop (
+  struct TALER_JSON_ExternalConversion *ec)
+{
+  if (NULL != ec->cwh)
+  {
+    GNUNET_wait_child_cancel (ec->cwh);
+    ec->cwh = NULL;
+  }
+  if (NULL != ec->helper)
+  {
+    GNUNET_break (0 ==
+                  GNUNET_OS_process_kill (ec->helper,
+                                          SIGKILL));
+    GNUNET_OS_process_destroy (ec->helper);
+  }
+  if (NULL != ec->read_task)
+  {
+    GNUNET_SCHEDULER_cancel (ec->read_task);
+    ec->read_task = NULL;
+  }
+  if (NULL != ec->write_task)
+  {
+    GNUNET_SCHEDULER_cancel (ec->write_task);
+    ec->write_task = NULL;
+  }
+  if (NULL != ec->chld_stdin)
+  {
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_file_close (ec->chld_stdin));
+    ec->chld_stdin = NULL;
+  }
+  if (NULL != ec->chld_stdout)
+  {
+    GNUNET_break (GNUNET_OK ==
+                  GNUNET_DISK_file_close (ec->chld_stdout));
+    ec->chld_stdout = NULL;
+  }
+  GNUNET_free (ec->read_buf);
+  free (ec->write_buf);
+  GNUNET_free (ec);
+}
diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c
new file mode 100644
index 0000000..a238d53
--- /dev/null
+++ b/src/util/crypto_confirmation.c
@@ -0,0 +1,286 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file util/crypto_confirmation.c
+ * @brief confirmation computation
+ * @author Christian Grothoff
+ * @author Priscilla Huang
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_mhd_lib.h"
+#include <gnunet/gnunet_db_lib.h>
+#include <gcrypt.h>
+
+/**
+ * How long is a TOTP code valid?
+ */
+#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_SECONDS, 30)
+
+/**
+ * Range of time we allow (plus-minus).
+ */
+#define TIME_INTERVAL_RANGE 2
+
+
+/**
+ * Compute TOTP code at current time with offset
+ * @a time_off for the @a key.
+ *
+ * @param ts current time
+ * @param time_off offset to apply when computing the code
+ * @param key pos_key in binary
+ * @param key_size number of bytes in @a key
+ */
+static uint64_t
+compute_totp (struct GNUNET_TIME_Timestamp ts,
+              int time_off,
+              const void *key,
+              size_t key_size)
+{
+  struct GNUNET_TIME_Absolute now;
+  time_t t;
+  uint64_t ctr;
+  uint8_t hmac[20]; /* SHA1: 20 bytes */
+
+  now = ts.abs_time;
+  while (time_off < 0)
+  {
+    now = GNUNET_TIME_absolute_subtract (now,
+                                         TOTP_VALIDITY_PERIOD);
+    time_off++;
+  }
+  while (time_off > 0)
+  {
+    now = GNUNET_TIME_absolute_add (now,
+                                    TOTP_VALIDITY_PERIOD);
+    time_off--;
+  }
+  t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us;
+  ctr = GNUNET_htonll (t / 30LLU);
+
+  {
+    gcry_md_hd_t md;
+    const unsigned char *mc;
+
+    GNUNET_assert (GPG_ERR_NO_ERROR ==
+                   gcry_md_open (&md,
+                                 GCRY_MD_SHA1,
+                                 GCRY_MD_FLAG_HMAC));
+    gcry_md_setkey (md,
+                    key,
+                    key_size);
+    gcry_md_write (md,
+                   &ctr,
+                   sizeof (ctr));
+    mc = gcry_md_read (md,
+                       GCRY_MD_SHA1);
+    GNUNET_assert (NULL != mc);
+    GNUNET_memcpy (hmac,
+                   mc,
+                   sizeof (hmac));
+    gcry_md_close (md);
+  }
+
+  {
+    uint32_t code = 0;
+    int offset;
+
+    offset = hmac[sizeof (hmac) - 1] & 0x0f;
+    for (int count = 0; count < 4; count++)
+      code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count);
+    code &= 0x7fffffff;
+    /* always use 8 digits (maximum) */
+    code = code % 100000000;
+    return code;
+  }
+}
+
+
+/**
+ * Compute RFC 3548 base32 decoding of @a val and write
+ * result to @a udata.
+ *
+ * @param val value to decode
+ * @param val_size number of bytes in @a val
+ * @param key is the val in bits
+ * @param key_len is the size of @a key
+ */
+static int
+base32decode (const char *val,
+              size_t val_size,
+              void *key,
+              size_t key_len)
+{
+  /**
+   * 32 characters for decoding, using RFC 3548.
+   */
+  static const char *decTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+  unsigned char *udata = key;
+  unsigned int wpos = 0;
+  unsigned int rpos = 0;
+  unsigned int bits = 0;
+  unsigned int vbit = 0;
+
+  while ((rpos < val_size) || (vbit >= 8))
+  {
+    if ((rpos < val_size) && (vbit < 8))
+    {
+      char c = val[rpos++];
+      if (c == '=')   // padding character
+      {
+        break;
+      }
+      const char *p = strchr (decTable__, toupper (c));
+      if (! p)
+      { // invalid character
+        return -1;
+      }
+      bits = (bits << 5) | (p - decTable__);
+      vbit += 5;
+    }
+    if (vbit >= 8)
+    {
+      udata[wpos++] = (bits >> (vbit - 8)) & 0xFF;
+      vbit -= 8;
+    }
+  }
+  return wpos;
+}
+
+
+/**
+ * @brief Builds POS confirmation to verify payment.
+ *
+ * @param h_key opaque key for the totp operation
+ * @param h_key_len size of h_key in bytes
+ * @param ts current time
+ * @return Token on success, NULL of failure
+ */
+static char *
+executive_totp (void *h_key,
+                size_t h_key_len,
+                struct GNUNET_TIME_Timestamp ts)
+{
+  uint64_t code; /* totp code */
+  char *ret;
+  ret = NULL;
+
+  for (int i = -TIME_INTERVAL_RANGE; i<= TIME_INTERVAL_RANGE; i++)
+  {
+    code = compute_totp (ts,
+                         i,
+                         h_key,
+                         h_key_len);
+    if (NULL == ret)
+    {
+      GNUNET_asprintf (&ret,
+                       "%08llu",
+                       (unsigned long long) code);
+    }
+    else
+    {
+      char *tmp;
+
+      GNUNET_asprintf (&tmp,
+                       "%s\n%08llu",
+                       ret,
+                       (unsigned long long) code);
+      GNUNET_free (ret);
+      ret = tmp;
+    }
+  }
+  return ret;
+
+}
+
+
+char *
+TALER_build_pos_confirmation (const char *pos_key,
+                              enum TALER_MerchantConfirmationAlgorithm pos_alg,
+                              const struct TALER_Amount *total,
+                              struct GNUNET_TIME_Timestamp ts)
+{
+  size_t pos_key_length = strlen (pos_key);
+  void *key; /* pos_key in binary */
+  size_t key_len; /* length of the key */
+  char *ret;
+  int dret;
+
+  if (TALER_MCA_NONE == pos_alg)
+    return NULL;
+  key_len = pos_key_length * 5 / 8;
+  key = GNUNET_malloc (key_len);
+  dret = base32decode (pos_key,
+                       pos_key_length,
+                       key,
+                       key_len);
+  if (-1 == dret)
+  {
+    GNUNET_free (key);
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  GNUNET_assert (dret <= key_len);
+  key_len = (size_t) dret;
+  switch (pos_alg)
+  {
+  case TALER_MCA_NONE:
+    GNUNET_break (0);
+    GNUNET_free (key);
+    return NULL;
+  case TALER_MCA_WITHOUT_PRICE: /* and 30s */
+    /* Return all T-OTP codes in range separated by new lines, e.g.
+       "12345678
+        24522552
+        25262425
+        42543525
+        25253552"
+    */
+    ret = executive_totp (key,
+                          key_len,
+                          ts);
+    GNUNET_free (key);
+    return ret;
+  case TALER_MCA_WITH_PRICE:
+    {
+      struct GNUNET_HashCode hkey;
+      struct TALER_AmountNBO ntotal;
+
+      TALER_amount_hton (&ntotal,
+                         total);
+      GNUNET_assert (GNUNET_YES ==
+                     GNUNET_CRYPTO_kdf (&hkey,
+                                        sizeof (hkey),
+                                        &ntotal,
+                                        sizeof (ntotal),
+                                        key,
+                                        key_len,
+                                        NULL,
+                                        0));
+      GNUNET_free (key);
+      ret = executive_totp (&hkey,
+                            sizeof(hkey),
+                            ts);
+      GNUNET_free (key);
+      return ret;
+    }
+  }
+  GNUNET_free (key);
+  GNUNET_break (0);
+  return NULL;
+}
diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c
new file mode 100644
index 0000000..bec34c9
--- /dev/null
+++ b/src/util/crypto_contract.c
@@ -0,0 +1,661 @@
+/*
+  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 util/crypto_contract.c
+ * @brief functions for encrypting and decrypting contracts for P2P payments
+ * @author Christian Grothoff <christian@grothoff.org>
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include <zlib.h>
+#include "taler_exchange_service.h"
+
+
+/**
+ * Different types of contracts supported.
+ */
+enum ContractFormats
+{
+  /**
+   * The encrypted contract represents a payment offer. The receiver
+   * can merge it into a reserve/account to accept the contract and
+   * obtain the payment.
+   */
+  TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER = 0,
+
+  /**
+   * The encrypted contract represents a payment request.
+   */
+  TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST = 1
+};
+
+
+/**
+ * Nonce used for encryption, 24 bytes.
+ */
+struct NonceP
+{
+  uint8_t nonce[crypto_secretbox_NONCEBYTES];
+};
+
+/**
+ * Specifies a key used for symmetric encryption, 32 bytes.
+ */
+struct SymKeyP
+{
+  uint32_t key[8];
+};
+
+
+/**
+ * Compute @a key.
+ *
+ * @param key_material key for calculation
+ * @param key_m_len length of key
+ * @param nonce nonce for calculation
+ * @param salt salt value for calculation
+ * @param[out] key where to write the en-/description key
+ */
+static void
+derive_key (const void *key_material,
+            size_t key_m_len,
+            const struct NonceP *nonce,
+            const char *salt,
+            struct SymKeyP *key)
+{
+  GNUNET_assert (GNUNET_YES ==
+                 GNUNET_CRYPTO_kdf (key,
+                                    sizeof (*key),
+                                    /* salt / XTS */
+                                    nonce,
+                                    sizeof (*nonce),
+                                    /* ikm */
+                                    key_material,
+                                    key_m_len,
+                                    /* info chunks */
+                                    /* The "salt" passed here is actually not 
something random,
+                                       but a protocol-specific identifier 
string.  Thus
+                                       we pass it as a context info to the 
HKDF */
+                                    salt,
+                                    strlen (salt),
+                                    NULL,
+                                    0));
+}
+
+
+/**
+ * Encryption of data.
+ *
+ * @param nonce value to use for the nonce
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to encrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param[out] res ciphertext output
+ * @param[out] res_size size of the ciphertext
+ */
+static void
+blob_encrypt (const struct NonceP *nonce,
+              const void *key,
+              size_t key_len,
+              const void *data,
+              size_t data_size,
+              const char *salt,
+              void **res,
+              size_t *res_size)
+{
+  size_t ciphertext_size;
+  struct SymKeyP skey;
+
+  derive_key (key,
+              key_len,
+              nonce,
+              salt,
+              &skey);
+  ciphertext_size = crypto_secretbox_NONCEBYTES
+                    + crypto_secretbox_MACBYTES
+                    + data_size;
+  *res_size = ciphertext_size;
+  *res = GNUNET_malloc (ciphertext_size);
+  GNUNET_memcpy (*res,
+                 nonce,
+                 crypto_secretbox_NONCEBYTES);
+  GNUNET_assert (0 ==
+                 crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES,
+                                        data,
+                                        data_size,
+                                        (void *) nonce,
+                                        (void *) &skey));
+}
+
+
+/**
+ * Decryption of data like encrypted recovery document etc.
+ *
+ * @param key key which is used to derive a key/iv pair from
+ * @param key_len length of key
+ * @param data data to decrypt
+ * @param data_size size of the data
+ * @param salt salt value which is used for key derivation
+ * @param[out] res plaintext output
+ * @param[out] res_size size of the plaintext
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+blob_decrypt (const void *key,
+              size_t key_len,
+              const void *data,
+              size_t data_size,
+              const char *salt,
+              void **res,
+              size_t *res_size)
+{
+  const struct NonceP *nonce;
+  struct SymKeyP skey;
+  size_t plaintext_size;
+
+  if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES)
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+  nonce = data;
+  derive_key (key,
+              key_len,
+              nonce,
+              salt,
+              &skey);
+  plaintext_size = data_size - (crypto_secretbox_NONCEBYTES
+                                + crypto_secretbox_MACBYTES);
+  *res = GNUNET_malloc (plaintext_size);
+  *res_size = plaintext_size;
+  if (0 != crypto_secretbox_open_easy (*res,
+                                       data + crypto_secretbox_NONCEBYTES,
+                                       data_size - crypto_secretbox_NONCEBYTES,
+                                       (void *) nonce,
+                                       (void *) &skey))
+  {
+    GNUNET_break (0);
+    GNUNET_free (*res);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Header for encrypted contracts.
+ */
+struct ContractHeaderP
+{
+  /**
+   * Type of the contract, in NBO.
+   */
+  uint32_t ctype;
+
+  /**
+   * Length of the encrypted contract, in NBO.
+   */
+  uint32_t clen;
+};
+
+
+/**
+ * Header for encrypted contracts.
+ */
+struct ContractHeaderMergeP
+{
+  /**
+   * Generic header.
+   */
+  struct ContractHeaderP header;
+
+  /**
+   * Private key with the merge capability.
+   */
+  struct TALER_PurseMergePrivateKeyP merge_priv;
+};
+
+
+/**
+ * Salt we use when encrypting contracts for merge.
+ */
+#define MERGE_SALT "p2p-merge-contract"
+
+
+void
+TALER_CRYPTO_contract_encrypt_for_merge (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  const json_t *contract_terms,
+  void **econtract,
+  size_t *econtract_size)
+{
+  struct GNUNET_HashCode key;
+  char *cstr;
+  size_t clen;
+  void *xbuf;
+  struct ContractHeaderMergeP *hdr;
+  struct NonceP nonce;
+  uLongf cbuf_size;
+  int ret;
+
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+                                           &purse_pub->eddsa_pub,
+                                           &key));
+  cstr = json_dumps (contract_terms,
+                     JSON_COMPACT | JSON_SORT_KEYS);
+  clen = strlen (cstr);
+  cbuf_size = compressBound (clen);
+  xbuf = GNUNET_malloc (cbuf_size);
+  ret = compress (xbuf,
+                  &cbuf_size,
+                  (const Bytef *) cstr,
+                  clen);
+  GNUNET_assert (Z_OK == ret);
+  free (cstr);
+  hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
+  hdr->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER);
+  hdr->header.clen = htonl ((uint32_t) clen);
+  hdr->merge_priv = *merge_priv;
+  GNUNET_memcpy (&hdr[1],
+                 xbuf,
+                 cbuf_size);
+  GNUNET_free (xbuf);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &nonce,
+                              sizeof (nonce));
+  blob_encrypt (&nonce,
+                &key,
+                sizeof (key),
+                hdr,
+                sizeof (*hdr) + cbuf_size,
+                MERGE_SALT,
+                econtract,
+                econtract_size);
+  GNUNET_free (hdr);
+}
+
+
+json_t *
+TALER_CRYPTO_contract_decrypt_for_merge (
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const void *econtract,
+  size_t econtract_size,
+  struct TALER_PurseMergePrivateKeyP *merge_priv)
+{
+  struct GNUNET_HashCode key;
+  void *xhdr;
+  size_t hdr_size;
+  const struct ContractHeaderMergeP *hdr;
+  char *cstr;
+  uLongf clen;
+  json_error_t json_error;
+  json_t *ret;
+
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+                                &purse_pub->eddsa_pub,
+                                &key))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  if (GNUNET_OK !=
+      blob_decrypt (&key,
+                    sizeof (key),
+                    econtract,
+                    econtract_size,
+                    MERGE_SALT,
+                    &xhdr,
+                    &hdr_size))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  if (hdr_size < sizeof (*hdr))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  hdr = xhdr;
+  if (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER != ntohl (hdr->header.ctype))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  clen = ntohl (hdr->header.clen);
+  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  cstr = GNUNET_malloc (clen + 1);
+  if (Z_OK !=
+      uncompress ((Bytef *) cstr,
+                  &clen,
+                  (const Bytef *) &hdr[1],
+                  hdr_size - sizeof (*hdr)))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  *merge_priv = hdr->merge_priv;
+  GNUNET_free (xhdr);
+  ret = json_loadb ((char *) cstr,
+                    clen,
+                    JSON_DECODE_ANY,
+                    &json_error);
+  if (NULL == ret)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    return NULL;
+  }
+  GNUNET_free (cstr);
+  return ret;
+}
+
+
+/**
+ * Salt we use when encrypting contracts for merge.
+ */
+#define DEPOSIT_SALT "p2p-deposit-contract"
+
+
+void
+TALER_CRYPTO_contract_encrypt_for_deposit (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const json_t *contract_terms,
+  void **econtract,
+  size_t *econtract_size)
+{
+  struct GNUNET_HashCode key;
+  char *cstr;
+  size_t clen;
+  void *xbuf;
+  struct ContractHeaderP *hdr;
+  struct NonceP nonce;
+  uLongf cbuf_size;
+  int ret;
+  void *xecontract;
+  size_t xecontract_size;
+
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+                                           &purse_pub->eddsa_pub,
+                                           &key));
+  cstr = json_dumps (contract_terms,
+                     JSON_COMPACT | JSON_SORT_KEYS);
+  GNUNET_assert (NULL != cstr);
+  clen = strlen (cstr);
+  cbuf_size = compressBound (clen);
+  xbuf = GNUNET_malloc (cbuf_size);
+  ret = compress (xbuf,
+                  &cbuf_size,
+                  (const Bytef *) cstr,
+                  clen);
+  GNUNET_assert (Z_OK == ret);
+  free (cstr);
+  hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size);
+  hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST);
+  hdr->clen = htonl ((uint32_t) clen);
+  GNUNET_memcpy (&hdr[1],
+                 xbuf,
+                 cbuf_size);
+  GNUNET_free (xbuf);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &nonce,
+                              sizeof (nonce));
+  blob_encrypt (&nonce,
+                &key,
+                sizeof (key),
+                hdr,
+                sizeof (*hdr) + cbuf_size,
+                DEPOSIT_SALT,
+                &xecontract,
+                &xecontract_size);
+  GNUNET_free (hdr);
+  /* prepend purse_pub */
+  *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub));
+  GNUNET_memcpy (*econtract,
+                 purse_pub,
+                 sizeof (*purse_pub));
+  GNUNET_memcpy (sizeof (*purse_pub) + *econtract,
+                 xecontract,
+                 xecontract_size);
+  *econtract_size = xecontract_size + sizeof (*purse_pub);
+  GNUNET_free (xecontract);
+}
+
+
+json_t *
+TALER_CRYPTO_contract_decrypt_for_deposit (
+  const struct TALER_ContractDiffiePrivateP *contract_priv,
+  const void *econtract,
+  size_t econtract_size)
+{
+  const struct TALER_PurseContractPublicKeyP *purse_pub = econtract;
+
+  if (econtract_size < sizeof (*purse_pub))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  struct GNUNET_HashCode key;
+  void *xhdr;
+  size_t hdr_size;
+  const struct ContractHeaderP *hdr;
+  char *cstr;
+  uLongf clen;
+  json_error_t json_error;
+  json_t *ret;
+
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv,
+                                &purse_pub->eddsa_pub,
+                                &key))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  econtract += sizeof (*purse_pub);
+  econtract_size -= sizeof (*purse_pub);
+  if (GNUNET_OK !=
+      blob_decrypt (&key,
+                    sizeof (key),
+                    econtract,
+                    econtract_size,
+                    DEPOSIT_SALT,
+                    &xhdr,
+                    &hdr_size))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  if (hdr_size < sizeof (*hdr))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  hdr = xhdr;
+  if (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST != ntohl (hdr->ctype))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  clen = ntohl (hdr->clen);
+  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  cstr = GNUNET_malloc (clen + 1);
+  if (Z_OK !=
+      uncompress ((Bytef *) cstr,
+                  &clen,
+                  (const Bytef *) &hdr[1],
+                  hdr_size - sizeof (*hdr)))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  GNUNET_free (xhdr);
+  ret = json_loadb ((char *) cstr,
+                    clen,
+                    JSON_DECODE_ANY,
+                    &json_error);
+  if (NULL == ret)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    return NULL;
+  }
+  GNUNET_free (cstr);
+  return ret;
+}
+
+
+/**
+ * Salt we use when encrypting KYC attributes.
+ */
+#define ATTRIBUTE_SALT "kyc-attributes"
+
+
+void
+TALER_CRYPTO_kyc_attributes_encrypt (
+  const struct TALER_AttributeEncryptionKeyP *key,
+  const json_t *attr,
+  void **enc_attr,
+  size_t *enc_attr_size)
+{
+  uLongf cbuf_size;
+  char *cstr;
+  uLongf clen;
+  void *xbuf;
+  int ret;
+  uint32_t belen;
+  struct NonceP nonce;
+
+  cstr = json_dumps (attr,
+                     JSON_COMPACT | JSON_SORT_KEYS);
+  GNUNET_assert (NULL != cstr);
+  clen = strlen (cstr);
+  GNUNET_assert (clen <= UINT32_MAX);
+  cbuf_size = compressBound (clen);
+  xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t));
+  belen = htonl ((uint32_t) clen);
+  GNUNET_memcpy (xbuf,
+                 &belen,
+                 sizeof (belen));
+  ret = compress (xbuf + 4,
+                  &cbuf_size,
+                  (const Bytef *) cstr,
+                  clen);
+  GNUNET_assert (Z_OK == ret);
+  free (cstr);
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+                              &nonce,
+                              sizeof (nonce));
+  blob_encrypt (&nonce,
+                key,
+                sizeof (*key),
+                xbuf,
+                cbuf_size + sizeof (uint32_t),
+                ATTRIBUTE_SALT,
+                enc_attr,
+                enc_attr_size);
+  GNUNET_free (xbuf);
+}
+
+
+json_t *
+TALER_CRYPTO_kyc_attributes_decrypt (
+  const struct TALER_AttributeEncryptionKeyP *key,
+  const void *enc_attr,
+  size_t enc_attr_size)
+{
+  void *xhdr;
+  size_t hdr_size;
+  char *cstr;
+  uLongf clen;
+  json_error_t json_error;
+  json_t *ret;
+  uint32_t belen;
+
+  if (GNUNET_OK !=
+      blob_decrypt (key,
+                    sizeof (*key),
+                    enc_attr,
+                    enc_attr_size,
+                    ATTRIBUTE_SALT,
+                    &xhdr,
+                    &hdr_size))
+  {
+    GNUNET_break_op (0);
+    return NULL;
+  }
+  GNUNET_memcpy (&belen,
+                 xhdr,
+                 sizeof (belen));
+  clen = ntohl (belen);
+  if (clen >= GNUNET_MAX_MALLOC_CHECKED)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  cstr = GNUNET_malloc (clen + 1);
+  if (Z_OK !=
+      uncompress ((Bytef *) cstr,
+                  &clen,
+                  (const Bytef *) (xhdr + sizeof (uint32_t)),
+                  hdr_size - sizeof (uint32_t)))
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    GNUNET_free (xhdr);
+    return NULL;
+  }
+  GNUNET_free (xhdr);
+  ret = json_loadb ((char *) cstr,
+                    clen,
+                    JSON_DECODE_ANY,
+                    &json_error);
+  if (NULL == ret)
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (cstr);
+    return NULL;
+  }
+  GNUNET_free (cstr);
+  return ret;
+}
diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c
new file mode 100644
index 0000000..3aa464a
--- /dev/null
+++ b/src/util/exchange_signatures.c
@@ -0,0 +1,1877 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2021, 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 exchange_signatures.c
+ * @brief Utility functions for Taler security module signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a confirmation
+ * from the exchange that a deposit request succeeded.
+ */
+struct TALER_DepositConfirmationPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT.  Signed
+   * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the contract for which this deposit is made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+  /**
+   * Hash over the optional policy extension of the deposit, 0 if there
+   * was no policy.
+   */
+  struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED;
+
+  /**
+   * Time when this confirmation was generated / when the exchange received
+   * the deposit request.
+   */
+  struct GNUNET_TIME_TimestampNBO exchange_timestamp;
+
+  /**
+   * By when does the exchange expect to pay the merchant
+   * (as per the merchant's request).
+   */
+  struct GNUNET_TIME_TimestampNBO wire_deadline;
+
+  /**
+   * How much time does the @e merchant have to issue a refund
+   * request?  Zero if refunds are not allowed.  After this time, the
+   * coin cannot be refunded.  Note that the wire transfer will not be
+   * performed by the exchange until the refund deadline.  This value
+   * is taken from the original deposit request.
+   */
+  struct GNUNET_TIME_TimestampNBO refund_deadline;
+
+  /**
+   * Amount to be deposited, excluding fee.  Calculated from the
+   * amount with fee and the fee from the deposit request.
+   */
+  struct TALER_AmountNBO amount_without_fee;
+
+  /**
+   * The public key of the coin that was deposited.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * The Merchant's public key.  Allows the merchant to later refund
+   * the transaction or to inquire about the wire transfer identifier.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_deposit_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Timestamp wire_deadline,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_DepositConfirmationPS dcs = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
+    .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)),
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire,
+    .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
+    .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
+    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+    .coin_pub = *coin_pub,
+    .merchant_pub = *merchant_pub
+  };
+
+  if (NULL != h_policy)
+    dcs.h_policy = *h_policy;
+  TALER_amount_hton (&dcs.amount_without_fee,
+                     amount_without_fee);
+  return scb (&dcs.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_deposit_confirmation_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  struct GNUNET_TIME_Timestamp exchange_timestamp,
+  struct GNUNET_TIME_Timestamp wire_deadline,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct TALER_DepositConfirmationPS dcs = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT),
+    .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)),
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire,
+    .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp),
+    .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline),
+    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+    .coin_pub = *coin_pub,
+    .merchant_pub = *merchant_pub
+  };
+
+  if (NULL != h_policy)
+    dcs.h_policy = *h_policy;
+  TALER_amount_hton (&dcs.amount_without_fee,
+                     amount_without_fee);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT,
+                                  &dcs,
+                                  &exchange_sig->eddsa_signature,
+                                  &exchange_pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to refund
+ * a coin into the account of the customer.
+ */
+struct TALER_RefundConfirmationPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the proposal data to identify the contract
+   * which is being refunded.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * The Merchant's public key.  Allows the merchant to later refund
+   * the transaction or to inquire about the wire transfer identifier.
+   */
+  struct TALER_MerchantPublicKeyP merchant;
+
+  /**
+   * Merchant-generated transaction ID for the refund.
+   */
+  uint64_t rtransaction_id GNUNET_PACKED;
+
+  /**
+   * Amount to be refunded, including refund fee charged by the
+   * exchange to the customer.
+   */
+  struct TALER_AmountNBO refund_amount;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_refund_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *refund_amount,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RefundConfirmationPS rc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+    .purpose.size = htonl (sizeof (rc)),
+    .h_contract_terms = *h_contract_terms,
+    .coin_pub = *coin_pub,
+    .merchant = *merchant,
+    .rtransaction_id = GNUNET_htonll (rtransaction_id)
+  };
+
+  TALER_amount_hton (&rc.refund_amount,
+                     refund_amount);
+  return scb (&rc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_refund_confirmation_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPublicKeyP *merchant,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *refund_amount,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RefundConfirmationPS rc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+    .purpose.size = htonl (sizeof (rc)),
+    .h_contract_terms = *h_contract_terms,
+    .coin_pub = *coin_pub,
+    .merchant = *merchant,
+    .rtransaction_id = GNUNET_htonll (rtransaction_id)
+  };
+
+  TALER_amount_hton (&rc.refund_amount,
+                     refund_amount);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
+                                  &rc,
+                                  &sig->eddsa_signature,
+                                  &pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format of the block signed by the Exchange in response to a 
successful
+ * "/refresh/melt" request.  Hereby the exchange affirms that all of the
+ * coins were successfully melted.  This also commits the exchange to a
+ * particular index to not be revealed during the refresh.
+ */
+struct TALER_RefreshMeltConfirmationPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT.   Signed
+   * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Commitment made in the /refresh/melt.
+   */
+  struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
+
+  /**
+   * Index that the client will not have to reveal, in NBO.
+   * Must be smaller than #TALER_CNC_KAPPA.
+   */
+  uint32_t noreveal_index GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_melt_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_RefreshCommitmentP *rc,
+  uint32_t noreveal_index,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RefreshMeltConfirmationPS confirm = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT),
+    .purpose.size = htonl (sizeof (confirm)),
+    .rc = *rc,
+    .noreveal_index = htonl (noreveal_index)
+  };
+
+  return scb (&confirm.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_melt_confirmation_verify (
+  const struct TALER_RefreshCommitmentP *rc,
+  uint32_t noreveal_index,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct TALER_RefreshMeltConfirmationPS confirm = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT),
+    .purpose.size = htonl (sizeof (confirm)),
+    .rc = *rc,
+    .noreveal_index = htonl (noreveal_index)
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT,
+                                &confirm,
+                                &exchange_sig->eddsa_signature,
+                                &exchange_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format of the block signed by the Exchange in response to a
+ * successful "/reserves/$RESERVE_PUB/age-withdraw" request.  Hereby the
+ * exchange affirms that the commitment along with the maximum age group and
+ * the amount were accepted.  This also commits the exchange to a particular
+ * index to not be revealed during the reveal.
+ */
+struct TALER_AgeWithdrawConfirmationPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW.   Signed by a
+   * `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Commitment made in the /reserves/$RESERVE_PUB/age-withdraw.
+   */
+  struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED;
+
+  /**
+   * Index that the client will not have to reveal, in NBO.
+   * Must be smaller than #TALER_CNC_KAPPA.
+   */
+  uint32_t noreveal_index GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+enum TALER_ErrorCode
+TALER_exchange_online_age_withdraw_confirmation_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  uint32_t noreveal_index,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+
+  struct TALER_AgeWithdrawConfirmationPS confirm = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW),
+    .purpose.size = htonl (sizeof (confirm)),
+    .h_commitment = *h_commitment,
+    .noreveal_index = htonl (noreveal_index)
+  };
+
+  return scb (&confirm.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_age_withdraw_confirmation_verify (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  uint32_t noreveal_index,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct TALER_AgeWithdrawConfirmationPS confirm = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW),
+    .purpose.size = htonl (sizeof (confirm)),
+    .h_commitment = *h_commitment,
+    .noreveal_index = htonl (noreveal_index)
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (
+        TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW,
+        &confirm,
+        &exchange_sig->eddsa_signature,
+        &exchange_pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/* TODO:oec: add signature for age-withdraw, age-reveal */
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange over the full set of keys, used
+ * to detect cheating exchanges that give out different sets to
+ * different users.
+ */
+struct TALER_ExchangeKeySetPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_KEY_SET.   Signed
+   * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the key set issue.
+   */
+  struct GNUNET_TIME_TimestampNBO list_issue_date;
+
+  /**
+   * Hash over the various denomination signing keys returned.
+   */
+  struct GNUNET_HashCode hc GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_key_set_sign (
+  TALER_ExchangeSignCallback2 scb,
+  void *cls,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct GNUNET_HashCode *hc,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeKeySetPS ks = {
+    .purpose.size = htonl (sizeof (ks)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
+    .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp),
+    .hc = *hc
+  };
+
+  return scb (cls,
+              &ks.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_key_set_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct GNUNET_HashCode *hc,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeKeySetPS ks = {
+    .purpose.size = htonl (sizeof (ks)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET),
+    .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp),
+    .hc = *hc
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET,
+                                &ks,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature by which an exchange affirms that an account
+ * successfully passed the KYC checks.
+ */
+struct TALER_ExchangeAccountSetupSuccessPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS.  Signed by a
+   * `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the payto for which the signature was made.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Hash over details on *which* KYC obligations were discharged!
+   */
+  struct GNUNET_HashCode h_kyc;
+
+  /**
+   * When was the signature made.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_account_setup_success_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_PaytoHashP *h_payto,
+  const json_t *kyc,
+  struct GNUNET_TIME_Timestamp timestamp,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = {
+    .purpose.size = htonl (sizeof (kyc_purpose)),
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
+    .h_payto = *h_payto,
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+  };
+
+  TALER_json_hash (kyc,
+                   &kyc_purpose.h_kyc);
+  return scb (&kyc_purpose.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_account_setup_success_verify (
+  const struct TALER_PaytoHashP *h_payto,
+  const json_t *kyc,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = {
+    .purpose.size = htonl (sizeof (kyc_purpose)),
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS),
+    .h_payto = *h_payto,
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+  };
+
+  TALER_json_hash (kyc,
+                   &kyc_purpose.h_kyc);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS,
+                                &kyc_purpose,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format internally used for packing the detailed information
+ * to generate the signature for /track/transfer signatures.
+ */
+struct TALER_WireDepositDetailP
+{
+
+  /**
+   * Hash of the contract
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Time when the wire transfer was performed by the exchange.
+   */
+  struct GNUNET_TIME_TimestampNBO execution_time;
+
+  /**
+   * Coin's public key.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Total value of the coin.
+   */
+  struct TALER_AmountNBO deposit_value;
+
+  /**
+   * Fees charged by the exchange for the deposit.
+   */
+  struct TALER_AmountNBO deposit_fee;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_online_wire_deposit_append (
+  struct GNUNET_HashContext *hash_context,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *deposit_value,
+  const struct TALER_Amount *deposit_fee)
+{
+  struct TALER_WireDepositDetailP dd = {
+    .h_contract_terms = *h_contract_terms,
+    .execution_time = GNUNET_TIME_timestamp_hton (execution_time),
+    .coin_pub = *coin_pub
+  };
+  TALER_amount_hton (&dd.deposit_value,
+                     deposit_value);
+  TALER_amount_hton (&dd.deposit_fee,
+                     deposit_fee);
+  GNUNET_CRYPTO_hash_context_read (hash_context,
+                                   &dd,
+                                   sizeof (dd));
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature for /wire/deposit
+ * replies.
+ */
+struct TALER_WireDepositDataPS
+{
+  /**
+   * Purpose header for the signature over the contract with
+   * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Total amount that was transferred.
+   */
+  struct TALER_AmountNBO total;
+
+  /**
+   * Wire fee that was charged.
+   */
+  struct TALER_AmountNBO wire_fee;
+
+  /**
+   * Public key of the merchant (for all aggregated transactions).
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+
+  /**
+   * Hash of bank account of the merchant.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Hash of the individual deposits that were aggregated,
+   * each in the format of a `struct TALER_WireDepositDetailP`.
+   */
+  struct GNUNET_HashCode h_details;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_wire_deposit_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_Amount *total,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const char *payto,
+  const struct GNUNET_HashCode *h_details,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_WireDepositDataPS wdp = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT),
+    .purpose.size = htonl (sizeof (wdp)),
+    .merchant_pub = *merchant_pub,
+    .h_details = *h_details
+  };
+
+  TALER_amount_hton (&wdp.total,
+                     total);
+  TALER_amount_hton (&wdp.wire_fee,
+                     wire_fee);
+  TALER_payto_hash (payto,
+                    &wdp.h_payto);
+  return scb (&wdp.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_wire_deposit_verify (
+  const struct TALER_Amount *total,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct GNUNET_HashCode *h_details,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_WireDepositDataPS wdp = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT),
+    .purpose.size = htonl (sizeof (wdp)),
+    .merchant_pub = *merchant_pub,
+    .h_details = *h_details,
+    .h_payto = *h_payto
+  };
+
+  TALER_amount_hton (&wdp.total,
+                     total);
+  TALER_amount_hton (&wdp.wire_fee,
+                     wire_fee);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT,
+                                &wdp,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Details affirmed by the exchange about a wire transfer the exchange
+ * claims to have done with respect to a deposit operation.
+ */
+struct TALER_ConfirmWirePS
+{
+  /**
+   * Purpose header for the signature over the contract with
+   * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+  /**
+   * Hash over the contract for which this deposit is made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * Raw value (binary encoding) of the wire transfer subject.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * When did the exchange execute this transfer? Note that the
+   * timestamp may not be exactly the same on the wire, i.e.
+   * because the wire has a different timezone or resolution.
+   */
+  struct GNUNET_TIME_TimestampNBO execution_time;
+
+  /**
+   * The contribution of @e coin_pub to the total transfer volume.
+   * This is the value of the deposit minus the fee.
+   */
+  struct TALER_AmountNBO coin_contribution;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_wire_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_Amount *coin_contribution,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+
+{
+  struct TALER_ConfirmWirePS cw = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE),
+    .purpose.size = htonl (sizeof (cw)),
+    .h_wire = *h_wire,
+    .h_contract_terms = *h_contract_terms,
+    .wtid = *wtid,
+    .coin_pub = *coin_pub,
+    .execution_time = GNUNET_TIME_timestamp_hton (execution_time)
+  };
+
+  TALER_amount_hton (&cw.coin_contribution,
+                     coin_contribution);
+  return scb (&cw.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_wire_verify (
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  struct GNUNET_TIME_Timestamp execution_time,
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ConfirmWirePS cw = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE),
+    .purpose.size = htonl (sizeof (cw)),
+    .h_wire = *h_wire,
+    .h_contract_terms = *h_contract_terms,
+    .wtid = *wtid,
+    .coin_pub = *coin_pub,
+    .execution_time = GNUNET_TIME_timestamp_hton (execution_time)
+  };
+
+  TALER_amount_hton (&cw.coin_contribution,
+                     coin_contribution);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE,
+                                &cw,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it will
+ * refund a coin as part of the emergency /recoup
+ * protocol.  The recoup will go back to the bank
+ * account that created the reserve.
+ */
+struct TALER_RecoupConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange receive the recoup request?
+   * Indirectly determines when the wire transfer is (likely)
+   * to happen.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+  /**
+   * How much of the coin's value will the exchange transfer?
+   * (Needed in case the coin was partially spent.)
+   */
+  struct TALER_AmountNBO recoup_amount;
+
+  /**
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Public key of the reserve that will receive the recoup.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RecoupConfirmationPS pc = {
+    .purpose.size = htonl (sizeof (pc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .coin_pub = *coin_pub,
+    .reserve_pub = *reserve_pub
+  };
+
+  TALER_amount_hton (&pc.recoup_amount,
+                     recoup_amount);
+  return scb (&pc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RecoupConfirmationPS pc = {
+    .purpose.size = htonl (sizeof (pc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .coin_pub = *coin_pub,
+    .reserve_pub = *reserve_pub
+  };
+
+  TALER_amount_hton (&pc.recoup_amount,
+                     recoup_amount);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP,
+                                &pc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it will refund a refreshed coin
+ * as part of the emergency /recoup protocol.  The recoup will go back to the
+ * old coin's balance.
+ */
+struct TALER_RecoupRefreshConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange receive the recoup request?
+   * Indirectly determines when the wire transfer is (likely)
+   * to happen.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+  /**
+   * How much of the coin's value will the exchange transfer?
+   * (Needed in case the coin was partially spent.)
+   */
+  struct TALER_AmountNBO recoup_amount;
+
+  /**
+   * Public key of the refreshed coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Public key of the old coin that will receive the recoup.
+   */
+  struct TALER_CoinSpendPublicKeyP old_coin_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_confirm_recoup_refresh_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RecoupRefreshConfirmationPS pc = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
+    .purpose.size = htonl (sizeof (pc)),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .coin_pub = *coin_pub,
+    .old_coin_pub = *old_coin_pub
+  };
+
+  TALER_amount_hton (&pc.recoup_amount,
+                     recoup_amount);
+  return scb (&pc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_confirm_recoup_refresh_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *recoup_amount,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_RecoupRefreshConfirmationPS pc = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH),
+    .purpose.size = htonl (sizeof (pc)),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .coin_pub = *coin_pub,
+    .old_coin_pub = *old_coin_pub
+  };
+
+  TALER_amount_hton (&pc.recoup_amount,
+                     recoup_amount);
+
+  return
+    GNUNET_CRYPTO_eddsa_verify 
(TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH,
+                                &pc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it does not
+ * currently know a denomination by the given hash.
+ */
+struct TALER_DenominationUnknownAffirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange sign this message.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+  /**
+   * Hash of the public denomination key we do not know.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_unknown_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_DenominationUnknownAffirmationPS dua = {
+    .purpose.size = htonl (sizeof (dua)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .h_denom_pub = *h_denom_pub,
+  };
+
+  return scb (&dua.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_unknown_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_DenominationUnknownAffirmationPS dua = {
+    .purpose.size = htonl (sizeof (dua)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .h_denom_pub = *h_denom_pub,
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN,
+                                &dua,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it does not
+ * currently consider the given denomination to be valid
+ * for the requested operation.
+ */
+struct TALER_DenominationExpiredAffirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange sign this message.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+  /**
+   * Name of the operation that is not allowed at this time.  Might NOT be 
0-terminated, but is padded with 0s.
+   */
+  char operation[8];
+
+  /**
+   * Hash of the public denomination key we do not know.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_denomination_expired_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const char *op,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_DenominationExpiredAffirmationPS dua = {
+    .purpose.size = htonl (sizeof (dua)),
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .h_denom_pub = *h_denom_pub,
+  };
+
+  /* strncpy would create a compiler warning */
+  GNUNET_memcpy (dua.operation,
+                 op,
+                 GNUNET_MIN (sizeof (dua.operation),
+                             strlen (op)));
+  return scb (&dua.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_denomination_expired_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const char *op,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_DenominationExpiredAffirmationPS dua = {
+    .purpose.size = htonl (sizeof (dua)),
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED),
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp),
+    .h_denom_pub = *h_denom_pub,
+  };
+
+  /* strncpy would create a compiler warning */
+  GNUNET_memcpy (dua.operation,
+                 op,
+                 GNUNET_MIN (sizeof (dua.operation),
+                             strlen (op)));
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED,
+                                &dua,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * closed a reserve and send back the funds.
+ */
+struct TALER_ReserveCloseConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange initiate the wire transfer.
+   */
+  struct GNUNET_TIME_TimestampNBO timestamp;
+
+  /**
+   * How much did the exchange send?
+   */
+  struct TALER_AmountNBO closing_amount;
+
+  /**
+   * How much did the exchange charge for closing the reserve?
+   */
+  struct TALER_AmountNBO closing_fee;
+
+  /**
+   * Public key of the reserve that was closed.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Hash of the receiver's bank account.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+  /**
+   * Wire transfer subject.
+   */
+  struct TALER_WireTransferIdentifierRawP wtid;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_closed_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *closing_amount,
+  const struct TALER_Amount *closing_fee,
+  const char *payto,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ReserveCloseConfirmationPS rcc = {
+    .purpose.size = htonl (sizeof (rcc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED),
+    .wtid = *wtid,
+    .reserve_pub = *reserve_pub,
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+  };
+
+  TALER_amount_hton (&rcc.closing_amount,
+                     closing_amount);
+  TALER_amount_hton (&rcc.closing_fee,
+                     closing_fee);
+  TALER_payto_hash (payto,
+                    &rcc.h_payto);
+  return scb (&rcc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_closed_verify (
+  struct GNUNET_TIME_Timestamp timestamp,
+  const struct TALER_Amount *closing_amount,
+  const struct TALER_Amount *closing_fee,
+  const char *payto,
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ReserveCloseConfirmationPS rcc = {
+    .purpose.size = htonl (sizeof (rcc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED),
+    .wtid = *wtid,
+    .reserve_pub = *reserve_pub,
+    .timestamp = GNUNET_TIME_timestamp_hton (timestamp)
+  };
+
+  TALER_amount_hton (&rcc.closing_amount,
+                     closing_amount);
+  TALER_amount_hton (&rcc.closing_fee,
+                     closing_fee);
+  TALER_payto_hash (payto,
+                    &rcc.h_payto);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED,
+                                &rcc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * received funds deposited into a purse.
+ */
+struct TALER_PurseCreateDepositConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange receive the deposits.
+   */
+  struct GNUNET_TIME_TimestampNBO exchange_time;
+
+  /**
+   * When will the purse expire?
+   */
+  struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+  /**
+   * How much should the purse ultimately contain.
+   */
+  struct TALER_AmountNBO amount_without_fee;
+
+  /**
+   * How much was deposited so far.
+   */
+  struct TALER_AmountNBO total_deposited;
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Hash of the contract of the purse.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_created_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *total_deposited,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_PurseCreateDepositConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION),
+    .purpose.size = htonl (sizeof (dc)),
+    .h_contract_terms = *h_contract_terms,
+    .purse_pub = *purse_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+  };
+
+  TALER_amount_hton (&dc.amount_without_fee,
+                     amount_without_fee);
+  TALER_amount_hton (&dc.total_deposited,
+                     total_deposited);
+  return scb (&dc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_created_verify (
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *total_deposited,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_PurseCreateDepositConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION),
+    .purpose.size = htonl (sizeof (dc)),
+    .h_contract_terms = *h_contract_terms,
+    .purse_pub = *purse_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+  };
+
+  TALER_amount_hton (&dc.amount_without_fee,
+                     amount_without_fee);
+  TALER_amount_hton (&dc.total_deposited,
+                     total_deposited);
+  return
+    GNUNET_CRYPTO_eddsa_verify 
(TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION,
+                                &dc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * received funds deposited into a purse.
+ */
+struct TALER_CoinPurseRefundConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * How much will be refunded to the purse.
+   */
+  struct TALER_AmountNBO refunded_amount;
+
+  /**
+   * How much was the refund fee.
+   */
+  struct TALER_AmountNBO refund_fee;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_refund_sign (
+  TALER_ExchangeSignCallback scb,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_CoinPurseRefundConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND),
+    .purpose.size = htonl (sizeof (dc)),
+    .coin_pub = *coin_pub,
+    .purse_pub = *purse_pub,
+  };
+
+  TALER_amount_hton (&dc.refunded_amount,
+                     amount_without_fee);
+  TALER_amount_hton (&dc.refund_fee,
+                     refund_fee);
+  return scb (&dc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_refund_verify (
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_CoinPurseRefundConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND),
+    .purpose.size = htonl (sizeof (dc)),
+    .coin_pub = *coin_pub,
+    .purse_pub = *purse_pub,
+  };
+
+  TALER_amount_hton (&dc.refunded_amount,
+                     amount_without_fee);
+  TALER_amount_hton (&dc.refund_fee,
+                     refund_fee);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND,
+                                &dc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which the exchange affirms that it has
+ * merged a purse into a reserve.
+ */
+struct TALER_PurseMergedConfirmationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the exchange receive the deposits.
+   */
+  struct GNUNET_TIME_TimestampNBO exchange_time;
+
+  /**
+   * When will the purse expire?
+   */
+  struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+  /**
+   * How much should the purse ultimately contain.
+   */
+  struct TALER_AmountNBO amount_without_fee;
+
+  /**
+   * Public key of the purse.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Public key of the reserve.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Hash of the contract of the purse.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Hash of the provider URL hosting the reserve.
+   */
+  struct GNUNET_HashCode h_provider_url;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_merged_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *exchange_url,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_PurseMergedConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED),
+    .purpose.size = htonl (sizeof (dc)),
+    .h_contract_terms = *h_contract_terms,
+    .purse_pub = *purse_pub,
+    .reserve_pub = *reserve_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+  };
+
+  TALER_amount_hton (&dc.amount_without_fee,
+                     amount_without_fee);
+  GNUNET_CRYPTO_hash (exchange_url,
+                      strlen (exchange_url) + 1,
+                      &dc.h_provider_url);
+  return scb (&dc.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_merged_verify (
+  struct GNUNET_TIME_Timestamp exchange_time,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_Amount *amount_without_fee,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const char *exchange_url,
+  const struct TALER_ExchangePublicKeyP *pub,
+  const struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_PurseMergedConfirmationPS dc = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED),
+    .purpose.size = htonl (sizeof (dc)),
+    .h_contract_terms = *h_contract_terms,
+    .purse_pub = *purse_pub,
+    .reserve_pub = *reserve_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time)
+  };
+
+  TALER_amount_hton (&dc.amount_without_fee,
+                     amount_without_fee);
+  GNUNET_CRYPTO_hash (exchange_url,
+                      strlen (exchange_url) + 1,
+                      &dc.h_provider_url);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED,
+                                &dc,
+                                &sig->eddsa_signature,
+                                &pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a purse status
+ * from the exchange.
+ */
+struct TALER_PurseStatusPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_EXCHANGE_PURSE_STATUS.  Signed
+   * by a `struct TALER_ExchangePublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time when the purse was merged, possibly 'never'.
+   */
+  struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+  /**
+   * Time when the purse was deposited last, possibly 'never'.
+   */
+  struct GNUNET_TIME_TimestampNBO deposit_timestamp;
+
+  /**
+   * Amount deposited in total in the purse without fees.
+   * May be possibly less than the target amount.
+   */
+  struct TALER_AmountNBO balance;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_purse_status_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  struct GNUNET_TIME_Timestamp deposit_timestamp,
+  const struct TALER_Amount *balance,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_PurseStatusPS dcs = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS),
+    .purpose.size = htonl (sizeof (dcs)),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp)
+  };
+
+  TALER_amount_hton (&dcs.balance,
+                     balance);
+  return scb (&dcs.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_purse_status_verify (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  struct GNUNET_TIME_Timestamp deposit_timestamp,
+  const struct TALER_Amount *balance,
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct TALER_PurseStatusPS dcs = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS),
+    .purpose.size = htonl (sizeof (dcs)),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp)
+  };
+
+  TALER_amount_hton (&dcs.balance,
+                     balance);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS,
+                                  &dcs,
+                                  &exchange_sig->eddsa_signature,
+                                  &exchange_pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by the exchange to affirm that the
+ * owner of a reserve has certain attributes.
+ */
+struct TALER_ExchangeAttestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time when the attestation was made.
+   */
+  struct GNUNET_TIME_TimestampNBO attest_timestamp;
+
+  /**
+   * Time when the attestation expires.
+   */
+  struct GNUNET_TIME_TimestampNBO expiration_time;
+
+  /**
+   * Public key of the reserve for which the attributes
+   * are attested.
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Hash over the attributes.
+   */
+  struct GNUNET_HashCode h_attributes;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum TALER_ErrorCode
+TALER_exchange_online_reserve_attest_details_sign (
+  TALER_ExchangeSignCallback scb,
+  struct GNUNET_TIME_Timestamp attest_timestamp,
+  struct GNUNET_TIME_Timestamp expiration_time,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const json_t *attributes,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeAttestPS rap = {
+    .purpose.size = htonl (sizeof (rap)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS),
+    .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp),
+    .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time),
+    .reserve_pub = *reserve_pub
+  };
+
+  TALER_json_hash (attributes,
+                   &rap.h_attributes);
+  return scb (&rap.purpose,
+              pub,
+              sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_online_reserve_attest_details_verify (
+  struct GNUNET_TIME_Timestamp attest_timestamp,
+  struct GNUNET_TIME_Timestamp expiration_time,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const json_t *attributes,
+  struct TALER_ExchangePublicKeyP *pub,
+  struct TALER_ExchangeSignatureP *sig)
+{
+  struct TALER_ExchangeAttestPS rap = {
+    .purpose.size = htonl (sizeof (rap)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS),
+    .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp),
+    .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time),
+    .reserve_pub = *reserve_pub
+  };
+
+  TALER_json_hash (attributes,
+                   &rap.h_attributes);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (
+        TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS,
+        &rap,
+        &sig->eddsa_signature,
+        &pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/* end of exchange_signatures.c */
diff --git a/src/util/merchant_signatures.c b/src/util/merchant_signatures.c
new file mode 100644
index 0000000..36f9649
--- /dev/null
+++ b/src/util/merchant_signatures.c
@@ -0,0 +1,352 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020 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 merchant_signatures.c
+ * @brief Utility functions for Taler merchant signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to obtain
+ * the wire transfer identifier associated with a deposit.
+ */
+struct TALER_DepositTrackPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the proposal data of the contract for which this deposit is 
made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_merchant_deposit_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_DepositTrackPS dtp = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION),
+    .purpose.size = htonl (sizeof (dtp)),
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire,
+    .coin_pub = *coin_pub
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+                            &dtp,
+                            &merchant_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_deposit_verify (
+  const struct TALER_MerchantPublicKeyP *merchant,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_DepositTrackPS tps = {
+    .purpose.size = htonl (sizeof (tps)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION),
+    .coin_pub = *coin_pub,
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION,
+                                &tps,
+                                &merchant_sig->eddsa_sig,
+                                &merchant->eddsa_pub);
+}
+
+
+/**
+ * @brief Format used to generate the signature on a request to refund
+ * a coin into the account of the customer.
+ */
+struct TALER_RefundRequestPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the proposal data to identify the contract
+   * which is being refunded.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * The coin's public key.  This is the value that must have been
+   * signed (blindly) by the Exchange.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Merchant-generated transaction ID for the refund.
+   */
+  uint64_t rtransaction_id GNUNET_PACKED;
+
+  /**
+   * Amount to be refunded, including refund fee charged by the
+   * exchange to the customer.
+   */
+  struct TALER_AmountNBO refund_amount;
+};
+
+
+void
+TALER_merchant_refund_sign (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *amount,
+  const struct TALER_MerchantPrivateKeyP *merchant_priv,
+  struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_RefundRequestPS rr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
+    .purpose.size = htonl (sizeof (rr)),
+    .h_contract_terms = *h_contract_terms,
+    .coin_pub = *coin_pub,
+    .rtransaction_id = GNUNET_htonll (rtransaction_id)
+  };
+
+  TALER_amount_hton (&rr.refund_amount,
+                     amount);
+  GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
+                            &rr,
+                            &merchant_sig->eddsa_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_refund_verify (
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  uint64_t rtransaction_id,
+  const struct TALER_Amount *amount,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_RefundRequestPS rr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
+    .purpose.size = htonl (sizeof (rr)),
+    .h_contract_terms = *h_contract_terms,
+    .coin_pub = *coin_pub,
+    .rtransaction_id = GNUNET_htonll (rtransaction_id)
+  };
+
+  TALER_amount_hton (&rr.refund_amount,
+                     amount);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND,
+                                &rr,
+                                &merchant_sig->eddsa_sig,
+                                &merchant_pub->eddsa_pub);
+}
+
+
+/**
+ * @brief Information signed by the exchange's master
+ * key affirming the IBAN details for the exchange.
+ */
+struct TALER_MerchantWireDetailsPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MERCHANT_WIRE_DETAILS.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Salted hash over the account holder's payto:// URL and
+   * the salt, as done by #TALER_merchant_wire_signature_hash().
+   */
+  struct TALER_MerchantWireHashP h_wire_details GNUNET_PACKED;
+
+};
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_wire_signature_check (
+  const char *payto_uri,
+  const struct TALER_WireSaltP *salt,
+  const struct TALER_MerchantPublicKeyP *merch_pub,
+  const struct TALER_MerchantSignatureP *merch_sig)
+{
+  struct TALER_MerchantWireDetailsPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS),
+    .purpose.size = htonl (sizeof (wd))
+  };
+
+  TALER_merchant_wire_signature_hash (payto_uri,
+                                      salt,
+                                      &wd.h_wire_details);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS,
+                                     &wd,
+                                     &merch_sig->eddsa_sig,
+                                     &merch_pub->eddsa_pub);
+}
+
+
+void
+TALER_merchant_wire_signature_make (
+  const char *payto_uri,
+  const struct TALER_WireSaltP *salt,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct TALER_MerchantSignatureP *merch_sig)
+{
+  struct TALER_MerchantWireDetailsPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS),
+    .purpose.size = htonl (sizeof (wd))
+  };
+
+  TALER_merchant_wire_signature_hash (payto_uri,
+                                      salt,
+                                      &wd.h_wire_details);
+  GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+                            &wd,
+                            &merch_sig->eddsa_sig);
+}
+
+
+/**
+ * Used by merchants to return signed responses to /pay requests.
+ * Currently only used to return 200 OK signed responses.
+ */
+struct TALER_PaymentResponsePS
+{
+  /**
+   * Set to #TALER_SIGNATURE_MERCHANT_PAYMENT_OK. Note that
+   * unsuccessful payments are usually proven by some exchange's signature.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the proposal data associated with this confirmation
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+};
+
+void
+TALER_merchant_pay_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct GNUNET_CRYPTO_EddsaSignature *merch_sig)
+{
+  struct TALER_PaymentResponsePS mr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+    .purpose.size = htonl (sizeof (mr)),
+    .h_contract_terms = *h_contract_terms
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+                            &mr,
+                            merch_sig);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_merchant_pay_verify (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_PaymentResponsePS pr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+    .purpose.size = htonl (sizeof (pr)),
+    .h_contract_terms = *h_contract_terms
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+                                &pr,
+                                &merchant_sig->eddsa_sig,
+                                &merchant_pub->eddsa_pub);
+}
+
+
+/**
+ * The contract sent by the merchant to the wallet.
+ */
+struct TALER_ProposalDataPS
+{
+  /**
+   * Purpose header for the signature over the proposal data
+   * with purpose #TALER_SIGNATURE_MERCHANT_CONTRACT.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the JSON contract in UTF-8 including 0-termination,
+   * using JSON_COMPACT | JSON_SORT_KEYS
+   */
+  struct TALER_PrivateContractHashP hash;
+};
+
+void
+TALER_merchant_contract_sign (
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_MerchantPrivateKeyP *merch_priv,
+  struct GNUNET_CRYPTO_EddsaSignature *merch_sig)
+{
+  struct TALER_ProposalDataPS pdps = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT),
+    .purpose.size = htonl (sizeof (pdps)),
+    .hash = *h_contract_terms
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv,
+                            &pdps,
+                            merch_sig);
+}
+
+
+// NB: "TALER_merchant_contract_verify" not (yet?) needed / not defined.
+
+/* end of merchant_signatures.c */
diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c
new file mode 100644
index 0000000..fbff850
--- /dev/null
+++ b/src/util/offline_signatures.c
@@ -0,0 +1,1388 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2020-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file offline_signatures.c
+ * @brief Utility functions for Taler exchange offline signatures
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an AML officer status change.
+ */
+struct TALER_MasterAmlOfficerStatusPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_AML_KEY.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the change.
+   */
+  struct GNUNET_TIME_TimestampNBO change_date;
+
+  /**
+   * Public key of the AML officer.
+   */
+  struct TALER_AmlOfficerPublicKeyP officer_pub;
+
+  /**
+   * Hash over the AML officer's name.
+   */
+  struct GNUNET_HashCode h_officer_name GNUNET_PACKED;
+
+  /**
+   * Bitmask: 1 if enabled; 2 for read-only access. in NBO.
+   */
+  uint32_t is_active GNUNET_PACKED;
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_aml_officer_status_sign (
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const char *officer_name,
+  struct GNUNET_TIME_Timestamp change_date,
+  bool is_active,
+  bool read_only,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAmlOfficerStatusPS as = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY),
+    .purpose.size = htonl (sizeof (as)),
+    .change_date = GNUNET_TIME_timestamp_hton (change_date),
+    .officer_pub = *officer_pub,
+    .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
+  };
+
+  GNUNET_CRYPTO_hash (officer_name,
+                      strlen (officer_name) + 1,
+                      &as.h_officer_name);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &as,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_aml_officer_status_verify (
+  const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+  const char *officer_name,
+  struct GNUNET_TIME_Timestamp change_date,
+  bool is_active,
+  bool read_only,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAmlOfficerStatusPS as = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY),
+    .purpose.size = htonl (sizeof (as)),
+    .change_date = GNUNET_TIME_timestamp_hton (change_date),
+    .officer_pub = *officer_pub,
+    .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0))
+  };
+
+  GNUNET_CRYPTO_hash (officer_name,
+                      strlen (officer_name) + 1,
+                      &as.h_officer_name);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_AML_KEY,
+                                     &as,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an auditor to be added to the exchange's set of auditors.
+ */
+struct TALER_MasterAddAuditorPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_ADD_AUDITOR.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the change.
+   */
+  struct GNUNET_TIME_TimestampNBO start_date;
+
+  /**
+   * Public key of the auditor.
+   */
+  struct TALER_AuditorPublicKeyP auditor_pub;
+
+  /**
+   * Hash over the auditor's URL.
+   */
+  struct GNUNET_HashCode h_auditor_url GNUNET_PACKED;
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_auditor_add_sign (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  struct GNUNET_TIME_Timestamp start_date,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAddAuditorPS kv = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_AUDITOR),
+    .purpose.size = htonl (sizeof (kv)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_date),
+    .auditor_pub = *auditor_pub,
+  };
+
+  GNUNET_CRYPTO_hash (auditor_url,
+                      strlen (auditor_url) + 1,
+                      &kv.h_auditor_url);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_add_verify (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  const char *auditor_url,
+  struct GNUNET_TIME_Timestamp start_date,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAddAuditorPS aa = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_ADD_AUDITOR),
+    .purpose.size = htonl (sizeof (aa)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_date),
+    .auditor_pub = *auditor_pub
+  };
+
+  GNUNET_CRYPTO_hash (auditor_url,
+                      strlen (auditor_url) + 1,
+                      &aa.h_auditor_url);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_ADD_AUDITOR,
+                                     &aa,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * an auditor to be removed from the exchange's set of auditors.
+ */
+struct TALER_MasterDelAuditorPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_DEL_AUDITOR.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the change.
+   */
+  struct GNUNET_TIME_TimestampNBO end_date;
+
+  /**
+   * Public key of the auditor.
+   */
+  struct TALER_AuditorPublicKeyP auditor_pub;
+
+};
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_auditor_del_sign (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDelAuditorPS kv = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_AUDITOR),
+    .purpose.size = htonl (sizeof (kv)),
+    .end_date = GNUNET_TIME_timestamp_hton (end_date),
+    .auditor_pub = *auditor_pub,
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_auditor_del_verify (
+  const struct TALER_AuditorPublicKeyP *auditor_pub,
+  struct GNUNET_TIME_Timestamp end_date,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDelAuditorPS da = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_DEL_AUDITOR),
+    .purpose.size = htonl (sizeof (da)),
+    .end_date = GNUNET_TIME_timestamp_hton (end_date),
+    .auditor_pub = *auditor_pub
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DEL_AUDITOR,
+                                     &da,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message confirming that a denomination key was revoked.
+ */
+struct TALER_MasterDenominationKeyRevocationPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the denomination key.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_denomination_revoke_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDenominationKeyRevocationPS rm = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
+    .purpose.size = htonl (sizeof (rm)),
+    .h_denom_pub = *h_denom_pub
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &rm,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denomination_revoke_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDenominationKeyRevocationPS kr = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED),
+    .purpose.size = htonl (sizeof (kr)),
+    .h_denom_pub = *h_denom_pub
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED,
+    &kr,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message confirming that an exchange online signing key was revoked.
+ */
+struct TALER_MasterSigningKeyRevocationPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * The exchange's public key.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_signkey_revoke_sign (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterSigningKeyRevocationPS kv = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED),
+    .purpose.size = htonl (sizeof (kv)),
+    .exchange_pub = *exchange_pub
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_revoke_verify (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterSigningKeyRevocationPS rm = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED),
+    .purpose.size = htonl (sizeof (rm)),
+    .exchange_pub = *exchange_pub
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED,
+    &rm,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a signing key of the exchange.  Signing keys are 
used
+ * to sign exchange messages other than coins, i.e. to confirm that a
+ * deposit was successful or that a refresh was accepted.
+ */
+struct TALER_ExchangeSigningKeyValidityPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When does this signing key begin to be valid?
+   */
+  struct GNUNET_TIME_TimestampNBO start;
+
+  /**
+   * When does this signing key expire? Note: This is currently when
+   * the Exchange will definitively stop using it.  Signatures made with
+   * the key remain valid until @e end.  When checking validity periods,
+   * clients should allow for some overlap between keys and tolerate
+   * the use of either key during the overlap time (due to the
+   * possibility of clock skew).
+   */
+  struct GNUNET_TIME_TimestampNBO expire;
+
+  /**
+   * When do signatures with this signing key become invalid?  After
+   * this point, these signatures cannot be used in (legal) disputes
+   * anymore, as the Exchange is then allowed to destroy its side of the
+   * evidence.  @e end is expected to be significantly larger than @e
+   * expire (by a year or more).
+   */
+  struct GNUNET_TIME_TimestampNBO end;
+
+  /**
+   * The public online signing key that the exchange will use
+   * between @e start and @e expire.
+   */
+  struct TALER_ExchangePublicKeyP signkey_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_signkey_validity_sign (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Timestamp end_sign,
+  struct GNUNET_TIME_Timestamp end_legal,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_ExchangeSigningKeyValidityPS skv = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
+    .purpose.size = htonl (sizeof (skv)),
+    .start = GNUNET_TIME_timestamp_hton (start_sign),
+    .expire = GNUNET_TIME_timestamp_hton (end_sign),
+    .end = GNUNET_TIME_timestamp_hton (end_legal),
+    .signkey_pub = *exchange_pub
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &skv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_signkey_validity_verify (
+  const struct TALER_ExchangePublicKeyP *exchange_pub,
+  struct GNUNET_TIME_Timestamp start_sign,
+  struct GNUNET_TIME_Timestamp end_sign,
+  struct GNUNET_TIME_Timestamp end_legal,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_ExchangeSigningKeyValidityPS skv = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY),
+    .purpose.size = htonl (sizeof (skv)),
+    .start = GNUNET_TIME_timestamp_hton (start_sign),
+    .expire = GNUNET_TIME_timestamp_hton (end_sign),
+    .end = GNUNET_TIME_timestamp_hton (end_legal),
+    .signkey_pub = *exchange_pub
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY,
+    &skv,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information about a denomination key. Denomination keys
+ * are used to sign coins of a certain value into existence.
+ */
+struct TALER_DenominationKeyValidityPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * The long-term offline master key of the exchange that was
+   * used to create @e signature.
+   *
+   * Note: This member is not strictly required, but here for
+   * backwards-compatibility. If we ever again badly break
+   * compatibility, we might want to remove it.
+   */
+  struct TALER_MasterPublicKeyP master;
+
+  /**
+   * Start time of the validity period for this key.
+   */
+  struct GNUNET_TIME_TimestampNBO start;
+
+  /**
+   * The exchange will sign fresh coins between @e start and this time.
+   * @e expire_withdraw will be somewhat larger than @e start to
+   * ensure a sufficiently large anonymity set, while also allowing
+   * the Exchange to limit the financial damage in case of a key being
+   * compromised.  Thus, exchanges with low volume are expected to have a
+   * longer withdraw period (@e expire_withdraw - @e start) than exchanges
+   * with high transaction volume.  The period may also differ between
+   * types of coins.  A exchange may also have a few denomination keys
+   * with the same value with overlapping validity periods, to address
+   * issues such as clock skew.
+   */
+  struct GNUNET_TIME_TimestampNBO expire_withdraw;
+
+  /**
+   * Coins signed with the denomination key must be spent or refreshed
+   * between @e start and this expiration time.  After this time, the
+   * exchange will refuse transactions involving this key as it will
+   * "drop" the table with double-spending information (shortly after)
+   * this time.  Note that wallets should refresh coins significantly
+   * before this time to be on the safe side.  @e expire_deposit must be
+   * significantly larger than @e expire_withdraw (by months or even
+   * years).
+   */
+  struct GNUNET_TIME_TimestampNBO expire_deposit;
+
+  /**
+   * When do signatures with this denomination key become invalid?
+   * After this point, these signatures cannot be used in (legal)
+   * disputes anymore, as the Exchange is then allowed to destroy its side
+   * of the evidence.  @e expire_legal is expected to be significantly
+   * larger than @e expire_deposit (by a year or more).
+   */
+  struct GNUNET_TIME_TimestampNBO expire_legal;
+
+  /**
+   * The value of the coins signed with this denomination key.
+   */
+  struct TALER_AmountNBO value;
+
+  /**
+   * Fees for the coin.
+   */
+  struct TALER_DenomFeeSetNBOP fees;
+
+  /**
+   * Hash code of the denomination public key. (Used to avoid having
+   * the variable-size RSA key in this struct.)
+   */
+  struct TALER_DenominationHashP denom_hash GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_denom_validity_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_DenominationKeyValidityPS issue = {
+    .purpose.purpose
+      = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY),
+    .purpose.size
+      = htonl (sizeof (issue)),
+    .start = GNUNET_TIME_timestamp_hton (stamp_start),
+    .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+    .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+    .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+    .denom_hash = *h_denom_pub
+  };
+
+  GNUNET_CRYPTO_eddsa_key_get_public (&master_priv->eddsa_priv,
+                                      &issue.master.eddsa_pub);
+  TALER_amount_hton (&issue.value,
+                     coin_value);
+  TALER_denom_fee_set_hton (&issue.fees,
+                            fees);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &issue,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_denom_validity_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp stamp_start,
+  struct GNUNET_TIME_Timestamp stamp_expire_withdraw,
+  struct GNUNET_TIME_Timestamp stamp_expire_deposit,
+  struct GNUNET_TIME_Timestamp stamp_expire_legal,
+  const struct TALER_Amount *coin_value,
+  const struct TALER_DenomFeeSet *fees,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_DenominationKeyValidityPS dkv = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY),
+    .purpose.size = htonl (sizeof (dkv)),
+    .master = *master_pub,
+    .start = GNUNET_TIME_timestamp_hton (stamp_start),
+    .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw),
+    .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit),
+    .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal),
+    .denom_hash = *h_denom_pub
+  };
+
+  TALER_amount_hton (&dkv.value,
+                     coin_value);
+  TALER_denom_fee_set_hton (&dkv.fees,
+                            fees);
+  return
+    GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY,
+    &dkv,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * a payto:// URI to be added to the exchange's set of active wire accounts.
+ */
+struct TALER_MasterAddWirePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_ADD_WIRE.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the change.
+   */
+  struct GNUNET_TIME_TimestampNBO start_date;
+
+  /**
+   * Hash over the exchange's payto URI.
+   */
+  struct TALER_PaytoHashP h_payto GNUNET_PACKED;
+
+  /**
+   * Hash over the conversion URL, all zeros if there
+   * is no conversion URL.
+   */
+  struct GNUNET_HashCode h_conversion_url;
+
+  /**
+   * Hash over the debit restrictions.
+   */
+  struct GNUNET_HashCode h_debit_restrictions;
+
+  /**
+   * Hash over the credit restrictions.
+   */
+  struct GNUNET_HashCode h_credit_restrictions;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_add_sign (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAddWirePS kv = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE),
+    .purpose.size = htonl (sizeof (kv)),
+    .start_date = GNUNET_TIME_timestamp_hton (now),
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &kv.h_payto);
+  if (NULL != conversion_url)
+    GNUNET_CRYPTO_hash (conversion_url,
+                        strlen (conversion_url) + 1,
+                        &kv.h_conversion_url);
+  TALER_json_hash (debit_restrictions,
+                   &kv.h_debit_restrictions);
+  TALER_json_hash (credit_restrictions,
+                   &kv.h_credit_restrictions);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_add_verify (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  struct GNUNET_TIME_Timestamp sign_time,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterAddWirePS aw = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE),
+    .purpose.size = htonl (sizeof (aw)),
+    .start_date = GNUNET_TIME_timestamp_hton (sign_time),
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &aw.h_payto);
+  if (NULL != conversion_url)
+    GNUNET_CRYPTO_hash (conversion_url,
+                        strlen (conversion_url) + 1,
+                        &aw.h_conversion_url);
+  TALER_json_hash (debit_restrictions,
+                   &aw.h_debit_restrictions);
+  TALER_json_hash (credit_restrictions,
+                   &aw.h_credit_restrictions);
+  return
+    GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_ADD_WIRE,
+    &aw,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the information of
+ * a  wire method to be removed to the exchange's set of active accounts.
+ */
+struct TALER_MasterDelWirePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_DEL_WIRE.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time of the change.
+   */
+  struct GNUNET_TIME_TimestampNBO end_date;
+
+  /**
+   * Hash over the exchange's payto URI.
+   */
+  struct TALER_PaytoHashP h_payto GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_del_sign (
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp now,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDelWirePS kv = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_WIRE),
+    .purpose.size = htonl (sizeof (kv)),
+    .end_date = GNUNET_TIME_timestamp_hton (now),
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &kv.h_payto);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_del_verify (
+  const char *payto_uri,
+  struct GNUNET_TIME_Timestamp sign_time,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterDelWirePS aw = {
+    .purpose.purpose = htonl (
+      TALER_SIGNATURE_MASTER_DEL_WIRE),
+    .purpose.size = htonl (sizeof (aw)),
+    .end_date = GNUNET_TIME_timestamp_hton (sign_time),
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &aw.h_payto);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_MASTER_DEL_WIRE,
+    &aw,
+    &master_sig->eddsa_signature,
+    &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information signed by the exchange's master
+ * key stating the wire fee to be paid per wire transfer.
+ */
+struct TALER_MasterWireFeePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_WIRE_FEES.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the wire method (yes, H("x-taler-bank") or H("iban")), in lower
+   * case, including 0-terminator.  Used to uniquely identify which
+   * wire method these fees apply to.
+   */
+  struct GNUNET_HashCode h_wire_method;
+
+  /**
+   * Start date when the fee goes into effect.
+   */
+  struct GNUNET_TIME_TimestampNBO start_date;
+
+  /**
+   * End date when the fee stops being in effect (exclusive)
+   */
+  struct GNUNET_TIME_TimestampNBO end_date;
+
+  /**
+   * Fees charged for wire transfers using the
+   * given wire method.
+   */
+  struct TALER_WireFeeSetNBOP fees;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_wire_fee_sign (
+  const char *payment_method,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_WireFeeSet *fees,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterWireFeePS kv = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES),
+    .purpose.size = htonl (sizeof (kv)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_time),
+    .end_date = GNUNET_TIME_timestamp_hton (end_time),
+  };
+
+  GNUNET_CRYPTO_hash (payment_method,
+                      strlen (payment_method) + 1,
+                      &kv.h_wire_method);
+  TALER_wire_fee_set_hton (&kv.fees,
+                           fees);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &kv,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_wire_fee_verify (
+  const char *payment_method,
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_WireFeeSet *fees,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterWireFeePS wf = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES),
+    .purpose.size = htonl (sizeof (wf)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_time),
+    .end_date = GNUNET_TIME_timestamp_hton (end_time)
+  };
+
+  GNUNET_CRYPTO_hash (payment_method,
+                      strlen (payment_method) + 1,
+                      &wf.h_wire_method);
+  TALER_wire_fee_set_hton (&wf.fees,
+                           fees);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES,
+                                &wf,
+                                &master_sig->eddsa_signature,
+                                &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Global fees charged by the exchange independent of
+ * denomination or wire method.
+ */
+struct TALER_MasterGlobalFeePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_GLOBAL_FEES.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Start date when the fee goes into effect.
+   */
+  struct GNUNET_TIME_TimestampNBO start_date;
+
+  /**
+   * End date when the fee stops being in effect (exclusive)
+   */
+  struct GNUNET_TIME_TimestampNBO end_date;
+
+  /**
+   * How long does an exchange keep a purse around after a purse
+   * has expired (or been successfully merged)?  A 'GET' request
+   * for a purse will succeed until the purse expiration time
+   * plus this value.
+   */
+  struct GNUNET_TIME_RelativeNBO purse_timeout;
+
+  /**
+   * How long will the exchange preserve the account history?  After an
+   * account was deleted/closed, the exchange will retain the account history
+   * for legal reasons until this time.
+   */
+  struct GNUNET_TIME_RelativeNBO history_expiration;
+
+  /**
+   * Fee charged to the merchant per wire transfer.
+   */
+  struct TALER_GlobalFeeSetNBOP fees;
+
+  /**
+   * Number of concurrent purses that any
+   * account holder is allowed to create without having
+   * to pay the @e purse_fee. Here given in NBO.
+   */
+  uint32_t purse_account_limit;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_global_fee_sign (
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterGlobalFeePS wf = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES),
+    .purpose.size = htonl (sizeof (wf)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_time),
+    .end_date = GNUNET_TIME_timestamp_hton (end_time),
+    .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout),
+    .history_expiration = GNUNET_TIME_relative_hton (history_expiration),
+    .purse_account_limit = htonl (purse_account_limit)
+  };
+
+  TALER_global_fee_set_hton (&wf.fees,
+                             fees);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &wf,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_global_fee_verify (
+  struct GNUNET_TIME_Timestamp start_time,
+  struct GNUNET_TIME_Timestamp end_time,
+  const struct TALER_GlobalFeeSet *fees,
+  struct GNUNET_TIME_Relative purse_timeout,
+  struct GNUNET_TIME_Relative history_expiration,
+  uint32_t purse_account_limit,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterGlobalFeePS wf = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES),
+    .purpose.size = htonl (sizeof (wf)),
+    .start_date = GNUNET_TIME_timestamp_hton (start_time),
+    .end_date = GNUNET_TIME_timestamp_hton (end_time),
+    .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout),
+    .history_expiration = GNUNET_TIME_relative_hton (history_expiration),
+    .purse_account_limit = htonl (purse_account_limit)
+  };
+
+  TALER_global_fee_set_hton (&wf.fees,
+                             fees);
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_GLOBAL_FEES,
+                                &wf,
+                                &master_sig->eddsa_signature,
+                                &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Signature made by the exchange offline key over the manifest of
+ * an extension.
+ */
+struct TALER_MasterExtensionManifestPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION.   Signed
+   * by a `struct TALER_MasterPublicKeyP` using EdDSA.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the JSON object that represents the manifests of extensions.
+   */
+  struct TALER_ExtensionManifestsHashP h_manifest GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_extension_manifests_hash_sign (
+  const struct TALER_ExtensionManifestsHashP *h_manifest,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterExtensionManifestPS ec = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
+    .purpose.size = htonl (sizeof(ec)),
+    .h_manifest = *h_manifest
+  };
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &ec,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_extension_manifests_hash_verify (
+  const struct TALER_ExtensionManifestsHashP *h_manifest,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig
+  )
+{
+  struct TALER_MasterExtensionManifestPS ec = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION),
+    .purpose.size = htonl (sizeof(ec)),
+    .h_manifest = *h_manifest
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION,
+                                     &ec,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Information signed by the exchange's master
+ * key affirming the IBAN details for the exchange.
+ */
+struct TALER_MasterWireDetailsPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_WIRE_DETAILS.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the account holder's payto:// URL.
+   */
+  struct TALER_PaytoHashP h_wire_details GNUNET_PACKED;
+
+  /**
+   * Hash over the conversion URL, all zeros if there
+   * is no conversion URL.
+   */
+  struct GNUNET_HashCode h_conversion_url;
+
+  /**
+   * Hash over the debit restrictions.
+   */
+  struct GNUNET_HashCode h_debit_restrictions;
+
+  /**
+   * Hash over the credit restrictions.
+   */
+  struct GNUNET_HashCode h_credit_restrictions;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_wire_signature_check (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterWireDetailsPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS),
+    .purpose.size = htonl (sizeof (wd))
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &wd.h_wire_details);
+  if (NULL != conversion_url)
+    GNUNET_CRYPTO_hash (conversion_url,
+                        strlen (conversion_url) + 1,
+                        &wd.h_conversion_url);
+  TALER_json_hash (debit_restrictions,
+                   &wd.h_debit_restrictions);
+  TALER_json_hash (credit_restrictions,
+                   &wd.h_credit_restrictions);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS,
+                                     &wd,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+void
+TALER_exchange_wire_signature_make (
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_MasterWireDetailsPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS),
+    .purpose.size = htonl (sizeof (wd))
+  };
+
+  TALER_payto_hash (payto_uri,
+                    &wd.h_wire_details);
+  if (NULL != conversion_url)
+    GNUNET_CRYPTO_hash (conversion_url,
+                        strlen (conversion_url) + 1,
+                        &wd.h_conversion_url);
+  TALER_json_hash (debit_restrictions,
+                   &wd.h_debit_restrictions);
+  TALER_json_hash (credit_restrictions,
+                   &wd.h_credit_restrictions);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &wd,
+                            &master_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to merge a purse into a reserve.
+ */
+struct TALER_PartnerConfigurationPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_PARNTER_DETAILS
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+  struct TALER_MasterPublicKeyP partner_pub;
+  struct GNUNET_TIME_TimestampNBO start_date;
+  struct GNUNET_TIME_TimestampNBO end_date;
+  struct GNUNET_TIME_RelativeNBO wad_frequency;
+  struct TALER_AmountNBO wad_fee;
+  struct GNUNET_HashCode h_url;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_partner_details_sign (
+  const struct TALER_MasterPublicKeyP *partner_pub,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  struct GNUNET_TIME_Relative wad_frequency,
+  const struct TALER_Amount *wad_fee,
+  const char *partner_base_url,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_PartnerConfigurationPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS),
+    .purpose.size = htonl (sizeof (wd)),
+    .partner_pub = *partner_pub,
+    .start_date = GNUNET_TIME_timestamp_hton (start_date),
+    .end_date = GNUNET_TIME_timestamp_hton (end_date),
+    .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency),
+  };
+
+  GNUNET_CRYPTO_hash (partner_base_url,
+                      strlen (partner_base_url) + 1,
+                      &wd.h_url);
+  TALER_amount_hton (&wd.wad_fee,
+                     wad_fee);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &wd,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_partner_details_verify (
+  const struct TALER_MasterPublicKeyP *partner_pub,
+  struct GNUNET_TIME_Timestamp start_date,
+  struct GNUNET_TIME_Timestamp end_date,
+  struct GNUNET_TIME_Relative wad_frequency,
+  const struct TALER_Amount *wad_fee,
+  const char *partner_base_url,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_PartnerConfigurationPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS),
+    .purpose.size = htonl (sizeof (wd)),
+    .partner_pub = *partner_pub,
+    .start_date = GNUNET_TIME_timestamp_hton (start_date),
+    .end_date = GNUNET_TIME_timestamp_hton (end_date),
+    .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency),
+  };
+
+  GNUNET_CRYPTO_hash (partner_base_url,
+                      strlen (partner_base_url) + 1,
+                      &wd.h_url);
+  TALER_amount_hton (&wd.wad_fee,
+                     wad_fee);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_PARTNER_DETAILS,
+                                     &wd,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to drain profits
+ * from the escrow account of the exchange.
+ */
+struct TALER_DrainProfitPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_MASTER_DRAIN_PROFITS
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+  struct TALER_WireTransferIdentifierRawP wtid;
+  struct GNUNET_TIME_TimestampNBO date;
+  struct TALER_AmountNBO amount;
+  struct GNUNET_HashCode h_section;
+  struct TALER_PaytoHashP h_payto;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_exchange_offline_profit_drain_sign (
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  struct GNUNET_TIME_Timestamp date,
+  const struct TALER_Amount *amount,
+  const char *account_section,
+  const char *payto_uri,
+  const struct TALER_MasterPrivateKeyP *master_priv,
+  struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_DrainProfitPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT),
+    .purpose.size = htonl (sizeof (wd)),
+    .wtid = *wtid,
+    .date = GNUNET_TIME_timestamp_hton (date),
+  };
+
+  GNUNET_CRYPTO_hash (account_section,
+                      strlen (account_section) + 1,
+                      &wd.h_section);
+  TALER_payto_hash (payto_uri,
+                    &wd.h_payto);
+  TALER_amount_hton (&wd.amount,
+                     amount);
+  GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv,
+                            &wd,
+                            &master_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_exchange_offline_profit_drain_verify (
+  const struct TALER_WireTransferIdentifierRawP *wtid,
+  struct GNUNET_TIME_Timestamp date,
+  const struct TALER_Amount *amount,
+  const char *account_section,
+  const char *payto_uri,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const struct TALER_MasterSignatureP *master_sig)
+{
+  struct TALER_DrainProfitPS wd = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT),
+    .purpose.size = htonl (sizeof (wd)),
+    .wtid = *wtid,
+    .date = GNUNET_TIME_timestamp_hton (date),
+  };
+
+  GNUNET_CRYPTO_hash (account_section,
+                      strlen (account_section) + 1,
+                      &wd.h_section);
+  TALER_payto_hash (payto_uri,
+                    &wd.h_payto);
+  TALER_amount_hton (&wd.amount,
+                     amount);
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DRAIN_PROFIT,
+                                     &wd,
+                                     &master_sig->eddsa_signature,
+                                     &master_pub->eddsa_pub);
+}
+
+
+/* end of offline_signatures.c */
diff --git a/src/util/os_installation.c b/src/util/os_installation.c
new file mode 100644
index 0000000..1cbb9e7
--- /dev/null
+++ b/src/util/os_installation.c
@@ -0,0 +1,70 @@
+/*
+     This file is part of GNU Taler.
+     Copyright (C) 2016 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, write to the
+     Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+     Boston, MA 02110-1301, USA.
+*/
+/**
+ * @file os_installation.c
+ * @brief initialize libgnunet OS subsystem for Taler.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+
+
+/**
+ * Default project data used for installation path detection
+ * for GNU Taler.
+ */
+static const struct GNUNET_OS_ProjectData taler_pd = {
+  .libname = "libtalerutil",
+  .project_dirname = "taler",
+  .binary_name = "taler-exchange-httpd",
+  .env_varname = "TALER_PREFIX",
+  .base_config_varname = "TALER_BASE_CONFIG",
+  .bug_email = "taler@gnu.org",
+  .homepage = "http://www.gnu.org/s/taler/";,
+  .config_file = "taler.conf",
+  .user_config_file = "~/.config/taler.conf",
+  .version = PACKAGE_VERSION "-" VCS_VERSION,
+  .is_gnu = 1,
+  .gettext_domain = "taler",
+  .gettext_path = NULL,
+};
+
+
+/**
+ * Return default project data used by Taler.
+ */
+const struct GNUNET_OS_ProjectData *
+TALER_project_data_default (void)
+{
+  return &taler_pd;
+}
+
+
+/**
+ * Initialize libtalerutil.
+ */
+void __attribute__ ((constructor))
+TALER_OS_init ()
+{
+  GNUNET_OS_init (&taler_pd);
+}
+
+
+/* end of os_installation.c */
diff --git a/src/util/paths.conf b/src/util/paths.conf
new file mode 100644
index 0000000..3415b70
--- /dev/null
+++ b/src/util/paths.conf
@@ -0,0 +1,29 @@
+# This file is in the public domain.
+#
+[PATHS]
+# The PATHS section is special, as filenames including $-expression are
+# expanded using the values from PATHS or the system environment (PATHS
+# is checked first).  Taler also supports expanding $-expressions using
+# defaults with the syntax "${VAR:-default}".  Here, "default" can again
+# be a $-expression.
+#
+# We usually want $HOME for $TALER_HOME, but we allow testcases to
+# easily override this by setting $TALER_TEST_HOME.
+#
+TALER_HOME = ${TALER_TEST_HOME:-${HOME:-${USERPROFILE}}}
+
+# see XDG Base Directory Specification at
+# http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+# for how these should be used.
+
+# Persistent data storage
+TALER_DATA_HOME = 
${TALER_TEST_HOME:-${XDG_DATA_HOME:-${TALER_HOME}/.local/share/}/.local/share/}taler/
+
+# Configuration files
+TALER_CONFIG_HOME = 
${TALER_TEST_HOME:-${XDG_CONFIG_HOME:-${TALER_HOME}/.config/}/.config/}taler/
+
+# Cached data, no big deal if lost
+TALER_CACHE_HOME = 
${TALER_TEST_HOME:-${XDG_CACHE_HOME:-${TALER_HOME}/.cache/}/.cache/}taler/
+
+# Runtime data (always lost on system boot)
+TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/
diff --git a/src/util/taler-config.c b/src/util/taler-config.c
new file mode 100644
index 0000000..0e432f8
--- /dev/null
+++ b/src/util/taler-config.c
@@ -0,0 +1,73 @@
+/*
+     This file is part of Taler.
+     Copyright (C) 2012-2021 Taler Systems SA
+
+     Taler is free software: you can redistribute it and/or modify it
+     under the terms of the GNU Affero General Public License as published
+     by the Free Software Foundation, either version 3 of the License,
+     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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+     SPDX-License-Identifier: AGPL3.0-or-later
+ */
+
+/**
+ * @file util/taler-config.c
+ * @brief tool to access and manipulate Taler configuration files
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_util_lib.h"
+
+
+/**
+ * Program to manipulate configuration files.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+      char *const *argv)
+{
+  struct GNUNET_CONFIGURATION_ConfigSettings cs = {
+    .api_version = GNUNET_UTIL_VERSION,
+    .global_ret = EXIT_SUCCESS
+  };
+  struct GNUNET_GETOPT_CommandLineOption options[] = {
+    GNUNET_GETOPT_OPTION_END
+  };
+  enum GNUNET_GenericReturnValue ret;
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_get_utf8_args (argc, argv,
+                                    &argc, &argv))
+    return EXIT_FAILURE;
+  TALER_OS_init ();
+  ret = GNUNET_PROGRAM_run (argc,
+                            argv,
+                            "taler-config [OPTIONS]",
+                            gettext_noop (
+                              "Manipulate Taler configuration files"),
+                            options,
+                            &GNUNET_CONFIGURATION_config_tool_run,
+                            &cs);
+  GNUNET_free_nz ((void *) argv);
+  GNUNET_CONFIGURATION_config_settings_free (&cs);
+  if (GNUNET_NO == ret)
+    return 0;
+  if (GNUNET_SYSERR == ret)
+    return EXIT_INVALIDARGUMENT;
+  return cs.global_ret;
+}
+
+
+/* end of taler-config.c */
diff --git a/src/util/taler-config.in b/src/util/taler-config.in
new file mode 100644
index 0000000..07f6401
--- /dev/null
+++ b/src/util/taler-config.in
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -eu
+
+if ! type gnunet-config >/dev/null; then
+  echo "$0 needs gnunet-config to be installed"
+  exit 1
+fi
+
+GC=`which gnunet-config`
+SO=`ls %libdir%/libtalerutil.so.* | sort -n | tail -n1`
+export LD_PRELOAD=${LD_PRELOAD:-}:${SO}
+exec gnunet-config "$@"
diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c
new file mode 100644
index 0000000..4b2fab6
--- /dev/null
+++ b/src/util/wallet_signatures.c
@@ -0,0 +1,1843 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2021-2023 Taler Systems SA
+
+  TALER is free software; you can redistribute it and/or modify it under the
+  terms of the GNU General Public License as published by the Free Software
+  Foundation; either version 3, or (at your option) any later version.
+
+  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License along with
+  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file wallet_signatures.c
+ * @brief Utility functions for Taler wallet signatures
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_util.h"
+#include "taler_signatures.h"
+#include <gnunet/gnunet_common.h>
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used to generate the signature on a request to deposit
+ * a coin into the account of a merchant.
+ */
+struct TALER_DepositRequestPS
+{
+  /**
+   * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT.
+   * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the contract for which this deposit is made.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED;
+
+  /**
+   * Hash over the age commitment that went into the coin. Maybe all zero, if
+   * age commitment isn't applicable to the denomination.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+  /**
+   * Hash over optional policy extension attributes shared with the exchange.
+   */
+  struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED;
+
+  /**
+   * Hash over the wiring information of the merchant.
+   */
+  struct TALER_MerchantWireHashP h_wire GNUNET_PACKED;
+
+  /**
+   * Hash over the denomination public key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+  /**
+   * 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_TimestampNBO 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_TimestampNBO refund_deadline;
+
+  /**
+   * Amount to be deposited, including deposit fee charged by the
+   * exchange.  This is the total amount that the coin's value at the exchange
+   * will be reduced by.
+   */
+  struct TALER_AmountNBO amount_with_fee;
+
+  /**
+   * Depositing fee charged by the exchange.  This must match the Exchange's
+   * denomination key's depositing fee.  If the client puts in an
+   * invalid deposit fee (too high or too low) that does not match the
+   * Exchange's denomination key, the deposit operation is invalid and
+   * will be rejected by the exchange.  The @e amount_with_fee minus the
+   * @e deposit_fee is the amount that will be transferred to the
+   * account identified by @e h_wire.
+   */
+  struct TALER_AmountNBO deposit_fee;
+
+  /**
+   * The Merchant's public key.  Allows the merchant to later refund
+   * the transaction or to inquire about the wire transfer identifier.
+   */
+  struct TALER_MerchantPublicKeyP merchant;
+
+  /**
+   * Hash over a JSON containing data provided by the
+   * wallet to complete the contract upon payment.
+   */
+  struct GNUNET_HashCode wallet_data_hash;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_deposit_sign (
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct GNUNET_HashCode *wallet_data_hash,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct GNUNET_TIME_Timestamp wallet_timestamp,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_DepositRequestPS dr = {
+    .purpose.size = htonl (sizeof (dr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire,
+    .h_denom_pub = *h_denom_pub,
+    .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
+    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+    .merchant = *merchant_pub
+  };
+
+  if (NULL != wallet_data_hash)
+    dr.wallet_data_hash = *wallet_data_hash;
+  if (NULL != h_age_commitment)
+    dr.h_age_commitment = *h_age_commitment;
+  if (NULL != h_policy)
+    dr.h_policy = *h_policy;
+  TALER_amount_hton (&dr.amount_with_fee,
+                     amount);
+  TALER_amount_hton (&dr.deposit_fee,
+                     deposit_fee);
+  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+                            &dr,
+                            &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_deposit_verify (
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct GNUNET_HashCode *wallet_data_hash,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_ExtensionPolicyHashP *h_policy,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  struct GNUNET_TIME_Timestamp wallet_timestamp,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  struct GNUNET_TIME_Timestamp refund_deadline,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_DepositRequestPS dr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT),
+    .purpose.size = htonl (sizeof (dr)),
+    .h_contract_terms = *h_contract_terms,
+    .h_wire = *h_wire,
+    .h_denom_pub = *h_denom_pub,
+    .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp),
+    .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline),
+    .merchant = *merchant_pub,
+  };
+
+  if (NULL != wallet_data_hash)
+    dr.wallet_data_hash = *wallet_data_hash;
+  if (NULL != h_age_commitment)
+    dr.h_age_commitment = *h_age_commitment;
+  if (NULL != h_policy)
+    dr.h_policy = *h_policy;
+  TALER_amount_hton (&dr.amount_with_fee,
+                     amount);
+  TALER_amount_hton (&dr.deposit_fee,
+                     deposit_fee);
+  if (GNUNET_OK !=
+      GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_DEPOSIT,
+                                  &dr,
+                                  &coin_sig->eddsa_signature,
+                                  &coin_pub->eddsa_pub))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used for to allow the wallet to authenticate
+ * link data provided by the exchange.
+ */
+struct TALER_LinkDataPS
+{
+
+  /**
+   * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK.
+   * Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the denomination public key of the new coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Transfer public key (for which the private key was not revealed)
+   */
+  struct TALER_TransferPublicKeyP transfer_pub;
+
+  /**
+   * Hash of the age commitment, if applicable.  Can be all zero
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment;
+
+  /**
+   * Hash of the blinded new coin.
+   */
+  struct TALER_BlindedCoinHashP coin_envelope_hash;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_link_sign (const struct TALER_DenominationHashP *h_denom_pub,
+                        const struct TALER_TransferPublicKeyP *transfer_pub,
+                        const struct TALER_BlindedCoinHashP *bch,
+                        const struct TALER_CoinSpendPrivateKeyP *old_coin_priv,
+                        struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_LinkDataPS ldp = {
+    .purpose.size = htonl (sizeof (ldp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
+    .h_denom_pub = *h_denom_pub,
+    .transfer_pub = *transfer_pub,
+    .coin_envelope_hash = *bch
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv,
+                            &ldp,
+                            &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_link_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_TransferPublicKeyP *transfer_pub,
+  const struct TALER_BlindedCoinHashP *h_coin_ev,
+  const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_LinkDataPS ldp = {
+    .purpose.size = htonl (sizeof (ldp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK),
+    .h_denom_pub = *h_denom_pub,
+    .transfer_pub = *transfer_pub,
+    .coin_envelope_hash = *h_coin_ev,
+  };
+
+  return
+    GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK,
+                                &ldp,
+                                &coin_sig->eddsa_signature,
+                                &old_coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Signed data to request that a coin should be refunded as part of
+ * the "emergency" /recoup protocol.  The refund will go back to the bank
+ * account that created the reserve.
+ */
+struct TALER_RecoupRequestPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_COIN_RECOUP
+   * or #TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash of the (revoked) denomination public key of the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub;
+
+  /**
+   * Blinding factor that was used to withdraw the coin.
+   */
+  union TALER_DenominationBlindingKeyP coin_blind;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RecoupRequestPS pr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
+    .purpose.size = htonl (sizeof (pr)),
+    .h_denom_pub = *h_denom_pub,
+    .coin_blind = *coin_bks
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP,
+                                     &pr,
+                                     &coin_sig->eddsa_signature,
+                                     &coin_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_recoup_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RecoupRequestPS pr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP),
+    .purpose.size = htonl (sizeof (pr)),
+    .h_denom_pub = *h_denom_pub,
+    .coin_blind = *coin_bks
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+                            &pr,
+                            &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_recoup_refresh_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RecoupRequestPS pr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
+    .purpose.size = htonl (sizeof (pr)),
+    .h_denom_pub = *h_denom_pub,
+    .coin_blind = *coin_bks
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify 
(TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH,
+                                     &pr,
+                                     &coin_sig->eddsa_signature,
+                                     &coin_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_recoup_refresh_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const union TALER_DenominationBlindingKeyP *coin_bks,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RecoupRequestPS pr = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH),
+    .purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)),
+    .h_denom_pub = *h_denom_pub,
+    .coin_blind = *coin_bks
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+                            &pr,
+                            &coin_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Message signed by a coin to indicate that the coin should be
+ * melted.
+ */
+struct TALER_RefreshMeltCoinAffirmationPS
+{
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT.
+   * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Which melt commitment is made by the wallet.
+   */
+  struct TALER_RefreshCommitmentP rc GNUNET_PACKED;
+
+  /**
+   * Hash over the denomination public key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+  /**
+   * If age commitment was provided during the withdrawal of the coin, this is
+   * the hash of the age commitment vector.  It must be all zeroes if no age
+   * commitment was provided.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+  /**
+   * How much of the value of the coin should be melted?  This amount
+   * includes the fees, so the final amount contributed to the melt is
+   * this value minus the fee for melting the coin.  We include the
+   * fee in what is being signed so that we can verify a reserve's
+   * remaining total balance without needing to access the respective
+   * denomination key information each time.
+   */
+  struct TALER_AmountNBO amount_with_fee;
+
+  /**
+   * Melting fee charged by the exchange.  This must match the Exchange's
+   * denomination key's melting fee.  If the client puts in an invalid
+   * melting fee (too high or too low) that does not match the Exchange's
+   * denomination key, the melting operation is invalid and will be
+   * rejected by the exchange.  The @e amount_with_fee minus the @e
+   * melt_fee is the amount that will be credited to the melting
+   * session.
+   */
+  struct TALER_AmountNBO melt_fee;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_melt_sign (
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *melt_fee,
+  const struct TALER_RefreshCommitmentP *rc,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RefreshMeltCoinAffirmationPS melt = {
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
+    .purpose.size = htonl (sizeof (melt)),
+    .rc = *rc,
+    .h_denom_pub = *h_denom_pub,
+    .h_age_commitment = {{{0}}},
+  };
+
+  if (NULL != h_age_commitment)
+    melt.h_age_commitment = *h_age_commitment;
+
+
+  TALER_amount_hton (&melt.amount_with_fee,
+                     amount_with_fee);
+  TALER_amount_hton (&melt.melt_fee,
+                     melt_fee);
+  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+                            &melt,
+                            &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_melt_verify (
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *melt_fee,
+  const struct TALER_RefreshCommitmentP *rc,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_RefreshMeltCoinAffirmationPS melt = {
+    .purpose.size = htonl (sizeof (melt)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT),
+    .rc = *rc,
+    .h_denom_pub = *h_denom_pub,
+    .h_age_commitment = {{{0}}},
+  };
+
+  if (NULL != h_age_commitment)
+    melt.h_age_commitment = *h_age_commitment;
+
+  TALER_amount_hton (&melt.amount_with_fee,
+                     amount_with_fee);
+  TALER_amount_hton (&melt.melt_fee,
+                     melt_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_COIN_MELT,
+    &melt,
+    &coin_sig->eddsa_signature,
+    &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * @brief Format used for to generate the signature on a request to withdraw
+ * coins from a reserve.
+ */
+struct TALER_WithdrawRequestPS
+{
+
+  /**
+   * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
+   * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Value of the coin being exchanged (matching the denomination key)
+   * plus the transaction fee.  We include this in what is being
+   * signed so that we can verify a reserve's remaining total balance
+   * without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_AmountNBO amount_with_fee;
+
+  /**
+   * Hash of the denomination public key for the coin that is withdrawn.
+   */
+  struct TALER_DenominationHashP h_denomination_pub GNUNET_PACKED;
+
+  /**
+   * Hash of the (blinded) message to be signed by the Exchange.
+   */
+  struct TALER_BlindedCoinHashP h_coin_envelope GNUNET_PACKED;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_withdraw_sign (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_BlindedCoinHashP *bch,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_WithdrawRequestPS req = {
+    .purpose.size = htonl (sizeof (req)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
+    .h_denomination_pub = *h_denom_pub,
+    .h_coin_envelope = *bch
+  };
+
+  TALER_amount_hton (&req.amount_with_fee,
+                     amount_with_fee);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &req,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_withdraw_verify (
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_BlindedCoinHashP *bch,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_WithdrawRequestPS wsrd = {
+    .purpose.size = htonl (sizeof (wsrd)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW),
+    .h_denomination_pub = *h_denom_pub,
+    .h_coin_envelope = *bch
+  };
+
+  TALER_amount_hton (&wsrd.amount_with_fee,
+                     amount_with_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
+    &wsrd,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * @brief Format used for to generate the signature on a request to
+ * age-withdraw from a reserve.
+ */
+struct TALER_AgeWithdrawRequestPS
+{
+
+  /**
+   * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW.
+   * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * The reserve's public key
+   */
+  struct TALER_ReservePublicKeyP reserve_pub;
+
+  /**
+   * Value of the coin being exchanged (matching the denomination key)
+   * plus the transaction fee.  We include this in what is being
+   * signed so that we can verify a reserve's remaining total balance
+   * without needing to access the respective denomination key
+   * information each time.
+   */
+  struct TALER_AmountNBO amount_with_fee;
+
+  /**
+   * Running SHA512 hash of the commitment of n*kappa coins
+   */
+  struct TALER_AgeWithdrawCommitmentHashP h_commitment;
+
+  /**
+   * The mask that defines the age groups.  MUST be the same for all 
denominations.
+   */
+  struct TALER_AgeMask mask;
+
+  /**
+   * Maximum age group that the coins are going to be restricted to.
+   */
+  uint8_t max_age_group;
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_age_withdraw_sign (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_AgeMask *mask,
+  uint8_t max_age,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AgeWithdrawRequestPS req = {
+    .purpose.size = htonl (sizeof (req)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW),
+    .h_commitment = *h_commitment,
+    .mask = *mask,
+    .max_age_group = TALER_get_age_group (mask, max_age)
+  };
+
+  GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
+                                      &req.reserve_pub.eddsa_pub);
+  TALER_amount_hton (&req.amount_with_fee,
+                     amount_with_fee);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &req,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_age_withdraw_verify (
+  const struct TALER_AgeWithdrawCommitmentHashP *h_commitment,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_AgeMask *mask,
+  uint8_t max_age,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AgeWithdrawRequestPS awsrd = {
+    .purpose.size = htonl (sizeof (awsrd)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW),
+    .reserve_pub = *reserve_pub,
+    .h_commitment = *h_commitment,
+    .mask = *mask,
+    .max_age_group = TALER_get_age_group (mask, max_age)
+  };
+
+  TALER_amount_hton (&awsrd.amount_with_fee,
+                     amount_with_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW,
+    &awsrd,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * @brief Format used for to generate the signature on a request to withdraw
+ * coins from a reserve.
+ */
+struct TALER_AccountSetupRequestSignaturePS
+{
+
+  /**
+   * Purpose must be #TALER_SIGNATURE_WALLET_ACCOUNT_SETUP.
+   * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`.
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Balance threshold the wallet is about to cross.
+   */
+  struct TALER_AmountNBO threshold;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_account_setup_sign (
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  const struct TALER_Amount *balance_threshold,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AccountSetupRequestSignaturePS asap = {
+    .purpose.size = htonl (sizeof (asap)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
+  };
+
+  TALER_amount_hton (&asap.threshold,
+                     balance_threshold);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &asap,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_setup_verify (
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_Amount *balance_threshold,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AccountSetupRequestSignaturePS asap = {
+    .purpose.size = htonl (sizeof (asap)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP)
+  };
+
+  TALER_amount_hton (&asap.threshold,
+                     balance_threshold);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_ACCOUNT_SETUP,
+    &asap,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+
+/**
+ * Response by which a wallet requests a full
+ * reserve history and indicates it is willing
+ * to pay for it.
+ */
+struct TALER_ReserveHistoryRequestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_HISTORY
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the wallet make the request.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+  /**
+   * How much does the exchange charge for the history?
+   */
+  struct TALER_AmountNBO history_fee;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_history_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveHistoryRequestPS rhr = {
+    .purpose.size = htonl (sizeof (rhr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  TALER_amount_hton (&rhr.history_fee,
+                     history_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_HISTORY,
+    &rhr,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_reserve_history_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_Amount *history_fee,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveHistoryRequestPS rhr = {
+    .purpose.size = htonl (sizeof (rhr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  TALER_amount_hton (&rhr.history_fee,
+                     history_fee);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &rhr,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Response by which a wallet requests an account status.
+ */
+struct TALER_ReserveStatusRequestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_STATUS
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When did the wallet make the request.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_status_verify (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveStatusRequestPS rsr = {
+    .purpose.size = htonl (sizeof (rsr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_STATUS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_RESERVE_STATUS,
+    &rsr,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_reserve_status_sign (
+  const struct GNUNET_TIME_Timestamp ts,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveStatusRequestPS rsr = {
+    .purpose.size = htonl (sizeof (rsr)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_STATUS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (ts)
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &rsr,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to create a purse (without reserve).
+ */
+struct TALER_PurseCreatePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_CREATE
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time when the purse will expire if still unmerged or unpaid.
+   */
+  struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+  /**
+   * Total amount (with fees) to be put into the purse.
+   */
+  struct TALER_AmountNBO purse_amount;
+
+  /**
+   * Contract this purse pays for.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Public key identifying the merge capability.
+   */
+  struct TALER_PurseMergePublicKeyP merge_pub;
+
+  /**
+   * Minimum age required for payments into this purse.
+   */
+  uint32_t min_age GNUNET_PACKED;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_purse_create_sign (
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  uint32_t min_age,
+  const struct TALER_Amount *amount,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseCreatePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .h_contract_terms = *h_contract_terms,
+    .merge_pub = *merge_pub,
+    .min_age = htonl (min_age)
+  };
+
+  TALER_amount_hton (&pm.purse_amount,
+                     amount);
+  GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
+                            &pm,
+                            &purse_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_create_verify (
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  uint32_t min_age,
+  const struct TALER_Amount *amount,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseCreatePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE),
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .h_contract_terms = *h_contract_terms,
+    .merge_pub = *merge_pub,
+    .min_age = htonl (min_age)
+  };
+
+  TALER_amount_hton (&pm.purse_amount,
+                     amount);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_PURSE_CREATE,
+    &pm,
+    &purse_sig->eddsa_signature,
+    &purse_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to delete a purse.
+ */
+struct TALER_PurseDeletePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DELETE
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+};
+
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_purse_delete_sign (
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseDeletePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
+  };
+
+  GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv,
+                            &pm,
+                            &purse_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_delete_verify (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseDeletePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE)
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_PURSE_DELETE,
+    &pm,
+    &purse_sig->eddsa_signature,
+    &purse_pub->eddsa_pub);
+}
+
+
+void
+TALER_wallet_purse_status_sign (
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+    .size = htonl (sizeof (purpose)),
+    .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
+  };
+
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
+                                            &purpose,
+                                            &purse_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_status_verify (
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose = {
+    .size = htonl (sizeof (purpose)),
+    .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS)
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_STATUS,
+                                      &purpose,
+                                      &purse_sig->eddsa_signature,
+                                      &purse_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to deposit a coin into a purse.
+ */
+struct TALER_PurseDepositPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DEPOSIT
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Amount (with deposit fee) to be deposited into the purse.
+   */
+  struct TALER_AmountNBO coin_amount;
+
+  /**
+   * Hash over the denomination public key used to sign the coin.
+   */
+  struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED;
+
+  /**
+   * Hash over the age commitment that went into the coin. Maybe all zero, if
+   * age commitment isn't applicable to the denomination.
+   */
+  struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED;
+
+  /**
+   * Purse to deposit funds into.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Hash of the base URL of the exchange hosting the
+   * @e purse_pub.
+   */
+  struct GNUNET_HashCode h_exchange_base_url GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_purse_deposit_sign (
+  const char *exchange_base_url,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_Amount *amount,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_PurseDepositPS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
+    .purse_pub = *purse_pub,
+    .h_denom_pub = *h_denom_pub,
+    .h_age_commitment = *h_age_commitment
+  };
+
+  GNUNET_CRYPTO_hash (exchange_base_url,
+                      strlen (exchange_base_url) + 1,
+                      &pm.h_exchange_base_url);
+  TALER_amount_hton (&pm.coin_amount,
+                     amount);
+  GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv,
+                            &pm,
+                            &coin_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_deposit_verify (
+  const char *exchange_base_url,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_Amount *amount,
+  const struct TALER_DenominationHashP *h_denom_pub,
+  const struct TALER_AgeCommitmentHash *h_age_commitment,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_PurseDepositPS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT),
+    .purse_pub = *purse_pub,
+    .h_denom_pub = *h_denom_pub,
+    .h_age_commitment = *h_age_commitment
+  };
+
+  GNUNET_CRYPTO_hash (exchange_base_url,
+                      strlen (exchange_base_url) + 1,
+                      &pm.h_exchange_base_url);
+  TALER_amount_hton (&pm.coin_amount,
+                     amount);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_PURSE_DEPOSIT,
+    &pm,
+    &coin_sig->eddsa_signature,
+    &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed to merge a purse into a reserve.
+ */
+struct TALER_PurseMergePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_MERGE
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time when the purse is merged into the reserve.
+   */
+  struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+  /**
+   * Which purse is being merged?
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Which reserve should the purse be merged with.
+   * Hash of the reserve's payto:// URI.
+   */
+  struct TALER_PaytoHashP h_payto;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_purse_merge_sign (
+  const char *reserve_uri,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePrivateKeyP *merge_priv,
+  struct TALER_PurseMergeSignatureP *merge_sig)
+{
+  struct TALER_PurseMergePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .purse_pub = *purse_pub
+  };
+
+  GNUNET_assert (0 ==
+                 strncasecmp (reserve_uri,
+                              "payto://taler-reserve",
+                              strlen ("payto://taler-reserve")));
+  TALER_payto_hash (reserve_uri,
+                    &pm.h_payto);
+  GNUNET_CRYPTO_eddsa_sign (&merge_priv->eddsa_priv,
+                            &pm,
+                            &merge_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_purse_merge_verify (
+  const char *reserve_uri,
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseMergePublicKeyP *merge_pub,
+  const struct TALER_PurseMergeSignatureP *merge_sig)
+{
+  struct TALER_PurseMergePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .purse_pub = *purse_pub
+  };
+
+  if (0 !=
+      strncasecmp (reserve_uri,
+                   "payto://taler-reserve",
+                   strlen ("payto://taler-reserve")))
+  {
+    GNUNET_break (0);
+    return GNUNET_NO;
+  }
+  TALER_payto_hash (reserve_uri,
+                    &pm.h_payto);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_PURSE_MERGE,
+    &pm,
+    &merge_sig->eddsa_signature,
+    &merge_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by account to merge a purse into a reserve.
+ */
+struct TALER_AccountMergePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_ACCOUNT_MERGE
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Time when the purse will expire if still unmerged or unpaid.
+   */
+  struct GNUNET_TIME_TimestampNBO purse_expiration;
+
+  /**
+   * Total amount (with fees) to be put into the purse.
+   */
+  struct TALER_AmountNBO purse_amount;
+
+  /**
+   * Purse creation fee to be paid by the reserve for
+   * this operation.
+   */
+  struct TALER_AmountNBO purse_fee;
+
+  /**
+   * Contract this purse pays for.
+   */
+  struct TALER_PrivateContractHashP h_contract_terms;
+
+  /**
+   * Purse to merge.
+   */
+  struct TALER_PurseContractPublicKeyP purse_pub;
+
+  /**
+   * Time when the purse is merged into the reserve.
+   */
+  struct GNUNET_TIME_TimestampNBO merge_timestamp;
+
+  /**
+   * Minimum age required for payments into this purse,
+   * in NBO.
+   */
+  uint32_t min_age GNUNET_PACKED;
+
+  /**
+   * Flags for the operation, in NBO. See
+   * `enum TALER_WalletAccountMergeFlags`.
+   */
+  uint32_t flags GNUNET_PACKED;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_account_merge_sign (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *purse_fee,
+  uint32_t min_age,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AccountMergePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .purse_pub = *purse_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .h_contract_terms = *h_contract_terms,
+    .min_age = htonl (min_age),
+    .flags = htonl ((uint32_t) flags)
+  };
+
+  TALER_amount_hton (&pm.purse_amount,
+                     amount);
+  TALER_amount_hton (&pm.purse_fee,
+                     purse_fee);
+  GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv,
+                            &pm,
+                            &reserve_sig->eddsa_signature);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_account_merge_verify (
+  struct GNUNET_TIME_Timestamp merge_timestamp,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  struct GNUNET_TIME_Timestamp purse_expiration,
+  const struct TALER_PrivateContractHashP *h_contract_terms,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *purse_fee,
+  uint32_t min_age,
+  enum TALER_WalletAccountMergeFlags flags,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_AccountMergePS pm = {
+    .purpose.size = htonl (sizeof (pm)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE),
+    .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp),
+    .purse_pub = *purse_pub,
+    .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration),
+    .h_contract_terms = *h_contract_terms,
+    .min_age = htonl (min_age),
+    .flags = htonl ((uint32_t) flags)
+  };
+
+  TALER_amount_hton (&pm.purse_amount,
+                     amount);
+  TALER_amount_hton (&pm.purse_fee,
+                     purse_fee);
+  return GNUNET_CRYPTO_eddsa_verify (
+    TALER_SIGNATURE_WALLET_ACCOUNT_MERGE,
+    &pm,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve key.
+ */
+struct TALER_ReserveOpenPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Amount to be paid from the reserve balance to open
+   * the reserve.
+   */
+  struct TALER_AmountNBO reserve_payment;
+
+  /**
+   * When was the request created.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+  /**
+   * For how long should the reserve be kept open.
+   * (Determines amount to be paid.)
+   */
+  struct GNUNET_TIME_TimestampNBO reserve_expiration;
+
+  /**
+   * How many open purses should be included with the
+   * open reserve?
+   * (Determines amount to be paid.)
+   */
+  uint32_t purse_limit GNUNET_PACKED;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_open_sign (
+  const struct TALER_Amount *reserve_payment,
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  struct GNUNET_TIME_Timestamp reserve_expiration,
+  uint32_t purse_limit,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveOpenPS rop = {
+    .purpose.size = htonl (sizeof (rop)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
+    .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
+    .purse_limit = htonl (purse_limit)
+  };
+
+  TALER_amount_hton (&rop.reserve_payment,
+                     reserve_payment);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+                                            &rop.purpose,
+                                            &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_verify (
+  const struct TALER_Amount *reserve_payment,
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  struct GNUNET_TIME_Timestamp reserve_expiration,
+  uint32_t purse_limit,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveOpenPS rop = {
+    .purpose.size = htonl (sizeof (rop)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp),
+    .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration),
+    .purse_limit = htonl (purse_limit)
+  };
+
+  TALER_amount_hton (&rop.reserve_payment,
+                     reserve_payment);
+  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_OPEN,
+                                      &rop.purpose,
+                                      &reserve_sig->eddsa_signature,
+                                      &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by
+ */
+struct TALER_ReserveOpenDepositPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Which reserve's opening signature should be paid for?
+   */
+  struct TALER_ReserveSignatureP reserve_sig;
+
+  /**
+   * Specifies how much of the coin's value should be spent on opening this
+   * reserve.
+   */
+  struct TALER_AmountNBO coin_contribution;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_open_deposit_sign (
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ReserveSignatureP *reserve_sig,
+  const struct TALER_CoinSpendPrivateKeyP *coin_priv,
+  struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_ReserveOpenDepositPS rod = {
+    .purpose.size = htonl (sizeof (rod)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
+    .reserve_sig = *reserve_sig
+  };
+
+  TALER_amount_hton (&rod.coin_contribution,
+                     coin_contribution);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&coin_priv->eddsa_priv,
+                                            &rod.purpose,
+                                            &coin_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_open_deposit_verify (
+  const struct TALER_Amount *coin_contribution,
+  const struct TALER_ReserveSignatureP *reserve_sig,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+  struct TALER_ReserveOpenDepositPS rod = {
+    .purpose.size = htonl (sizeof (rod)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT),
+    .reserve_sig = *reserve_sig
+  };
+
+  TALER_amount_hton (&rod.coin_contribution,
+                     coin_contribution);
+  return GNUNET_CRYPTO_eddsa_verify_ (
+    TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT,
+    &rod.purpose,
+    &coin_sig->eddsa_signature,
+    &coin_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve key.
+ */
+struct TALER_ReserveClosePS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_CLOSE
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When was the request created.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+  /**
+   * Hash of the payto://-URI of the target account
+   * for the closure, or all zeros for the reserve
+   * origin account.
+   */
+  struct TALER_PaytoHashP target_account_h_payto;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_close_sign (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveClosePS rcp = {
+    .purpose.size = htonl (sizeof (rcp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+  };
+
+  if (NULL != h_payto)
+    rcp.target_account_h_payto = *h_payto;
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+                                            &rcp.purpose,
+                                            &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_close_verify (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const struct TALER_PaytoHashP *h_payto,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveClosePS rcp = {
+    .purpose.size = htonl (sizeof (rcp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+  };
+
+  if (NULL != h_payto)
+    rcp.target_account_h_payto = *h_payto;
+  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_CLOSE,
+                                      &rcp.purpose,
+                                      &reserve_sig->eddsa_signature,
+                                      &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by reserve private key.
+ */
+struct TALER_ReserveAttestRequestPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_ATTEST_REQUEST
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * When was the request created.
+   */
+  struct GNUNET_TIME_TimestampNBO request_timestamp;
+
+  /**
+   * Hash over the JSON array of requested attributes.
+   */
+  struct GNUNET_HashCode h_details;
+
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+
+void
+TALER_wallet_reserve_attest_request_sign (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const json_t *details,
+  const struct TALER_ReservePrivateKeyP *reserve_priv,
+  struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveAttestRequestPS rcp = {
+    .purpose.size = htonl (sizeof (rcp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+  };
+
+  TALER_json_hash (details,
+                   &rcp.h_details);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv,
+                                            &rcp.purpose,
+                                            &reserve_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_reserve_attest_request_verify (
+  struct GNUNET_TIME_Timestamp request_timestamp,
+  const json_t *details,
+  const struct TALER_ReservePublicKeyP *reserve_pub,
+  const struct TALER_ReserveSignatureP *reserve_sig)
+{
+  struct TALER_ReserveAttestRequestPS rcp = {
+    .purpose.size = htonl (sizeof (rcp)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS),
+    .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp)
+  };
+
+  TALER_json_hash (details,
+                   &rcp.h_details);
+  return GNUNET_CRYPTO_eddsa_verify_ (
+    TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS,
+    &rcp.purpose,
+    &reserve_sig->eddsa_signature,
+    &reserve_pub->eddsa_pub);
+}
+
+
+GNUNET_NETWORK_STRUCT_BEGIN
+
+/**
+ * Message signed by purse to associate an encrypted contract.
+ */
+struct TALER_PurseContractPS
+{
+
+  /**
+   * Purpose is #TALER_SIGNATURE_WALLET_PURSE_ECONTRACT
+   */
+  struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+
+  /**
+   * Hash over the encrypted contract.
+   */
+  struct GNUNET_HashCode h_econtract;
+
+  /**
+   * Public key to decrypt the contract.
+   */
+  struct TALER_ContractDiffiePublicP contract_pub;
+};
+
+GNUNET_NETWORK_STRUCT_END
+
+void
+TALER_wallet_econtract_upload_sign (
+  const void *econtract,
+  size_t econtract_size,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPrivateKeyP *purse_priv,
+  struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseContractPS pc = {
+    .purpose.size = htonl (sizeof (pc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
+    .contract_pub = *contract_pub
+  };
+
+  GNUNET_CRYPTO_hash (econtract,
+                      econtract_size,
+                      &pc.h_econtract);
+  GNUNET_assert (GNUNET_OK ==
+                 GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv,
+                                            &pc.purpose,
+                                            &purse_sig->eddsa_signature));
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify2 (
+  const struct GNUNET_HashCode *h_econtract,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct TALER_PurseContractPS pc = {
+    .purpose.size = htonl (sizeof (pc)),
+    .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT),
+    .contract_pub = *contract_pub,
+    .h_econtract = *h_econtract
+  };
+
+  return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT,
+                                      &pc.purpose,
+                                      &purse_sig->eddsa_signature,
+                                      &purse_pub->eddsa_pub);
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_wallet_econtract_upload_verify (
+  const void *econtract,
+  size_t econtract_size,
+  const struct TALER_ContractDiffiePublicP *contract_pub,
+  const struct TALER_PurseContractPublicKeyP *purse_pub,
+  const struct TALER_PurseContractSignatureP *purse_sig)
+{
+  struct GNUNET_HashCode h_econtract;
+
+  GNUNET_CRYPTO_hash (econtract,
+                      econtract_size,
+                      &h_econtract);
+  return TALER_wallet_econtract_upload_verify2 (&h_econtract,
+                                                contract_pub,
+                                                purse_pub,
+                                                purse_sig);
+}
+
+
+/* end of wallet_signatures.c */
diff --git a/taler_config.h.in b/taler_config.h.in
new file mode 100644
index 0000000..f7300ad
--- /dev/null
+++ b/taler_config.h.in
@@ -0,0 +1,336 @@
+/* taler_config.h.in.  Generated from configure.ac by autoheader.  */
+
+/* Define to 1 if translation of program messages to the user's native
+   language is requested. */
+#undef ENABLE_NLS
+
+/* Define to 1 to enable epoll support */
+#undef EPOLL_SUPPORT
+
+/* Define to cull all logging calls */
+#undef GNUNET_CULL_LOGGING
+
+/* 1 if extra logging is enabled, 2 for very verbose extra logging, 0
+   otherwise */
+#undef GNUNET_EXTRA_LOGGING
+
+/* Define to 1 if you have the Mac OS X function CFLocaleCopyCurrent in the
+   CoreFoundation framework. */
+#undef HAVE_CFLOCALECOPYCURRENT
+
+/* Define to 1 if you have the Mac OS X function CFPreferencesCopyAppValue in
+   the CoreFoundation framework. */
+#undef HAVE_CFPREFERENCESCOPYAPPVALUE
+
+/* Define if the GNU dcgettext() function is already present or preinstalled.
+   */
+#undef HAVE_DCGETTEXT
+
+/* Define to 1 if you have the declaration of `CURLINFO_TLS_SESSION', and to 0
+   if you don't. */
+#undef HAVE_DECL_CURLINFO_TLS_SESSION
+
+/* 1 if developer logic is enabled, 0 otherwise */
+#undef HAVE_DEVELOPER
+
+/* Define to 1 if you have the <dlfcn.h> header file. */
+#undef HAVE_DLFCN_H
+
+/* Define if you have epoll_create1 function. */
+#undef HAVE_EPOLL_CREATE1
+
+/* Define if the GNU gettext() function is already present or preinstalled. */
+#undef HAVE_GETTEXT
+
+/* Define to 1 if you have the <gnunet/gnunet_curl_lib.h> header file. */
+#undef HAVE_GNUNET_GNUNET_CURL_LIB_H
+
+/* Define to 1 if you have the <gnunet/gnunet_json_lib.h> header file. */
+#undef HAVE_GNUNET_GNUNET_JSON_LIB_H
+
+/* Define to 1 if you have the <gnunet/gnunet_pq_lib.h> header file. */
+#undef HAVE_GNUNET_GNUNET_PQ_LIB_H
+
+/* Define to 1 if you have the <gnunet/gnunet_sq_lib.h> header file. */
+#undef HAVE_GNUNET_GNUNET_SQ_LIB_H
+
+/* Define to 1 if you have the <gnunet/gnunet_util_lib.h> header file. */
+#undef HAVE_GNUNET_GNUNET_UTIL_LIB_H
+
+/* Define if you have the iconv() function and it works. */
+#undef HAVE_ICONV
+
+/* Define to 1 if the system has the type `intmax_t'. */
+#undef HAVE_INTMAX_T
+
+/* Define to 1 if you have the <inttypes.h> header file. */
+#undef HAVE_INTTYPES_H
+
+/* Have libcurl */
+#undef HAVE_LIBCURL
+
+/* Lacking libgnurl */
+#undef HAVE_LIBGNURL
+
+/* Define to 1 if you have the <libpq-fe.h> header file. */
+#undef HAVE_LIBPQ_FE_H
+
+/* Define to 1 if the system has the type 'long long int'. */
+#undef HAVE_LONG_LONG_INT
+
+/* Define to 1 if you have the <netinet/in.h> header file. */
+#undef HAVE_NETINET_IN_H
+
+/* Define to 1 if you have the <netinet/ip.h> header file. */
+#undef HAVE_NETINET_IP_H
+
+/* Define to 1 if Postgres is available */
+#undef HAVE_POSTGRESQL
+
+/* Define to 1 if you have the <sqlite3.h> header file. */
+#undef HAVE_SQLITE3_H
+
+/* Define to 1 if you have the <stdint.h> header file. */
+#undef HAVE_STDINT_H
+
+/* Define to 1 if you have the <stdio.h> header file. */
+#undef HAVE_STDIO_H
+
+/* Define to 1 if you have the <stdlib.h> header file. */
+#undef HAVE_STDLIB_H
+
+/* Define to 1 if you have the `strdup' function. */
+#undef HAVE_STRDUP
+
+/* Define to 1 if you have the <strings.h> header file. */
+#undef HAVE_STRINGS_H
+
+/* Define to 1 if you have the <string.h> header file. */
+#undef HAVE_STRING_H
+
+/* Define to 1 if you have the <sys/socket.h> header file. */
+#undef HAVE_SYS_SOCKET_H
+
+/* Define to 1 if you have the <sys/stat.h> header file. */
+#undef HAVE_SYS_STAT_H
+
+/* Define to 1 if you have the <sys/types.h> header file. */
+#undef HAVE_SYS_TYPES_H
+
+/* Define to 1 if you have the <sys/un.h> header file. */
+#undef HAVE_SYS_UN_H
+
+/* Define to 1 if you have the <taler/taler_twister_service.h> header file. */
+#undef HAVE_TALER_TALER_TWISTER_SERVICE_H
+
+/* Define to 1 if the system has the type `uintmax_t'. */
+#undef HAVE_UINTMAX_T
+
+/* Define to 1 if you have the <unistd.h> header file. */
+#undef HAVE_UNISTD_H
+
+/* Define to 1 if the system has the type 'unsigned long long int'. */
+#undef HAVE_UNSIGNED_LONG_LONG_INT
+
+/* Defined if libcurl supports AsynchDNS */
+#undef LIBCURL_FEATURE_ASYNCHDNS
+
+/* Defined if libcurl supports IDN */
+#undef LIBCURL_FEATURE_IDN
+
+/* Defined if libcurl supports IPv6 */
+#undef LIBCURL_FEATURE_IPV6
+
+/* Defined if libcurl supports KRB4 */
+#undef LIBCURL_FEATURE_KRB4
+
+/* Defined if libcurl supports libz */
+#undef LIBCURL_FEATURE_LIBZ
+
+/* Defined if libcurl supports NTLM */
+#undef LIBCURL_FEATURE_NTLM
+
+/* Defined if libcurl supports SSL */
+#undef LIBCURL_FEATURE_SSL
+
+/* Defined if libcurl supports SSPI */
+#undef LIBCURL_FEATURE_SSPI
+
+/* Defined if libcurl supports DICT */
+#undef LIBCURL_PROTOCOL_DICT
+
+/* Defined if libcurl supports FILE */
+#undef LIBCURL_PROTOCOL_FILE
+
+/* Defined if libcurl supports FTP */
+#undef LIBCURL_PROTOCOL_FTP
+
+/* Defined if libcurl supports FTPS */
+#undef LIBCURL_PROTOCOL_FTPS
+
+/* Defined if libcurl supports HTTP */
+#undef LIBCURL_PROTOCOL_HTTP
+
+/* Defined if libcurl supports HTTPS */
+#undef LIBCURL_PROTOCOL_HTTPS
+
+/* Defined if libcurl supports IMAP */
+#undef LIBCURL_PROTOCOL_IMAP
+
+/* Defined if libcurl supports LDAP */
+#undef LIBCURL_PROTOCOL_LDAP
+
+/* Defined if libcurl supports POP3 */
+#undef LIBCURL_PROTOCOL_POP3
+
+/* Defined if libcurl supports RTSP */
+#undef LIBCURL_PROTOCOL_RTSP
+
+/* Defined if libcurl supports SMTP */
+#undef LIBCURL_PROTOCOL_SMTP
+
+/* Defined if libcurl supports TELNET */
+#undef LIBCURL_PROTOCOL_TELNET
+
+/* Defined if libcurl supports TFTP */
+#undef LIBCURL_PROTOCOL_TFTP
+
+/* Defined if libgnurl supports AsynchDNS */
+#undef LIBGNURL_FEATURE_ASYNCHDNS
+
+/* Defined if libgnurl supports IDN */
+#undef LIBGNURL_FEATURE_IDN
+
+/* Defined if libgnurl supports IPv6 */
+#undef LIBGNURL_FEATURE_IPV6
+
+/* Defined if libgnurl supports KRB4 */
+#undef LIBGNURL_FEATURE_KRB4
+
+/* Defined if libgnurl supports libz */
+#undef LIBGNURL_FEATURE_LIBZ
+
+/* Defined if libgnurl supports NTLM */
+#undef LIBGNURL_FEATURE_NTLM
+
+/* Defined if libgnurl supports SSL */
+#undef LIBGNURL_FEATURE_SSL
+
+/* Defined if libgnurl supports SSPI */
+#undef LIBGNURL_FEATURE_SSPI
+
+/* Defined if libgnurl supports DICT */
+#undef LIBGNURL_PROTOCOL_DICT
+
+/* Defined if libgnurl supports FILE */
+#undef LIBGNURL_PROTOCOL_FILE
+
+/* Defined if libgnurl supports FTP */
+#undef LIBGNURL_PROTOCOL_FTP
+
+/* Defined if libgnurl supports FTPS */
+#undef LIBGNURL_PROTOCOL_FTPS
+
+/* Defined if libgnurl supports HTTP */
+#undef LIBGNURL_PROTOCOL_HTTP
+
+/* Defined if libgnurl supports HTTPS */
+#undef LIBGNURL_PROTOCOL_HTTPS
+
+/* Defined if libgnurl supports IMAP */
+#undef LIBGNURL_PROTOCOL_IMAP
+
+/* Defined if libgnurl supports LDAP */
+#undef LIBGNURL_PROTOCOL_LDAP
+
+/* Defined if libgnurl supports POP3 */
+#undef LIBGNURL_PROTOCOL_POP3
+
+/* Defined if libgnurl supports RTSP */
+#undef LIBGNURL_PROTOCOL_RTSP
+
+/* Defined if libgnurl supports SMTP */
+#undef LIBGNURL_PROTOCOL_SMTP
+
+/* Defined if libgnurl supports TELNET */
+#undef LIBGNURL_PROTOCOL_TELNET
+
+/* Defined if libgnurl supports TFTP */
+#undef LIBGNURL_PROTOCOL_TFTP
+
+/* Define to the sub-directory where libtool stores uninstalled libraries. */
+#undef LT_OBJDIR
+
+/* minimum version of libgcrypt required */
+#undef NEED_LIBGCRYPT_VERSION
+
+/* Name of package */
+#undef PACKAGE
+
+/* Define to the address where bug reports for this package should be sent. */
+#undef PACKAGE_BUGREPORT
+
+/* Define to the full name of this package. */
+#undef PACKAGE_NAME
+
+/* Define to the full name and version of this package. */
+#undef PACKAGE_STRING
+
+/* Define to the one symbol short name of this package. */
+#undef PACKAGE_TARNAME
+
+/* Define to the home page for this package. */
+#undef PACKAGE_URL
+
+/* Define to the version of this package. */
+#undef PACKAGE_VERSION
+
+/* Define to 1 if all of the C90 standard headers exist (not just the ones
+   required in a freestanding environment). This macro is provided for
+   backward compatibility; new code need not use it. */
+#undef STDC_HEADERS
+
+/* VCS revision/hash or tarball version */
+#undef VCS_VERSION
+
+/* Version number of package */
+#undef VERSION
+
+/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT32_T
+
+/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>,
+   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
+   #define below would cause a syntax error. */
+#undef _UINT64_T
+
+/* Define curl_free() as free() if our version of curl lacks curl_free. */
+#undef curl_free
+
+/* Define to the widest signed integer type if <stdint.h> and <inttypes.h> do
+   not define. */
+#undef intmax_t
+
+/* Define as a signed integer type capable of holding a process identifier. */
+#undef pid_t
+
+/* Define to `unsigned int' if <sys/types.h> does not define. */
+#undef size_t
+
+/* Define to the type of an unsigned integer type of width exactly 16 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint16_t
+
+/* Define to the type of an unsigned integer type of width exactly 32 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint32_t
+
+/* Define to the type of an unsigned integer type of width exactly 64 bits if
+   such a type exists and the standard includes do not define it. */
+#undef uint64_t
+
+/* Define to the widest unsigned integer type if <stdint.h> and <inttypes.h>
+   do not define. */
+#undef uintmax_t
diff --git a/uncrustify.cfg b/uncrustify.cfg
new file mode 120000
index 0000000..f96cec5
--- /dev/null
+++ b/uncrustify.cfg
@@ -0,0 +1 @@
+contrib/uncrustify.cfg
\ No newline at end of file

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