gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (ad7171c9 -> 51300a1a)


From: gnunet
Subject: [libeufin] branch master updated (ad7171c9 -> 51300a1a)
Date: Tue, 23 Jan 2024 19:39:27 +0100

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

antoine pushed a change to branch master
in repository libeufin.

    from ad7171c9 Configure ebics logging via CLI flags
     new 73a98866 Share database logic
     new 8aeffb3f Share TalerAmount logic
     new bb7e455b Split utils into common and ebics and ename integration to 
testbench
     new 51300a1a Share IbanPayto logic and improve full IBAN payto logic

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .gitignore                                         |  11 +-
 Makefile                                           |  12 +-
 bank/build.gradle                                  |   6 +-
 .../tech/libeufin/bank/BankIntegrationApi.kt       |   2 +-
 bank/src/main/kotlin/tech/libeufin/bank/Config.kt  |   6 +-
 .../main/kotlin/tech/libeufin/bank/Constants.kt    |   8 +-
 .../kotlin/tech/libeufin/bank/ConversionApi.kt     |   3 +-
 .../main/kotlin/tech/libeufin/bank/CoreBankApi.kt  |   8 +-
 bank/src/main/kotlin/tech/libeufin/bank/Error.kt   |   3 +-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |  28 +--
 bank/src/main/kotlin/tech/libeufin/bank/Params.kt  |   3 +-
 .../main/kotlin/tech/libeufin/bank/RevenueApi.kt   |   2 +-
 .../main/kotlin/tech/libeufin/bank/TalerCommon.kt  | 182 +------------------
 .../main/kotlin/tech/libeufin/bank/TalerMessage.kt |  17 +-
 .../kotlin/tech/libeufin/bank/WireGatewayApi.kt    |   2 +-
 .../main/kotlin/tech/libeufin/bank/auth/auth.kt    |   4 +-
 .../kotlin/tech/libeufin/bank/db/AccountDAO.kt     |  18 +-
 .../kotlin/tech/libeufin/bank/db/CashoutDAO.kt     |   2 +-
 .../kotlin/tech/libeufin/bank/db/ConversionDAO.kt  |   2 +-
 .../main/kotlin/tech/libeufin/bank/db/Database.kt  |  65 +------
 .../kotlin/tech/libeufin/bank/db/ExchangeDAO.kt    |   2 +-
 .../tech/libeufin/bank/db/NotificationWatcher.kt   |   2 +-
 .../main/kotlin/tech/libeufin/bank/db/TanDAO.kt    |   2 +-
 .../main/kotlin/tech/libeufin/bank/db/TokenDAO.kt  |   2 +-
 .../kotlin/tech/libeufin/bank/db/TransactionDAO.kt |   4 +-
 .../kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt  |   4 +-
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt |   5 +-
 bank/src/test/kotlin/AmountTest.kt                 |  44 ++---
 bank/src/test/kotlin/BankIntegrationApiTest.kt     |   3 +-
 bank/src/test/kotlin/ConversionApiTest.kt          |   2 +-
 bank/src/test/kotlin/CoreBankApiTest.kt            |  30 ++-
 bank/src/test/kotlin/DatabaseTest.kt               |  36 +---
 bank/src/test/kotlin/JsonTest.kt                   |   1 +
 bank/src/test/kotlin/SecurityTest.kt               |   2 +-
 bank/src/test/kotlin/StatsTest.kt                  |   2 +-
 bank/src/test/kotlin/WireGatewayApiTest.kt         |   3 +-
 bank/src/test/kotlin/helpers.kt                    |  18 +-
 bank/src/test/kotlin/routines.kt                   |   3 +-
 build.gradle                                       |   2 +-
 {util => common}/build.gradle                      |   9 +-
 {util => common}/import.py                         |   2 +-
 {util => common}/src/main/kotlin/Backoff.kt        |   2 +-
 {util => common}/src/main/kotlin/Cli.kt            |   9 +-
 {util => common}/src/main/kotlin/Client.kt         |   3 +-
 {util => common}/src/main/kotlin/Config.kt         |   2 +-
 .../src/main/kotlin/Constants.kt                   |  17 +-
 {util => common}/src/main/kotlin/CryptoUtil.kt     |   3 +-
 {util => common}/src/main/kotlin/DB.kt             |  62 ++++++-
 {util => common}/src/main/kotlin/Encoding.kt       |   2 +-
 {util => common}/src/main/kotlin/HTTP.kt           |   2 +-
 common/src/main/kotlin/TalerCommon.kt              | 201 +++++++++++++++++++++
 {util => common}/src/main/kotlin/TalerConfig.kt    |   2 +
 {util => common}/src/main/kotlin/TalerErrorCode.kt |   2 +-
 {util => common}/src/main/kotlin/iban.kt           |   2 +-
 {util => common}/src/main/kotlin/strings.kt        |   3 +-
 {util => common}/src/main/kotlin/time.kt           |   2 +-
 .../src/main/resources/xsd/camt.052.001.02.xsd     |   0
 .../src/main/resources/xsd/camt.053.001.02.xsd     |   0
 .../src/main/resources/xsd/camt.054.001.02.xsd     |   0
 .../src/main/resources/xsd/ebics_H004.xsd          |   0
 .../src/main/resources/xsd/ebics_H005.xsd          |   0
 .../src/main/resources/xsd/ebics_hev.xsd           |   0
 .../resources/xsd/ebics_keymgmt_request_H004.xsd   |   0
 .../resources/xsd/ebics_keymgmt_request_H005.xsd   |   0
 .../resources/xsd/ebics_keymgmt_response_H004.xsd  |   0
 .../resources/xsd/ebics_keymgmt_response_H005.xsd  |   0
 .../src/main/resources/xsd/ebics_orders_H004.xsd   |   0
 .../src/main/resources/xsd/ebics_orders_H005.xsd   |   0
 .../src/main/resources/xsd/ebics_request_H004.xsd  |   0
 .../src/main/resources/xsd/ebics_request_H005.xsd  |   0
 .../src/main/resources/xsd/ebics_response_H004.xsd |   0
 .../src/main/resources/xsd/ebics_response_H005.xsd |   0
 .../main/resources/xsd/ebics_signature_S002.xsd    |   0
 .../src/main/resources/xsd/ebics_signatures.xsd    |   0
 .../src/main/resources/xsd/ebics_types_H004.xsd    |   0
 .../src/main/resources/xsd/ebics_types_H005.xsd    |   0
 .../main/resources/xsd/pain.001.001.03.ch.02.xsd   |   0
 .../src/main/resources/xsd/pain.001.001.03.xsd     |   0
 .../main/resources/xsd/pain.001.001.09.ch.03.xsd   |   0
 .../src/main/resources/xsd/pain.002.001.13.xsd     |   0
 .../src/main/resources/xsd/xmldsig-core-schema.xsd |   0
 common/src/test/kotlin/AmountTest.kt               |  62 +++++++
 {util => common}/src/test/kotlin/CryptoUtilTest.kt |   3 +-
 common/src/test/kotlin/PaytoTest.kt                |  80 ++++++++
 .../src/test/kotlin/TalerConfigTest.kt             |   1 +
 {util => common}/src/test/kotlin/TimeTest.kt       |   4 +-
 {util => common}/src/test/resources/ebics_hev.xml  |   0
 .../src/test/resources/ebics_ini_inner_key.xml     |   0
 .../test/resources/ebics_ini_request_sample.xml    |   0
 .../src/test/resources/hia_request.xml             |   0
 .../src/test/resources/hia_request_order_data.xml  |   0
 .../src/test/resources/hpb_request.xml             |   0
 .../src/test/resources/signature1/doc.xml          |   0
 .../src/test/resources/signature1/public_key.txt   |   0
 {util => ebics}/build.gradle                       |  12 +-
 {util => ebics}/import.py                          |   2 +-
 {util => ebics}/src/main/kotlin/Ebics.kt           |  15 +-
 {util => ebics}/src/main/kotlin/EbicsCodeSets.kt   |   2 +-
 {util => ebics}/src/main/kotlin/EbicsOrderUtil.kt  |   3 +-
 {util => ebics}/src/main/kotlin/XMLUtil.kt         |   4 +-
 {util => ebics}/src/main/kotlin/XmlCombinators.kt  |   2 +-
 .../ebics_h004/EbicsKeyManagementResponse.kt       |   2 +-
 .../src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt |   2 +-
 .../src/main/kotlin/ebics_h004/EbicsRequest.kt     |   4 +-
 .../src/main/kotlin/ebics_h004/EbicsResponse.kt    |   6 +-
 .../src/main/kotlin/ebics_h004/EbicsTypes.kt       |   2 +-
 .../kotlin/ebics_h004/EbicsUnsecuredRequest.kt     |   6 +-
 .../main/kotlin/ebics_h004/HIARequestOrderData.kt  |   2 +-
 .../main/kotlin/ebics_h004/HKDResponseOrderData.kt |   2 +-
 .../main/kotlin/ebics_h004/HPBResponseOrderData.kt |   2 +-
 .../main/kotlin/ebics_h004/HTDResponseOrderData.kt |   2 +-
 .../src/main/kotlin/ebics_h004/package-info.java   |   2 +-
 .../src/main/kotlin/ebics_h005/Ebics3Request.kt    |   4 +-
 .../src/main/kotlin/ebics_h005/Ebics3Response.kt   |   8 +-
 .../src/main/kotlin/ebics_h005/Ebics3Types.kt      |   2 +-
 .../src/main/kotlin/ebics_h005/package-info.java   |   2 +-
 .../src/main/kotlin/ebics_hev/EbicsMessages.kt     |   2 +-
 .../src/main/kotlin/ebics_hev/package-info.java    |   2 +-
 .../src/main/kotlin/ebics_s001/SignatureTypes.kt   |   2 +-
 .../main/kotlin/ebics_s001/UserSignatureData.kt    |   2 +-
 .../src/main/kotlin/ebics_s001/package-info.java   |   2 +-
 .../src/main/kotlin/ebics_s002/SignatureTypes.kt   |   2 +-
 .../kotlin/ebics_s002/UserSignatureDataEbics3.kt   |   2 +-
 .../src/main/kotlin/ebics_s002/package-info.java   |   2 +-
 ebics/src/main/resources/version.txt               |   1 +
 .../src/main/resources/xsd/camt.052.001.02.xsd     |   0
 .../src/main/resources/xsd/camt.053.001.02.xsd     |   0
 .../src/main/resources/xsd/camt.054.001.02.xsd     |   0
 .../src/main/resources/xsd/ebics_H004.xsd          |   0
 .../src/main/resources/xsd/ebics_H005.xsd          |   0
 .../src/main/resources/xsd/ebics_hev.xsd           |   0
 .../resources/xsd/ebics_keymgmt_request_H004.xsd   |   0
 .../resources/xsd/ebics_keymgmt_request_H005.xsd   |   0
 .../resources/xsd/ebics_keymgmt_response_H004.xsd  |   0
 .../resources/xsd/ebics_keymgmt_response_H005.xsd  |   0
 .../src/main/resources/xsd/ebics_orders_H004.xsd   |   0
 .../src/main/resources/xsd/ebics_orders_H005.xsd   |   0
 .../src/main/resources/xsd/ebics_request_H004.xsd  |   0
 .../src/main/resources/xsd/ebics_request_H005.xsd  |   0
 .../src/main/resources/xsd/ebics_response_H004.xsd |   0
 .../src/main/resources/xsd/ebics_response_H005.xsd |   0
 .../main/resources/xsd/ebics_signature_S002.xsd    |   0
 .../src/main/resources/xsd/ebics_signatures.xsd    |   0
 .../src/main/resources/xsd/ebics_types_H004.xsd    |   0
 .../src/main/resources/xsd/ebics_types_H005.xsd    |   0
 .../main/resources/xsd/pain.001.001.03.ch.02.xsd   |   0
 .../src/main/resources/xsd/pain.001.001.03.xsd     |   0
 .../main/resources/xsd/pain.001.001.09.ch.03.xsd   |   0
 .../src/main/resources/xsd/pain.002.001.13.xsd     |   0
 .../src/main/resources/xsd/xmldsig-core-schema.xsd |   0
 .../src/test/kotlin/EbicsMessagesTest.kt           |  12 +-
 .../src/test/kotlin/EbicsOrderUtilTest.kt          |   6 +-
 .../src/test/kotlin/SignatureDataTest.kt           |   8 +-
 .../src/test/kotlin/XmlCombinatorsTest.kt          |   4 +-
 {util => ebics}/src/test/kotlin/XmlUtilTest.kt     |  14 +-
 {util => ebics}/src/test/resources/ebics_hev.xml   |   0
 .../src/test/resources/ebics_ini_inner_key.xml     |   0
 .../test/resources/ebics_ini_request_sample.xml    |   0
 {util => ebics}/src/test/resources/hia_request.xml |   0
 .../src/test/resources/hia_request_order_data.xml  |   0
 {util => ebics}/src/test/resources/hpb_request.xml |   0
 .../src/test/resources/signature1/doc.xml          |   0
 .../src/test/resources/signature1/public_key.txt   |   0
 nexus/build.gradle                                 |   5 +-
 .../main/kotlin/tech/libeufin/nexus/Database.kt    | 141 ++++-----------
 .../src/main/kotlin/tech/libeufin/nexus/DbInit.kt  |   2 +-
 .../main/kotlin/tech/libeufin/nexus/EbicsFetch.kt  |  56 +-----
 .../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt  |   8 +-
 .../main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt |  12 +-
 .../main/kotlin/tech/libeufin/nexus/Iso20022.kt    |  22 +--
 nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt   |   2 +-
 nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt  |   6 +-
 .../kotlin/tech/libeufin/nexus/ebics/Ebics2.kt     |   6 +-
 .../kotlin/tech/libeufin/nexus/ebics/Ebics3.kt     |  10 +-
 .../tech/libeufin/nexus/ebics/EbicsCommon.kt       |   5 +-
 nexus/src/test/kotlin/CliTest.kt                   |   2 +-
 nexus/src/test/kotlin/Common.kt                    |   6 +-
 nexus/src/test/kotlin/ConfigLoading.kt             |   2 +-
 nexus/src/test/kotlin/DatabaseTest.kt              |  19 +-
 nexus/src/test/kotlin/Ebics.kt                     |   4 +-
 nexus/src/test/kotlin/Keys.kt                      |   2 +-
 nexus/src/test/kotlin/MySerializers.kt             |   4 +-
 nexus/src/test/kotlin/Parsing.kt                   |  61 +------
 settings.gradle                                    |   5 +-
 {integration => testbench}/build.gradle            |   6 +-
 {integration => testbench}/conf/integration.conf   |   0
 {integration => testbench}/conf/mini.conf          |   0
 {integration => testbench}/conf/netzbon.conf       |   0
 {integration => testbench}/conf/postfinance.conf   |   0
 {integration => testbench}/src/main/kotlin/Main.kt |  20 +-
 .../src/test/kotlin/IntegrationTest.kt             |  66 +++----
 util/src/main/kotlin/IbanPayto.kt                  | 104 -----------
 util/src/test/kotlin/PaytoTest.kt                  |  51 ------
 193 files changed, 768 insertions(+), 1034 deletions(-)
 copy {util => common}/build.gradle (76%)
 copy {util => common}/import.py (98%)
 rename {util => common}/src/main/kotlin/Backoff.kt (97%)
 rename {util => common}/src/main/kotlin/Cli.kt (96%)
 rename {util => common}/src/main/kotlin/Client.kt (97%)
 rename {util => common}/src/main/kotlin/Config.kt (97%)
 copy bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt => 
common/src/main/kotlin/Constants.kt (67%)
 rename {util => common}/src/main/kotlin/CryptoUtil.kt (99%)
 rename {util => common}/src/main/kotlin/DB.kt (83%)
 rename {util => common}/src/main/kotlin/Encoding.kt (99%)
 rename {util => common}/src/main/kotlin/HTTP.kt (98%)
 create mode 100644 common/src/main/kotlin/TalerCommon.kt
 rename {util => common}/src/main/kotlin/TalerConfig.kt (99%)
 rename {util => common}/src/main/kotlin/TalerErrorCode.kt (99%)
 rename {util => common}/src/main/kotlin/iban.kt (98%)
 rename {util => common}/src/main/kotlin/strings.kt (97%)
 rename {util => common}/src/main/kotlin/time.kt (99%)
 copy {util => common}/src/main/resources/xsd/camt.052.001.02.xsd (100%)
 copy {util => common}/src/main/resources/xsd/camt.053.001.02.xsd (100%)
 copy {util => common}/src/main/resources/xsd/camt.054.001.02.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_H004.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_H005.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_hev.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd 
(100%)
 copy {util => common}/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd 
(100%)
 copy {util => common}/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd 
(100%)
 copy {util => common}/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd 
(100%)
 copy {util => common}/src/main/resources/xsd/ebics_orders_H004.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_orders_H005.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_request_H004.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_request_H005.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_response_H004.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_response_H005.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_signature_S002.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_signatures.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_types_H004.xsd (100%)
 copy {util => common}/src/main/resources/xsd/ebics_types_H005.xsd (100%)
 copy {util => common}/src/main/resources/xsd/pain.001.001.03.ch.02.xsd (100%)
 copy {util => common}/src/main/resources/xsd/pain.001.001.03.xsd (100%)
 copy {util => common}/src/main/resources/xsd/pain.001.001.09.ch.03.xsd (100%)
 copy {util => common}/src/main/resources/xsd/pain.002.001.13.xsd (100%)
 copy {util => common}/src/main/resources/xsd/xmldsig-core-schema.xsd (100%)
 create mode 100644 common/src/test/kotlin/AmountTest.kt
 rename {util => common}/src/test/kotlin/CryptoUtilTest.kt (99%)
 create mode 100644 common/src/test/kotlin/PaytoTest.kt
 rename {util => common}/src/test/kotlin/TalerConfigTest.kt (98%)
 rename {util => common}/src/test/kotlin/TimeTest.kt (95%)
 copy {util => common}/src/test/resources/ebics_hev.xml (100%)
 copy {util => common}/src/test/resources/ebics_ini_inner_key.xml (100%)
 copy {util => common}/src/test/resources/ebics_ini_request_sample.xml (100%)
 copy {util => common}/src/test/resources/hia_request.xml (100%)
 copy {util => common}/src/test/resources/hia_request_order_data.xml (100%)
 copy {util => common}/src/test/resources/hpb_request.xml (100%)
 copy {util => common}/src/test/resources/signature1/doc.xml (100%)
 copy {util => common}/src/test/resources/signature1/public_key.txt (100%)
 rename {util => ebics}/build.gradle (61%)
 rename {util => ebics}/import.py (98%)
 rename {util => ebics}/src/main/kotlin/Ebics.kt (96%)
 rename {util => ebics}/src/main/kotlin/EbicsCodeSets.kt (99%)
 rename {util => ebics}/src/main/kotlin/EbicsOrderUtil.kt (97%)
 rename {util => ebics}/src/main/kotlin/XMLUtil.kt (99%)
 rename {util => ebics}/src/main/kotlin/XmlCombinators.kt (99%)
 rename {util => 
ebics}/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt (98%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/EbicsRequest.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/EbicsResponse.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/EbicsTypes.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt 
(98%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/HIARequestOrderData.kt (96%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt (92%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt (95%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt (91%)
 rename {util => ebics}/src/main/kotlin/ebics_h004/package-info.java (76%)
 rename {util => ebics}/src/main/kotlin/ebics_h005/Ebics3Request.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h005/Ebics3Response.kt (98%)
 rename {util => ebics}/src/main/kotlin/ebics_h005/Ebics3Types.kt (99%)
 rename {util => ebics}/src/main/kotlin/ebics_h005/package-info.java (78%)
 rename {util => ebics}/src/main/kotlin/ebics_hev/EbicsMessages.kt (98%)
 rename {util => ebics}/src/main/kotlin/ebics_hev/package-info.java (89%)
 rename {util => ebics}/src/main/kotlin/ebics_s001/SignatureTypes.kt (98%)
 rename {util => ebics}/src/main/kotlin/ebics_s001/UserSignatureData.kt (95%)
 rename {util => ebics}/src/main/kotlin/ebics_s001/package-info.java (76%)
 rename {util => ebics}/src/main/kotlin/ebics_s002/SignatureTypes.kt (98%)
 rename {util => ebics}/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt 
(95%)
 rename {util => ebics}/src/main/kotlin/ebics_s002/package-info.java (76%)
 create mode 100644 ebics/src/main/resources/version.txt
 rename {util => ebics}/src/main/resources/xsd/camt.052.001.02.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/camt.053.001.02.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/camt.054.001.02.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_H004.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_H005.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_hev.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd 
(100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd 
(100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd 
(100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd 
(100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_orders_H004.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_orders_H005.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_request_H004.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_request_H005.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_response_H004.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_response_H005.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_signature_S002.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_signatures.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_types_H004.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/ebics_types_H005.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/pain.001.001.03.ch.02.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/pain.001.001.03.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/pain.001.001.09.ch.03.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/pain.002.001.13.xsd (100%)
 rename {util => ebics}/src/main/resources/xsd/xmldsig-core-schema.xsd (100%)
 rename {util => ebics}/src/test/kotlin/EbicsMessagesTest.kt (98%)
 rename {util => ebics}/src/test/kotlin/EbicsOrderUtilTest.kt (99%)
 rename {util => ebics}/src/test/kotlin/SignatureDataTest.kt (95%)
 rename {util => ebics}/src/test/kotlin/XmlCombinatorsTest.kt (96%)
 rename {util => ebics}/src/test/kotlin/XmlUtilTest.kt (95%)
 rename {util => ebics}/src/test/resources/ebics_hev.xml (100%)
 rename {util => ebics}/src/test/resources/ebics_ini_inner_key.xml (100%)
 rename {util => ebics}/src/test/resources/ebics_ini_request_sample.xml (100%)
 rename {util => ebics}/src/test/resources/hia_request.xml (100%)
 rename {util => ebics}/src/test/resources/hia_request_order_data.xml (100%)
 rename {util => ebics}/src/test/resources/hpb_request.xml (100%)
 rename {util => ebics}/src/test/resources/signature1/doc.xml (100%)
 rename {util => ebics}/src/test/resources/signature1/public_key.txt (100%)
 rename {integration => testbench}/build.gradle (86%)
 rename {integration => testbench}/conf/integration.conf (100%)
 rename {integration => testbench}/conf/mini.conf (100%)
 rename {integration => testbench}/conf/netzbon.conf (100%)
 rename {integration => testbench}/conf/postfinance.conf (100%)
 rename {integration => testbench}/src/main/kotlin/Main.kt (92%)
 rename {integration => testbench}/src/test/kotlin/IntegrationTest.kt (84%)
 delete mode 100644 util/src/main/kotlin/IbanPayto.kt
 delete mode 100644 util/src/test/kotlin/PaytoTest.kt

diff --git a/.gitignore b/.gitignore
index 8d1b734f..d046aaa3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,8 @@
 .idea/*
 .vscode
-/nexus/bin/
-/sandbox/bin/
-/util/bin/
-nexus/libeufin-nexus-dev
 nexus/test
-integration/test
-integration/config.json
-sandbox/libeufin-sandbox-dev
+testbench/test
+testbench/config.json
 configure
 build/
 .gradle
@@ -26,7 +21,7 @@ __pycache__
 *.log
 .DS_Store
 *.mk
-util/src/main/resources/version.txt
+common/src/main/resources/version.txt
 debian/usr/share/libeufin/demobank-ui/index.js
 debian/usr/share/libeufin/demobank-ui/*.html
 debian/usr/share/libeufin/demobank-ui/*.css
diff --git a/Makefile b/Makefile
index e4c393f6..9a63ae7e 100644
--- a/Makefile
+++ b/Makefile
@@ -110,13 +110,13 @@ bank-test: install-nobuild-bank-files
 nexus-test: install-nobuild-nexus-files
        ./gradlew :nexus:test --tests $(test) -i
 
-.PHONY: integration-test
-integration-test: install-nobuild-bank-files install-nobuild-nexus-files
-       ./gradlew :integration:test --tests $(test) -i
+.PHONY: testbench-test
+testbench-test: install-nobuild-bank-files install-nobuild-nexus-files
+       ./gradlew :testbench:test --tests $(test) -i
 
-.PHONY: integration
-integration: install-nobuild-bank-files install-nobuild-nexus-files
-       ./gradlew :integration:run --console=plain --args="$(test)"
+.PHONY: testbench
+testbench: install-nobuild-bank-files install-nobuild-nexus-files
+       ./gradlew :testbench:run --console=plain --args="$(platform)"
 
 .PHONY: doc
 doc:
diff --git a/bank/build.gradle b/bank/build.gradle
index 0f0bb006..1e9558ba 100644
--- a/bank/build.gradle
+++ b/bank/build.gradle
@@ -21,11 +21,9 @@ dependencies {
     // Core language libraries
     
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
 
-    // LibEuFin util library
-    implementation(project(":util"))
+    implementation(project(":common"))
 
     implementation("org.postgresql:postgresql:$postgres_version")
-    implementation("com.zaxxer:HikariCP:5.0.1")
     implementation("com.github.ajalt.clikt:clikt:$clikt_version")
 
     implementation("io.ktor:ktor-server-core:$ktor_version")
@@ -42,7 +40,7 @@ dependencies {
     testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
     testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
 
-    testImplementation(project(":util"))
+    testImplementation(project(":common"))
 }
 
 application {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
index ae3419f4..be38286f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt
@@ -26,7 +26,7 @@ import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import io.ktor.http.*
-import net.taler.common.errorcodes.TalerErrorCode
+import tech.libeufin.common.TalerErrorCode
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.db.WithdrawalDAO.*
 import java.lang.AssertionError
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
index 4604ad04..a1dd7711 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -18,12 +18,10 @@
  */
 package tech.libeufin.bank
 
-import ConfigSource
-import TalerConfig
-import TalerConfigError
+import tech.libeufin.common.*
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.json.Json
-import tech.libeufin.util.DatabaseConfig
+import tech.libeufin.common.DatabaseConfig
 
 /**
  * Application the parsed configuration.
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
index a1c65035..ef647112 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt
@@ -18,7 +18,7 @@
  */
 package tech.libeufin.bank
 
-import ConfigSource
+import tech.libeufin.common.*
 import java.time.Duration
 
 // Config
@@ -39,11 +39,7 @@ const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5;
 // Security
 const val MAX_BODY_LENGTH: Long = 4 * 1024 // 4kB
 
-// DB
-const val MIN_VERSION: Int = 14
-const val SERIALIZATION_RETRY: Int = 10;
-
-// API version
+// API version 
 const val COREBANK_API_VERSION: String = "4:0:0"
 const val CONVERSION_API_VERSION: String = "0:0:0"
 const val INTEGRATION_API_VERSION: String = "2:0:2"
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
index d9e88942..6707eff0 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
@@ -24,11 +24,10 @@ import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import java.util.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.auth.*
 import tech.libeufin.bank.db.ConversionDAO.*
 import tech.libeufin.bank.db.*
-import net.taler.common.errorcodes.TalerErrorCode
 
 fun Routing.conversionApi(db: Database, ctx: BankConfig) = 
conditional(ctx.allowConversion) {
     get("/conversion-info/config") {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index 04616e88..d17842fe 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -32,8 +32,6 @@ import kotlinx.serialization.json.Json
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.future.await
 import kotlinx.coroutines.withContext
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.bank.*
@@ -45,7 +43,7 @@ import tech.libeufin.bank.db.CashoutDAO.*
 import tech.libeufin.bank.db.ExchangeDAO.*
 import tech.libeufin.bank.db.TransactionDAO.*
 import tech.libeufin.bank.db.WithdrawalDAO.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-api")
 
@@ -140,7 +138,7 @@ private fun Routing.coreBankTokenApi(db: Database) {
     }
 }
 
-suspend fun createAccount(db: Database, ctx: BankConfig, req: 
RegisterAccountRequest, isAdmin: Boolean): Pair<AccountCreationResult, 
IbanPayTo>  {
+suspend fun createAccount(db: Database, ctx: BankConfig, req: 
RegisterAccountRequest, isAdmin: Boolean): Pair<AccountCreationResult, 
IbanPayto>  {
     // Prohibit reserved usernames:
     if (RESERVED_ACCOUNTS.contains(req.username))
         throw conflict(
@@ -185,7 +183,7 @@ suspend fun createAccount(db: Database, ctx: BankConfig, 
req: RegisterAccountReq
     var retry = if (req.payto_uri == null) IBAN_ALLOCATION_RETRY_COUNTER else 0
 
     while (true) {
-        val internalPayto = req.payto_uri ?: IbanPayTo(genIbanPaytoUri())
+        val internalPayto = req.payto_uri ?: IbanPayto(genIbanPaytoUri())
         val res = db.account.create(
             login = req.username,
             name = req.name,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
index 04c3881e..eacf6a08 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
@@ -23,8 +23,7 @@ import io.ktor.server.response.*
 import io.ktor.server.application.ApplicationCall
 import io.ktor.util.AttributeKey
 import kotlinx.serialization.Serializable
-import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 /**
  * Convenience type to throw errors along the bank activity
  * and that is meant to be caught by Ktor and responded to the
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 89eac6d2..575efe6c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -48,14 +48,13 @@ import java.io.File
 import kotlinx.coroutines.*
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import org.slf4j.event.Level
 import org.postgresql.util.PSQLState
 import tech.libeufin.bank.db.AccountDAO.*
 import tech.libeufin.bank.db.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-bank")
 // Dirty local variable to stop the server in test TODO remove this ugly hack
@@ -172,13 +171,16 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankConfig) {
                     while (rootCause?.cause != null)
                         rootCause = rootCause.cause
                     // Telling apart invalid JSON vs missing parameter vs 
invalid parameter.
-                    val talerErrorCode = when (cause) {
-                        is MissingRequestParameterException ->
+                    val talerErrorCode = when {
+                        cause is MissingRequestParameterException ->
                             TalerErrorCode.GENERIC_PARAMETER_MISSING
-
-                        is ParameterConversionException ->
+                        cause is ParameterConversionException ->
                             TalerErrorCode.GENERIC_PARAMETER_MALFORMED
-
+                        rootCause is CommonError -> when (rootCause) {
+                            is CommonError.AmountFormat -> 
TalerErrorCode.BANK_BAD_FORMAT_AMOUNT
+                            is CommonError.AmountNumberTooBig -> 
TalerErrorCode.BANK_NUMBER_TOO_BIG
+                            is CommonError.IbanPayto -> 
TalerErrorCode.GENERIC_JSON_INVALID
+                        }
                         else -> TalerErrorCode.GENERIC_JSON_INVALID
                     }
                     call.err(
@@ -363,7 +365,7 @@ class EditAccount : CliktCommand(
     private val email: String? by option(help = "E-Mail address used for TAN 
transmission")
     private val phone: String? by option(help = "Phone number used for TAN 
transmission")
     private val tan_channel: String? by option(help = "which channel TAN 
challenges should be sent to")
-    private val cashout_payto_uri: IbanPayTo? by option(help = "Payto URI of a 
fiant account who receive cashout amount").convert { IbanPayTo(it) }
+    private val cashout_payto_uri: IbanPayto? by option(help = "Payto URI of a 
fiant account who receive cashout amount").convert { IbanPayto(it) }
     private val debit_threshold: TalerAmount? by option(help = "Max debit 
allowed for this account").convert { TalerAmount(it) }
  
     override fun run() = cliCmd(logger, common.log) {
@@ -424,13 +426,13 @@ class CreateAccountOption: OptionGroup() {
     ).flag()
     val email: String? by option(help = "E-Mail address used for TAN 
transmission")
     val phone: String? by option(help = "Phone number used for TAN 
transmission")
-    val cashout_payto_uri: IbanPayTo? by option(
+    val cashout_payto_uri: IbanPayto? by option(
         help = "Payto URI of a fiant account who receive cashout amount"
-    ).convert { IbanPayTo(it) }
-    val internal_payto_uri: IbanPayTo? by option(hidden = true).convert { 
IbanPayTo(it) }
-    val payto_uri: IbanPayTo? by option(
+    ).convert { IbanPayto(it) }
+    val internal_payto_uri: IbanPayto? by option(hidden = true).convert { 
IbanPayto(it) }
+    val payto_uri: IbanPayto? by option(
         help = "Payto URI of this account"
-    ).convert { IbanPayTo(it) }
+    ).convert { IbanPayto(it) }
     val debit_threshold: TalerAmount? by option(
         help = "Max debit allowed for this account")
     .convert { TalerAmount(it) }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Params.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Params.kt
index bdfa2dd8..7b3e53e8 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Params.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Params.kt
@@ -27,8 +27,7 @@ import io.ktor.server.util.*
 import java.time.*
 import java.time.temporal.*
 import java.util.*
-import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 fun Parameters.expect(name: String): String 
     = get(name) ?: throw badRequest("Missing '$name' parameter", 
TalerErrorCode.GENERIC_PARAMETER_MISSING)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
index 4575b106..7200de47 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/RevenueApi.kt
@@ -24,7 +24,7 @@ import io.ktor.server.request.*
 import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import java.util.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.auth.*
 import tech.libeufin.bank.db.*
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt
index fa135161..389ebc8b 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerCommon.kt
@@ -19,6 +19,7 @@
 
 package tech.libeufin.bank
 
+import tech.libeufin.common.*
 import io.ktor.http.*
 import io.ktor.serialization.kotlinx.json.*
 import io.ktor.server.application.*
@@ -32,9 +33,6 @@ import kotlinx.serialization.*
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.encoding.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
-import net.taler.wallet.crypto.EncodingException
 
 /** 32-byte Crockford's Base32 encoded data */
 @Serializable(with = Base32Crockford32B.Serializer::class)
@@ -197,98 +195,6 @@ data class TalerProtocolTimestamp(
     }
 }
 
-/**
- * Represents a Taler amount.  This type can be used both
- * to hold database records and amounts coming from the parser.
- * If maybeCurrency is null, then the constructor defaults it
- * to be the "internal currency".  Internal currency is the one
- * with which Libeufin-Bank moves funds within itself, therefore
- * not to be mistaken with the cashout currency, which is the one
- * that gets credited to Libeufin-Bank users to their cashout_payto_uri.
- *
- * maybeCurrency is typically null when the TalerAmount object gets
- * defined by the Database class.
- */
-@Serializable(with = TalerAmount.Serializer::class)
-class TalerAmount {
-    val value: Long
-    val frac: Int
-    val currency: String
-
-    constructor(value: Long, frac: Int, currency: String) {
-        this.value = value
-        this.frac = frac
-        this.currency = currency
-    }
-    constructor(encoded: String) {
-        val match = PATTERN.matchEntire(encoded) ?: throw badRequest(
-            "Invalid amount format",
-            TalerErrorCode.BANK_BAD_FORMAT_AMOUNT
-        );
-        val (currency, value, frac) = match.destructured
-        this.currency = currency
-        this.value = value.toLongOrNull() ?: throw badRequest(
-            "Invalid value",
-            TalerErrorCode.BANK_BAD_FORMAT_AMOUNT
-        )
-        if (this.value > MAX_VALUE) throw badRequest(
-            "Value specified in amount is too large",
-            TalerErrorCode.BANK_NUMBER_TOO_BIG
-        )
-        this.frac = if (frac.isEmpty()) {
-            0
-        } else {
-            var tmp = frac.toIntOrNull() ?: throw badRequest(
-                "Invalid fractional value",
-                TalerErrorCode.BANK_BAD_FORMAT_AMOUNT
-            )
-            if (tmp > FRACTION_BASE) throw badRequest(
-                "Fractional calue specified in amount is too large",
-                TalerErrorCode.BANK_NUMBER_TOO_BIG
-            )
-            repeat(8 - frac.length) {
-                tmp *= 10
-            }
-            tmp
-        }
-    }
-
-    override fun equals(other: Any?): Boolean {
-        return other is TalerAmount &&
-                other.value == this.value &&
-                other.frac == this.frac &&
-                other.currency == this.currency
-    }
-
-    override fun toString(): String {
-        if (frac == 0) {
-            return "$currency:$value"
-        } else {
-            return "$currency:$value.${frac.toString().padStart(8, '0')}"
-                .dropLastWhile { it == '0' } // Trim useless fractional 
trailing 0
-        }
-    }
-
-    internal object Serializer : KSerializer<TalerAmount> {
-        override val descriptor: SerialDescriptor =
-        PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING)
-    
-        override fun serialize(encoder: Encoder, value: TalerAmount) {
-            encoder.encodeString(value.toString())
-        }
-    
-        override fun deserialize(decoder: Decoder): TalerAmount {
-            return TalerAmount(decoder.decodeString())
-        }
-    }
-
-    companion object {
-        const val FRACTION_BASE = 100000000
-        const val MAX_VALUE = 4503599627370496L; // 2^52
-        private val PATTERN = 
Regex("([A-Z]{1,11}):([0-9]+)(?:\\.([0-9]{1,8}))?");
-    }
-}
-
 @Serializable(with = DecimalNumber.Serializer::class)
 class DecimalNumber {
     val value: Long
@@ -413,88 +319,4 @@ class ExchangeUrl {
             return ExchangeUrl(decoder.decodeString())
         }
     }
-}
-
-sealed class PaytoUri {
-    abstract val amount: TalerAmount?
-    abstract val message: String?
-    abstract val receiverName: String?
-}
-
-// TODO x-taler-bank Payto
-
-@Serializable(with = IbanPayTo.Serializer::class)
-class IbanPayTo: PaytoUri {
-    val parsed: URI
-    val canonical: String
-    val iban: String
-    override val amount: TalerAmount?
-    override val message: String?
-    override val receiverName: String?
-
-    constructor(raw: String) {
-        parsed = URI(raw)
-        require(parsed.scheme == "payto") { "expect a payto URI" }
-        require(parsed.host == "iban") { "expect a IBAN payto URI"  }
-
-        val splitPath = parsed.path.split("/").filter { it.isNotEmpty() }
-        require(splitPath.size < 3 && splitPath.isNotEmpty()) { "too many path 
segments" }
-        val rawIban = if (splitPath.size == 1) splitPath[0] else splitPath[1]
-        iban = rawIban.uppercase().replace(SEPARATOR, "")
-        checkIban(iban)
-        canonical = "payto://iban/$iban"
-    
-        val params = (parsed.query ?: "").parseUrlEncodedParameters();
-        amount = params["amount"]?.run { TalerAmount(this) }
-        message = params["message"]
-        receiverName = params["receiver-name"]
-    }
-
-    /** Canonical IBAN payto with receiver-name parameter if present */
-    fun maybeFull(): String {
-        return canonical + if (receiverName != null) ("?receiver-name=" + 
receiverName.encodeURLParameter()) else ""
-    }
-
-    /** Canonical IBAN payto with receiver-name parameter, fail if absent */
-    fun expectFull(): String {
-        return canonical + "?receiver-name=" + 
receiverName!!.encodeURLParameter()
-    }
-
-    /** Canonical IBAN payto with receiver-name parameter set to [defaultName] 
if absent */
-    fun fullOptName(defaultName: String): String {
-        return canonical + "?receiver-name=" + (receiverName ?: 
defaultName).encodeURLParameter()
-    }
-
-    override fun toString(): String = canonical
-
-    internal object Serializer : KSerializer<IbanPayTo> {
-        override val descriptor: SerialDescriptor =
-                PrimitiveSerialDescriptor("IbanPayTo", PrimitiveKind.STRING)
-
-        override fun serialize(encoder: Encoder, value: IbanPayTo) {
-            encoder.encodeString(value.parsed.toString())
-        }
-
-        override fun deserialize(decoder: Decoder): IbanPayTo {
-            return IbanPayTo(decoder.decodeString())
-        }
-    }
-
-    companion object {
-        private val SEPARATOR = Regex("[\\ \\-]");
-
-        fun checkIban(iban: String) {
-            val builder = StringBuilder(iban.length + iban.asSequence().map { 
if (it.isDigit()) 1 else 2 }.sum())
-            (iban.subSequence(4, iban.length).asSequence() + 
iban.subSequence(0, 4).asSequence()).forEach {
-                if (it.isDigit()) {
-                    builder.append(it)
-                } else {
-                    builder.append((it.code - 'A'.code) + 10)
-                }
-            }
-            val str = builder.toString()
-            val mod = str.toBigInteger().mod(97.toBigInteger()).toInt();
-            if (mod != 1) throw badRequest("Iban malformed, modulo is $mod 
expected 1")
-        }
-    }
-}
+}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index 3d27fbac..b4857085 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -19,11 +19,10 @@
 
 package tech.libeufin.bank
 
+import tech.libeufin.common.*
 import io.ktor.http.*
 import io.ktor.server.application.*
 import kotlinx.serialization.*
-import net.taler.wallet.crypto.Base32Crockford
-import net.taler.wallet.crypto.EncodingException
 import java.time.Duration
 import java.time.Instant
 import java.time.temporal.ChronoUnit
@@ -173,8 +172,8 @@ data class RegisterAccountRequest(
     val is_public: Boolean = false,
     val is_taler_exchange: Boolean = false,
     val contact_data: ChallengeContactData? = null,
-    val cashout_payto_uri: IbanPayTo? = null,
-    val payto_uri: IbanPayTo? = null,
+    val cashout_payto_uri: IbanPayto? = null,
+    val payto_uri: IbanPayto? = null,
     val debit_threshold: TalerAmount? = null,
     val tan_channel: TanChannel? = null,
 )
@@ -190,7 +189,7 @@ data class RegisterAccountResponse(
 @Serializable
 data class AccountReconfiguration(
     val contact_data: ChallengeContactData? = null,
-    val cashout_payto_uri: Option<IbanPayTo?> = Option.None,
+    val cashout_payto_uri: Option<IbanPayto?> = Option.None,
     val name: String? = null,
     val is_public: Boolean? = null,
     val debit_threshold: TalerAmount? = null,
@@ -359,7 +358,7 @@ data class AccountData(
 
 @Serializable
 data class TransactionCreateRequest(
-    val payto_uri: IbanPayTo,
+    val payto_uri: IbanPayto,
     val amount: TalerAmount?
 )
 
@@ -441,7 +440,7 @@ data class BankWithdrawalOperationStatus(
 @Serializable
 data class BankWithdrawalOperationPostRequest(
     val reserve_pub: EddsaPublicKey,
-    val selected_exchange: IbanPayTo,
+    val selected_exchange: IbanPayto,
 )
 
 /**
@@ -524,7 +523,7 @@ data class ConversionResponse(
 data class AddIncomingRequest(
     val amount: TalerAmount,
     val reserve_pub: EddsaPublicKey,
-    val debit_account: IbanPayTo
+    val debit_account: IbanPayto
 )
 
 /**
@@ -612,7 +611,7 @@ data class TransferRequest(
     val amount: TalerAmount,
     val exchange_base_url: ExchangeUrl,
     val wtid: ShortHashCode,
-    val credit_account: IbanPayTo
+    val credit_account: IbanPayto
 )
 
 /**
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
index fa376fd4..eba396a1 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
@@ -28,7 +28,7 @@ import io.ktor.server.response.*
 import io.ktor.server.routing.*
 import io.ktor.util.pipeline.PipelineContext
 import java.time.Instant
-import net.taler.common.errorcodes.TalerErrorCode
+import tech.libeufin.common.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.db.ExchangeDAO.*
 import tech.libeufin.bank.auth.*
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt
index 7898dad6..b7b5befe 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/auth/auth.kt
@@ -26,12 +26,10 @@ import io.ktor.server.response.header
 import io.ktor.util.AttributeKey
 import io.ktor.util.pipeline.PipelineContext
 import java.time.Instant
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
 import tech.libeufin.bank.db.AccountDAO.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 /** Used to store if the currenly authenticated user is admin */
 private val AUTH_IS_ADMIN = AttributeKey<Boolean>("is_admin");
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
index e9e30c52..cfd65156 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -19,7 +19,7 @@
 
 package tech.libeufin.bank.db
 
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.time.*
 import java.sql.Types
 import tech.libeufin.bank.*
@@ -41,8 +41,8 @@ class AccountDAO(private val db: Database) {
         name: String,
         email: String?,
         phone: String?,
-        cashoutPayto: IbanPayTo?,
-        internalPaytoUri: IbanPayTo,
+        cashoutPayto: IbanPayto?,
+        internalPaytoUri: IbanPayto,
         isPublic: Boolean,
         isTalerExchange: Boolean,
         maxDebt: TalerAmount,
@@ -71,7 +71,7 @@ class AccountDAO(private val db: Database) {
                 setString(1, name)
                 setString(2, email)
                 setString(3, phone)
-                setString(4, cashoutPayto?.fullOptName(name))
+                setString(4, cashoutPayto?.full(name)?.full)
                 setBoolean(5, checkPaytoIdempotent)
                 setString(6, internalPaytoUri.canonical)
                 setBoolean(7, isPublic)
@@ -122,7 +122,7 @@ class AccountDAO(private val db: Database) {
                     setString(3, name)
                     setString(4, email)
                     setString(5, phone)
-                    setString(6, cashoutPayto?.fullOptName(name))
+                    setString(6, cashoutPayto?.full(name)?.full)
                     setString(7, tanChannel?.name)
                     oneOrNull { it.getLong("customer_id") }!!
                 }
@@ -223,7 +223,7 @@ class AccountDAO(private val db: Database) {
     suspend fun reconfig(
         login: String,
         name: String?,
-        cashoutPayto: Option<IbanPayTo?>,
+        cashoutPayto: Option<IbanPayto?>,
         phone: Option<String?>,
         email: Option<String?>,
         tan_channel: Option<TanChannel?>,
@@ -292,12 +292,12 @@ class AccountDAO(private val db: Database) {
             null -> null
         }
         // Cashout payto with a receiver-name using if receiver-name is 
missing the new named if present or the current one 
-        val cashoutPaytoNamed = cashoutPayto.get()?.fullOptName(name ?: 
curr.name)
+        val fullCashoutPayto = cashoutPayto.get()?.full(name ?: curr.name)
 
         // Check reconfig rights
         if (checkName && name != curr.name) 
             return@transaction AccountPatchResult.NonAdminName
-        if (checkCashout && cashoutPaytoNamed != curr.cashoutPayTo) 
+        if (checkCashout && fullCashoutPayto?.full != curr.cashoutPayTo) 
             return@transaction AccountPatchResult.NonAdminCashout
         if (checkDebtLimit && debtLimit != curr.debtLimit)
             return@transaction AccountPatchResult.NonAdminDebtLimit
@@ -352,7 +352,7 @@ class AccountDAO(private val db: Database) {
             },
             "WHERE customer_id = ?",
             sequence {
-                cashoutPayto.some { yield(cashoutPaytoNamed) }
+                cashoutPayto.some { yield(fullCashoutPayto?.full) }
                 phone.some { yield(it) }
                 email.some { yield(it) }
                 tan_channel.some { yield(it?.name) }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
index a7950aa2..25e6ed61 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
@@ -22,7 +22,7 @@ package tech.libeufin.bank.db
 import java.time.Duration
 import java.time.Instant
 import java.util.concurrent.TimeUnit
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.*
 
 /** Data access logic for cashout operations */
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt
index b2d521c7..fc46b91e 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt
@@ -19,7 +19,7 @@
 
 package tech.libeufin.bank.db
 
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.*
 import tech.libeufin.bank.*
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
index 27cbfc2f..a0edbdec 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -32,10 +32,8 @@ import java.util.concurrent.TimeUnit
 import kotlin.math.abs
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.*
-import com.zaxxer.hikari.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import io.ktor.http.HttpStatusCode
-import net.taler.common.errorcodes.TalerErrorCode
 import tech.libeufin.bank.*
 
 private val logger: Logger = LoggerFactory.getLogger("libeufin-bank-db")
@@ -59,27 +57,8 @@ private val logger: Logger = 
LoggerFactory.getLogger("libeufin-bank-db")
 internal fun faultyTimestampByBank() = internalServerError("Bank took 
overflowing timestamp")
 internal fun faultyDurationByClient() = badRequest("Overflowing duration, 
please specify 'forever' instead.")
 
-class Database(dbConfig: String, internal val bankCurrency: String, internal 
val fiatCurrency: String?): java.io.Closeable {
-    val dbPool: HikariDataSource
-    internal val notifWatcher: NotificationWatcher
-
-    init {
-        val pgSource = pgDataSource(dbConfig)
-        val config = HikariConfig();
-        config.dataSource = pgSource
-        config.connectionInitSql = "SET search_path TO libeufin_bank;SET 
SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"
-        config.validate()
-        dbPool = HikariDataSource(config);
-        dbPool.getConnection().use { con -> 
-            val meta = con.getMetaData();
-            val majorVersion = meta.getDatabaseMajorVersion()
-            val minorVersion = meta.getDatabaseMinorVersion()
-            if (majorVersion < MIN_VERSION) {
-                throw Exception("postgres version must be at least 
$MIN_VERSION.0 got $majorVersion.$minorVersion")
-            }
-        }
-        notifWatcher = NotificationWatcher(pgSource)
-    }
+class Database(dbConfig: String, internal val bankCurrency: String, internal 
val fiatCurrency: String?): DbPool(dbConfig, "libeufin_bank") {
+    internal val notifWatcher: NotificationWatcher = 
NotificationWatcher(pgSource)
 
     val cashout = CashoutDAO(this)
     val withdrawal = WithdrawalDAO(this)
@@ -142,36 +121,6 @@ class Database(dbConfig: String, internal val 
bankCurrency: String, internal val
         } ?: throw internalServerError("No result from DB procedure 
stats_get_frame")
     }
 
-    override fun close() {
-        dbPool.close()
-    }
-
-    suspend fun <R> conn(lambda: suspend (PgConnection) -> R): R {
-        // Use a coroutine dispatcher that we can block as JDBC API is blocking
-        return withContext(Dispatchers.IO) {
-            val conn = dbPool.getConnection()
-            conn.use{ it -> lambda(it.unwrap(PgConnection::class.java)) }
-        }
-    }
-
-
-    suspend fun <R> serializable(lambda: suspend (PgConnection) -> R): R = 
conn { conn ->
-        repeat(SERIALIZATION_RETRY) {
-            try {
-                return@conn lambda(conn);
-            } catch (e: SQLException) {
-                if (e.sqlState != PSQLState.SERIALIZATION_FAILURE.state)
-                    throw e
-            }
-        }
-        try {
-            return@conn lambda(conn)
-        } catch(e: SQLException) {
-            logger.warn("Serialization failure after $SERIALIZATION_RETRY 
retry")
-            throw e
-        }
-    }
-
     /** Apply paging logic to a sql query */
     internal suspend fun <T> page(
         params: PageParams,
@@ -260,12 +209,4 @@ fun ResultSet.getTalerTimestamp(name: String): 
TalerProtocolTimestamp{
     return TalerProtocolTimestamp(
         getLong(name).microsToJavaInstant() ?: throw faultyTimestampByBank()
     )
-}
-
-fun ResultSet.getAmount(name: String, currency: String): TalerAmount{
-    return TalerAmount(
-        getLong("${name}_val"),
-        getInt("${name}_frac"),
-        currency
-    )
 }
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
index 44cb272c..3339bae6 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/ExchangeDAO.kt
@@ -23,7 +23,7 @@ import java.util.UUID
 import java.time.Instant
 import java.time.Duration
 import java.util.concurrent.TimeUnit
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.*
 
 /** Data access logic for exchange specific logic */
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt
index c17ffb58..973423e1 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/NotificationWatcher.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.*
 import org.postgresql.ds.PGSimpleDataSource
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.*
 
 private val logger: Logger = 
LoggerFactory.getLogger("libeufin-bank-db-watcher")
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
index 457d1216..3594b188 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt
@@ -19,7 +19,7 @@
 
 package tech.libeufin.bank.db
 
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.*
 import tech.libeufin.bank.db.*
 import java.util.concurrent.TimeUnit
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
index d3754938..92708308 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TokenDAO.kt
@@ -19,7 +19,7 @@
 
 package tech.libeufin.bank.db
 
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.time.Instant
 import tech.libeufin.bank.*
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
index 9c21fd53..b6038dc5 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/TransactionDAO.kt
@@ -21,7 +21,7 @@ package tech.libeufin.bank.db
 
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.time.*
 import java.sql.Types
 import tech.libeufin.bank.*
@@ -43,7 +43,7 @@ class TransactionDAO(private val db: Database) {
 
     /** Create a new transaction */
     suspend fun create(
-        creditAccountPayto: IbanPayTo,
+        creditAccountPayto: IbanPayto,
         debitAccountUsername: String,
         subject: String,
         amount: TalerAmount,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
index 380263b4..72b92490 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt
@@ -23,7 +23,7 @@ import java.util.UUID
 import java.time.Instant
 import java.time.Duration
 import java.util.concurrent.TimeUnit
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import kotlinx.coroutines.flow.*
 import kotlinx.coroutines.*
 import tech.libeufin.bank.*
@@ -102,7 +102,7 @@ class WithdrawalDAO(private val db: Database) {
     /** Set details ([exchangePayto] & [reservePub]) for withdrawal operation 
[uuid] */
     suspend fun setDetails(
         uuid: UUID,
-        exchangePayto: IbanPayTo,
+        exchangePayto: IbanPayto,
         reservePub: EddsaPublicKey
     ): WithdrawalSelectionResult = db.serializable { conn ->
         val stmt = conn.prepareStatement("""
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 47de2b15..b40223c6 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -36,8 +36,7 @@ import java.net.*
 import java.time.*
 import java.time.temporal.*
 import java.util.*
-import net.taler.common.errorcodes.TalerErrorCode
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.db.AccountDAO.*
 import tech.libeufin.bank.auth.*
@@ -126,7 +125,7 @@ suspend fun maybeCreateAdminAccount(db: Database, ctx: 
BankConfig, pw: String? =
         login = "admin",
         password = pwStr,
         name = "Bank administrator",
-        internalPaytoUri = IbanPayTo(genIbanPaytoUri()),
+        internalPaytoUri = IbanPayto(genIbanPaytoUri()),
         isPublic = false,
         isTalerExchange = false,
         maxDebt = ctx.defaultDebtLimit,
diff --git a/bank/src/test/kotlin/AmountTest.kt 
b/bank/src/test/kotlin/AmountTest.kt
index 43b6fbef..e23cdfda 100644
--- a/bank/src/test/kotlin/AmountTest.kt
+++ b/bank/src/test/kotlin/AmountTest.kt
@@ -26,13 +26,12 @@ import tech.libeufin.bank.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.db.TransactionDAO.*
 import tech.libeufin.bank.db.WithdrawalDAO.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class AmountTest {
     // Test amount computation in database
     @Test
-    fun computationTest() = bankSetup { db ->  
-        val conn = db.dbPool.getConnection().unwrap(PgConnection::class.java)
+    fun computationTest() = bankSetup { db -> db.conn { conn ->
         conn.execSQLUpdate("UPDATE libeufin_bank.bank_accounts SET balance.val 
= 100000 WHERE internal_payto_uri = '$customerPayto'")
         val stmt = conn.prepareStatement("""
             UPDATE libeufin_bank.bank_accounts 
@@ -125,29 +124,7 @@ class AmountTest {
             hasBalanceDebt = true,
             maxDebt = TalerAmount(0, 1, "KUDOS")
         ))
-
-
-    }
-
-    @Test
-    fun parse() {
-        assertEquals(TalerAmount("EUR:4"), TalerAmount(4L, 0, "EUR"))
-        assertEquals(TalerAmount("EUR:0.02"), TalerAmount(0L, 2000000, "EUR"))
-        assertEquals(TalerAmount("EUR:4.12"), TalerAmount(4L, 12000000, "EUR"))
-        assertEquals(TalerAmount("LOCAL:4444.1000"), TalerAmount(4444L, 
10000000, "LOCAL"))
-        assertEquals(TalerAmount("EUR:${TalerAmount.MAX_VALUE}.99999999"), 
TalerAmount(TalerAmount.MAX_VALUE, 99999999, "EUR"))
-
-        assertException("Invalid amount format") {TalerAmount("")}
-        assertException("Invalid amount format") {TalerAmount("EUR")}
-        assertException("Invalid amount format") {TalerAmount("eur:12")}
-        assertException("Invalid amount format") {TalerAmount(" EUR:12")}
-        assertException("Invalid amount format") {TalerAmount("EUR:1.")}
-        assertException("Invalid amount format") {TalerAmount("EUR:.1")}
-        assertException("Invalid amount format") 
{TalerAmount("AZERTYUIOPQSD:12")}
-        assertException("Value specified in amount is too large") 
{TalerAmount("EUR:${Long.MAX_VALUE}")}
-        assertException("Invalid amount format") 
{TalerAmount("EUR:4.000000000")}
-        assertException("Invalid amount format") {TalerAmount("EUR:4.4a")}
-    }
+    }}
 
     @Test
     fun parseRoundTrip() {
@@ -336,4 +313,19 @@ class AmountTest {
             }
         }
     }
+
+    @Test
+    fun apiError() = bankSetup { _ -> 
+        val base = obj {
+            "payto_uri" to "$exchangePayto?message=payout"
+        }
+
+        // Check OK
+        client.postA("/accounts/merchant/transactions") {
+            json(base) { "amount" to "KUDOS:0.3ABC" }
+        }.assertBadRequest(TalerErrorCode.BANK_BAD_FORMAT_AMOUNT)
+        client.postA("/accounts/merchant/transactions") {
+            json(base) { "amount" to "KUDOS:999999999999999999" }
+        }.assertBadRequest(TalerErrorCode.BANK_NUMBER_TOO_BIG)
+    }
 }
\ No newline at end of file
diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt 
b/bank/src/test/kotlin/BankIntegrationApiTest.kt
index ce6b46c7..d2205f21 100644
--- a/bank/src/test/kotlin/BankIntegrationApiTest.kt
+++ b/bank/src/test/kotlin/BankIntegrationApiTest.kt
@@ -26,11 +26,10 @@ import java.util.*
 import kotlin.test.*
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.bank.db.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class BankIntegrationApiTest {
     // GET /taler-integration/config
diff --git a/bank/src/test/kotlin/ConversionApiTest.kt 
b/bank/src/test/kotlin/ConversionApiTest.kt
index 588a4e54..a228705f 100644
--- a/bank/src/test/kotlin/ConversionApiTest.kt
+++ b/bank/src/test/kotlin/ConversionApiTest.kt
@@ -26,9 +26,9 @@ import java.util.*
 import kotlin.test.*
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
 import org.junit.Test
 import tech.libeufin.bank.*
+import tech.libeufin.common.*
 
 class ConversionApiTest {
     // GET /conversion-info/config
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt 
b/bank/src/test/kotlin/CoreBankApiTest.kt
index c9ecfa03..bd001feb 100644
--- a/bank/src/test/kotlin/CoreBankApiTest.kt
+++ b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -30,12 +30,10 @@ import java.util.*
 import kotlin.test.*
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.JsonElement
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.bank.db.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class CoreBankConfigTest {
     // GET /config
@@ -195,20 +193,20 @@ class CoreBankAccountsApiTest {
         }
 
         // Check given payto
-        val ibanPayto = IbanPayTo(genIbanPaytoUri())
+        val IbanPayto = IbanPayto(genIbanPaytoUri())
         val req = obj {
             "username" to "foo"
             "password" to "password"
             "name" to "Jane"
             "is_public" to true
-            "payto_uri" to ibanPayto
+            "payto_uri" to IbanPayto
             "is_taler_exchange" to true
         }
         // Check Ok
         client.post("/accounts") {
             json(req)
         }.assertOkJson<RegisterAccountResponse> {
-            assertEquals(ibanPayto.canonical, it.internal_payto_uri)
+            assertEquals(IbanPayto.canonical, it.internal_payto_uri)
         }
         // Testing idempotency
         client.post("/accounts") {
@@ -305,13 +303,13 @@ class CoreBankAccountsApiTest {
                 "username" to "cashout_guess"
                 "password" to "cashout_guess-password"
                 "name" to "Mr Guess My Name"
-                "cashout_payto_uri" to ibanPayto
+                "cashout_payto_uri" to IbanPayto
             }
         }.assertOk()
         client.getA("/accounts/cashout_guess").assertOkJson<AccountData> {
-            assertEquals(ibanPayto.fullOptName("Mr Guess My Name"), 
it.cashout_payto_uri)
+            assertEquals(IbanPayto.full("Mr Guess My Name").full, 
it.cashout_payto_uri)
         }
-        val full = ibanPayto.fullOptName("Santa Claus")
+        val full = IbanPayto.full("Santa Claus").full
         client.post("/accounts") {
             json {
                 "username" to "cashout_keep"
@@ -488,7 +486,7 @@ class CoreBankAccountsApiTest {
         }
 
         // Successful attempt now
-        val cashout = IbanPayTo(genIbanPaytoUri())
+        val cashout = IbanPayto(genIbanPaytoUri())
         val req = obj {
             "cashout_payto_uri" to cashout
             "name" to "Roger"
@@ -521,7 +519,7 @@ class CoreBankAccountsApiTest {
         // Check patch
         client.getA("/accounts/merchant").assertOkJson<AccountData> { obj ->
             assertEquals("Roger", obj.name)
-            assertEquals(cashout.fullOptName(obj.name), obj.cashout_payto_uri)
+            assertEquals(cashout.full(obj.name).full, obj.cashout_payto_uri)
             assertEquals("+99", obj.contact_data?.phone?.get())
             assertEquals("foo@example.com", obj.contact_data?.email?.get())
             assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold)
@@ -535,7 +533,7 @@ class CoreBankAccountsApiTest {
         }.assertNoContent()
         client.getA("/accounts/merchant").assertOkJson<AccountData> { obj ->
             assertEquals("Roger", obj.name)
-            assertEquals(cashout.fullOptName(obj.name), obj.cashout_payto_uri)
+            assertEquals(cashout.full(obj.name).full, obj.cashout_payto_uri)
             assertEquals("+99", obj.contact_data?.phone?.get())
             assertEquals("foo@example.com", obj.contact_data?.email?.get())
             assertEquals(TalerAmount("KUDOS:100"), obj.debit_threshold)
@@ -559,10 +557,10 @@ class CoreBankAccountsApiTest {
             }
         }.assertOk()
         for ((cashout, name, expect) in listOf(
-            Triple(cashout.canonical, null, cashout.fullOptName("Mr Cashout 
Cashout")),
-            Triple(cashout.canonical, "New name", cashout.fullOptName("New 
name")),
-            Triple(cashout.fullOptName("Full name"), null, 
cashout.fullOptName("Full name")),
-            Triple(cashout.fullOptName("Full second name"), "Another name", 
cashout.fullOptName("Full second name"))
+            Triple(cashout.canonical, null, cashout.full("Mr Cashout 
Cashout").full),
+            Triple(cashout.canonical, "New name", cashout.full("New 
name").full),
+            Triple(cashout.full("Full name").full, null, cashout.full("Full 
name").full),
+            Triple(cashout.full("Full second name").full, "Another name", 
cashout.full("Full second name").full)
         )) {
             client.patch("/accounts/cashout") {
                 pwAuth("admin")
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index de1e608f..01cb0dd2 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -30,7 +30,7 @@ import kotlinx.coroutines.*
 import org.junit.Test
 import tech.libeufin.bank.*
 import tech.libeufin.bank.db.AccountDAO.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class DatabaseTest {
     
@@ -168,40 +168,6 @@ class DatabaseTest {
             assertEquals(Triple(true, false, false), cTry(this, "new-code", 
expired))
         }
     }}
-
-    // Testing iban payto uri normalization
-    @Test
-    fun ibanPayto() = setup { _, _ ->
-        val canonical = "payto://iban/CH9300762011623852957"
-        val inputs = listOf(
-            "payto://iban/BIC/CH9300762011623852957?receiver-name=NotGiven",
-            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
-            "payto://iban/ch%209300-7620-1162-3852-957",
-        )
-        val names = listOf(
-            "NotGiven", "Grothoff Hans", null
-        )
-        val full = listOf(
-            "payto://iban/CH9300762011623852957?receiver-name=NotGiven",
-            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
-            canonical
-        )
-        for ((i, input) in inputs.withIndex()) {
-            val payto = IbanPayTo(input)
-            assertEquals(canonical, payto.canonical)
-            assertEquals(full[i], payto.maybeFull())
-            assertEquals(names[i], payto.receiverName)
-        }
-        
-        assertEquals(
-            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
-            
IbanPayTo("payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans").fullOptName("Santa
 Claus")
-        )
-        assertEquals(
-            "payto://iban/CH9300762011623852957?receiver-name=Santa%20Claus",
-            IbanPayTo("payto://iban/CH9300762011623852957").fullOptName("Santa 
Claus")
-        )
-    }
 }
 
 
diff --git a/bank/src/test/kotlin/JsonTest.kt b/bank/src/test/kotlin/JsonTest.kt
index 82af4d2d..3a7e5f21 100644
--- a/bank/src/test/kotlin/JsonTest.kt
+++ b/bank/src/test/kotlin/JsonTest.kt
@@ -25,6 +25,7 @@ import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import org.junit.Test
 import tech.libeufin.bank.*
+import tech.libeufin.common.*
 
 @Serializable
 data class MyJsonType(
diff --git a/bank/src/test/kotlin/SecurityTest.kt 
b/bank/src/test/kotlin/SecurityTest.kt
index c73140e2..b3c5f5ac 100644
--- a/bank/src/test/kotlin/SecurityTest.kt
+++ b/bank/src/test/kotlin/SecurityTest.kt
@@ -27,7 +27,7 @@ import kotlin.test.*
 import kotlinx.coroutines.*
 import org.junit.Test
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class SecurityTest {
     @Test
diff --git a/bank/src/test/kotlin/StatsTest.kt 
b/bank/src/test/kotlin/StatsTest.kt
index 4198dbe4..1aa1979e 100644
--- a/bank/src/test/kotlin/StatsTest.kt
+++ b/bank/src/test/kotlin/StatsTest.kt
@@ -28,7 +28,7 @@ import java.util.*
 import kotlin.test.*
 import org.junit.Test
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class StatsTest {
     @Test
diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt 
b/bank/src/test/kotlin/WireGatewayApiTest.kt
index 89c199ca..150de96a 100644
--- a/bank/src/test/kotlin/WireGatewayApiTest.kt
+++ b/bank/src/test/kotlin/WireGatewayApiTest.kt
@@ -25,10 +25,9 @@ import io.ktor.server.testing.*
 import java.util.*
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
 import org.junit.Test
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 class WireGatewayApiTest {
     // Testing the POST /transfer call from the TWG API.
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
index 951d24f5..69c0ede9 100644
--- a/bank/src/test/kotlin/helpers.kt
+++ b/bank/src/test/kotlin/helpers.kt
@@ -29,28 +29,26 @@ import kotlin.test.*
 import kotlin.random.Random
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
 import tech.libeufin.bank.*
 import tech.libeufin.bank.db.*
 import tech.libeufin.bank.db.AccountDAO.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 /* ----- Setup ----- */
 
-val merchantPayto = IbanPayTo(genIbanPaytoUri())
-val exchangePayto = IbanPayTo(genIbanPaytoUri())
-val customerPayto = IbanPayTo(genIbanPaytoUri())
-val unknownPayto  = IbanPayTo(genIbanPaytoUri())
-var tmpPayTo      = IbanPayTo(genIbanPaytoUri())
+val merchantPayto = IbanPayto(genIbanPaytoUri())
+val exchangePayto = IbanPayto(genIbanPaytoUri())
+val customerPayto = IbanPayto(genIbanPaytoUri())
+val unknownPayto  = IbanPayto(genIbanPaytoUri())
+var tmpPayTo      = IbanPayto(genIbanPaytoUri())
 val paytos = mapOf(
     "merchant" to merchantPayto, 
     "exchange" to exchangePayto, 
     "customer" to customerPayto
 )
 
-fun genTmpPayTo(): IbanPayTo {
-    tmpPayTo = IbanPayTo(genIbanPaytoUri())
+fun genTmpPayTo(): IbanPayto {
+    tmpPayTo = IbanPayto(genIbanPaytoUri())
     return tmpPayTo
 }
 
diff --git a/bank/src/test/kotlin/routines.kt b/bank/src/test/kotlin/routines.kt
index 92fdfd9c..6618b157 100644
--- a/bank/src/test/kotlin/routines.kt
+++ b/bank/src/test/kotlin/routines.kt
@@ -18,7 +18,7 @@
  */
 
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import io.ktor.client.statement.HttpResponse
 import io.ktor.server.testing.ApplicationTestBuilder
 import io.ktor.client.request.*
@@ -26,7 +26,6 @@ import io.ktor.http.*
 import kotlin.test.*
 import kotlinx.coroutines.*
 import kotlinx.serialization.json.*
-import net.taler.common.errorcodes.TalerErrorCode
 
 // Test endpoint is correctly authenticated 
 suspend fun ApplicationTestBuilder.authRoutine(
diff --git a/build.gradle b/build.gradle
index 87891a99..35df956e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -52,7 +52,7 @@ task versionFile() {
     }
     def gitHash = stdout.toString().trim()
     def version = getRootProject().version
-    new File("${projectDir}/util/src/main/resources", "version.txt").text = 
"v$version-git-$gitHash"
+    new File("${projectDir}/common/src/main/resources", "version.txt").text = 
"v$version-git-$gitHash"
 }
 
 // See: 
https://stackoverflow.com/questions/24936781/gradle-plugin-project-version-number
diff --git a/util/build.gradle b/common/build.gradle
similarity index 76%
copy from util/build.gradle
copy to common/build.gradle
index 0bd9b551..79b7fda3 100644
--- a/util/build.gradle
+++ b/common/build.gradle
@@ -1,8 +1,6 @@
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
 plugins {
-    id("java")
     id("kotlin")
+    id("org.jetbrains.kotlin.plugin.serialization") version "$kotlin_version"
 }
 
 version = rootProject.version
@@ -19,14 +17,11 @@ sourceSets.main.java.srcDirs = ["src/main/kotlin"]
 
 dependencies {
     implementation("ch.qos.logback:logback-classic:1.4.5")
-    // XML Stuff
-    implementation("javax.xml.bind:jaxb-api:2.3.1")
-    implementation("org.glassfish.jaxb:jaxb-runtime:2.3.1")
-    implementation("org.apache.santuario:xmlsec:2.2.2")
     // Crypto
     implementation("org.bouncycastle:bcprov-jdk15on:1.69")
     // Database helper
     implementation("org.postgresql:postgresql:$postgres_version")
+    implementation("com.zaxxer:HikariCP:5.0.1")
     
     implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
     implementation("io.ktor:ktor-server-test-host:$ktor_version")
diff --git a/util/import.py b/common/import.py
similarity index 98%
copy from util/import.py
copy to common/import.py
index e1389052..769f1a3d 100644
--- a/util/import.py
+++ b/common/import.py
@@ -56,7 +56,7 @@ kt = f"""/*
 
 // THIS FILE IS GENERATED, DO NOT EDIT
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 {extractCodeSet("ExternalStatusReason1Code", "ExternalStatusReasonCode")}
 
diff --git a/util/src/main/kotlin/Backoff.kt b/common/src/main/kotlin/Backoff.kt
similarity index 97%
rename from util/src/main/kotlin/Backoff.kt
rename to common/src/main/kotlin/Backoff.kt
index db31e266..fdbc358f 100644
--- a/util/src/main/kotlin/Backoff.kt
+++ b/common/src/main/kotlin/Backoff.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import kotlin.random.Random
 
diff --git a/util/src/main/kotlin/Cli.kt b/common/src/main/kotlin/Cli.kt
similarity index 96%
rename from util/src/main/kotlin/Cli.kt
rename to common/src/main/kotlin/Cli.kt
index 0c1803f5..2db1e906 100644
--- a/util/src/main/kotlin/Cli.kt
+++ b/common/src/main/kotlin/Cli.kt
@@ -17,11 +17,8 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
-import ConfigSource
-import TalerConfig
-import TalerConfigError
 import com.github.ajalt.clikt.core.*
 import com.github.ajalt.clikt.parameters.types.*
 import com.github.ajalt.clikt.parameters.arguments.*
@@ -41,11 +38,11 @@ fun cliCmd(logger: Logger, level: Level, lambda: () -> 
Unit) {
     try {
         lambda()
     } catch (e: Throwable) {
-        var msg = StringBuilder(e.message)
+        var msg = StringBuilder(e.message ?: e::class.simpleName)
         var cause = e.cause;
         while (cause != null) {
             msg.append(": ")
-            msg.append(cause.message)
+            msg.append(cause.message ?: cause::class.simpleName)
             cause = cause.cause
         }
         logger.error(msg.toString())
diff --git a/util/src/main/kotlin/Client.kt b/common/src/main/kotlin/Client.kt
similarity index 97%
rename from util/src/main/kotlin/Client.kt
rename to common/src/main/kotlin/Client.kt
index 3ffbdbeb..b65f59db 100644
--- a/util/src/main/kotlin/Client.kt
+++ b/common/src/main/kotlin/Client.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import io.ktor.http.*
 import kotlinx.serialization.json.*
@@ -26,7 +26,6 @@ import io.ktor.client.statement.*
 import java.io.ByteArrayOutputStream
 import java.util.zip.DeflaterOutputStream
 import kotlin.test.assertEquals
-import net.taler.common.errorcodes.TalerErrorCode
 
 /* ----- Json DSL ----- */
 
diff --git a/util/src/main/kotlin/Config.kt b/common/src/main/kotlin/Config.kt
similarity index 97%
rename from util/src/main/kotlin/Config.kt
rename to common/src/main/kotlin/Config.kt
index 124a9db8..95496839 100644
--- a/util/src/main/kotlin/Config.kt
+++ b/common/src/main/kotlin/Config.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import ch.qos.logback.core.util.Loader
 
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt 
b/common/src/main/kotlin/Constants.kt
similarity index 67%
copy from bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt
copy to common/src/main/kotlin/Constants.kt
index 4c1a13d7..054011d4 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Metadata.kt
+++ b/common/src/main/kotlin/Constants.kt
@@ -1,6 +1,6 @@
 /*
  * This file is part of LibEuFin.
- * Copyright (C) 2023 Taler Systems S.A.
+ * Copyright (C) 2024 Taler Systems S.A.
 
  * LibEuFin is free software; you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as
@@ -16,15 +16,8 @@
  * License along with LibEuFin; see the file COPYING.  If not, see
  * <http://www.gnu.org/licenses/>
  */
-package tech.libeufin.bank
+package tech.libeufin.common
 
-private val PATTERN = Regex("[a-z0-9A-Z]{52}")
-
-fun parseIncomingTxMetadata(subject: String): EddsaPublicKey? {
-    val match = PATTERN.find(subject)?.value ?: return null;
-    try {
-        return EddsaPublicKey(match)
-    } catch (e: Exception) { 
-        return null
-    }
-}
\ No newline at end of file
+// DB
+const val MIN_VERSION: Int = 14
+const val SERIALIZATION_RETRY: Int = 10;
\ No newline at end of file
diff --git a/util/src/main/kotlin/CryptoUtil.kt 
b/common/src/main/kotlin/CryptoUtil.kt
similarity index 99%
rename from util/src/main/kotlin/CryptoUtil.kt
rename to common/src/main/kotlin/CryptoUtil.kt
index 923e84da..bcf19ae1 100644
--- a/util/src/main/kotlin/CryptoUtil.kt
+++ b/common/src/main/kotlin/CryptoUtil.kt
@@ -17,9 +17,8 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
-import net.taler.wallet.crypto.Base32Crockford
 import org.bouncycastle.jce.provider.BouncyCastleProvider
 import java.io.ByteArrayOutputStream
 import java.math.BigInteger
diff --git a/util/src/main/kotlin/DB.kt b/common/src/main/kotlin/DB.kt
similarity index 83%
rename from util/src/main/kotlin/DB.kt
rename to common/src/main/kotlin/DB.kt
index facff98b..5707bd7f 100644
--- a/util/src/main/kotlin/DB.kt
+++ b/common/src/main/kotlin/DB.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import org.postgresql.ds.PGSimpleDataSource
 import org.postgresql.jdbc.PgConnection
@@ -29,6 +29,8 @@ import java.net.URI
 import java.sql.PreparedStatement
 import java.sql.ResultSet
 import java.sql.SQLException
+import kotlinx.coroutines.*
+import com.zaxxer.hikari.*
 
 fun getCurrentUser(): String = System.getProperty("user.name")
 
@@ -276,4 +278,62 @@ fun resetDatabaseTables(conn: PgConnection, cfg: 
DatabaseConfig, sqlFilePrefix:
 
     val sqlDrop = File("${cfg.sqlDir}/$sqlFilePrefix-drop.sql").readText()
     conn.execSQLUpdate(sqlDrop)
+}
+
+abstract class DbPool(cfg: String, schema: String): java.io.Closeable {
+    val pgSource = pgDataSource(cfg)
+    private val pool: HikariDataSource
+
+    init {
+        val config = HikariConfig();
+        config.dataSource = pgSource
+        config.schema = schema
+        config.transactionIsolation = "TRANSACTION_SERIALIZABLE"
+        pool = HikariDataSource(config)
+        pool.getConnection().use { con -> 
+            val meta = con.getMetaData();
+            val majorVersion = meta.getDatabaseMajorVersion()
+            val minorVersion = meta.getDatabaseMinorVersion()
+            if (majorVersion < MIN_VERSION) {
+                throw Exception("postgres version must be at least 
$MIN_VERSION.0 got $majorVersion.$minorVersion")
+            }
+        }
+    }
+
+    suspend fun <R> conn(lambda: suspend (PgConnection) -> R): R {
+        // Use a coroutine dispatcher that we can block as JDBC API is blocking
+        return withContext(Dispatchers.IO) {
+            val conn = pool.getConnection()
+            conn.use{ it -> lambda(it.unwrap(PgConnection::class.java)) }
+        }
+    }
+
+    suspend fun <R> serializable(lambda: suspend (PgConnection) -> R): R = 
conn { conn ->
+        repeat(SERIALIZATION_RETRY) {
+            try {
+                return@conn lambda(conn);
+            } catch (e: SQLException) {
+                if (e.sqlState != PSQLState.SERIALIZATION_FAILURE.state)
+                    throw e
+            }
+        }
+        try {
+            return@conn lambda(conn)
+        } catch(e: SQLException) {
+            logger.warn("Serialization failure after $SERIALIZATION_RETRY 
retry")
+            throw e
+        }
+    }
+
+    override fun close() {
+        pool.close()
+    }
+}
+
+fun ResultSet.getAmount(name: String, currency: String): TalerAmount{
+    return TalerAmount(
+        getLong("${name}_val"),
+        getInt("${name}_frac"),
+        currency
+    )
 }
\ No newline at end of file
diff --git a/util/src/main/kotlin/Encoding.kt 
b/common/src/main/kotlin/Encoding.kt
similarity index 99%
rename from util/src/main/kotlin/Encoding.kt
rename to common/src/main/kotlin/Encoding.kt
index dc76ffeb..cef7b0c0 100644
--- a/util/src/main/kotlin/Encoding.kt
+++ b/common/src/main/kotlin/Encoding.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package net.taler.wallet.crypto
+package tech.libeufin.common
 
 import java.io.ByteArrayOutputStream
 
diff --git a/util/src/main/kotlin/HTTP.kt b/common/src/main/kotlin/HTTP.kt
similarity index 98%
rename from util/src/main/kotlin/HTTP.kt
rename to common/src/main/kotlin/HTTP.kt
index 5b194283..3ba10dcf 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/common/src/main/kotlin/HTTP.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import io.ktor.http.*
 import io.ktor.server.application.*
diff --git a/common/src/main/kotlin/TalerCommon.kt 
b/common/src/main/kotlin/TalerCommon.kt
new file mode 100644
index 00000000..76c8d801
--- /dev/null
+++ b/common/src/main/kotlin/TalerCommon.kt
@@ -0,0 +1,201 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+ *
+ * LibEuFin 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.
+ *
+ * LibEuFin is distributed in the hope that 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 LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+package tech.libeufin.common
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import io.ktor.http.*
+import java.net.URI
+
+sealed class CommonError(msg: String): Exception(msg) {
+    class AmountFormat(msg: String): CommonError(msg)
+    class AmountNumberTooBig(msg: String): CommonError(msg)
+    class IbanPayto(msg: String): CommonError(msg)
+}
+
+@Serializable(with = TalerAmount.Serializer::class)
+class TalerAmount {
+    val value: Long
+    val frac: Int
+    val currency: String
+
+    constructor(value: Long, frac: Int, currency: String) {
+        this.value = value
+        this.frac = frac
+        this.currency = currency
+    }
+    constructor(encoded: String) {
+        val match = PATTERN.matchEntire(encoded) ?: 
+            throw CommonError.AmountFormat("Invalid amount format");
+        val (currency, value, frac) = match.destructured
+        this.currency = currency
+        this.value = value.toLongOrNull() ?: 
+            throw CommonError.AmountFormat("Invalid value")
+        if (this.value > MAX_VALUE) 
+            throw CommonError.AmountNumberTooBig("Value specified in amount is 
too large")
+        this.frac = if (frac.isEmpty()) {
+            0
+        } else {
+            var tmp = frac.toIntOrNull() ?: 
+                throw CommonError.AmountFormat("Invalid fractional value")
+            if (tmp > FRACTION_BASE) 
+                throw CommonError.AmountFormat("Fractional calue specified in 
amount is too large")
+            repeat(8 - frac.length) {
+                tmp *= 10
+            }
+            tmp
+        }
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return other is TalerAmount &&
+                other.value == this.value &&
+                other.frac == this.frac &&
+                other.currency == this.currency
+    }
+
+    override fun toString(): String {
+        if (frac == 0) {
+            return "$currency:$value"
+        } else {
+            return "$currency:$value.${frac.toString().padStart(8, '0')}"
+                .dropLastWhile { it == '0' } // Trim useless fractional 
trailing 0
+        }
+    }
+
+    internal object Serializer : KSerializer<TalerAmount> {
+        override val descriptor: SerialDescriptor =
+        PrimitiveSerialDescriptor("TalerAmount", PrimitiveKind.STRING)
+    
+        override fun serialize(encoder: Encoder, value: TalerAmount) {
+            encoder.encodeString(value.toString())
+        }
+    
+        override fun deserialize(decoder: Decoder): TalerAmount {
+            return TalerAmount(decoder.decodeString())
+        }
+    }
+
+    companion object {
+        const val FRACTION_BASE = 100000000
+        const val MAX_VALUE = 4503599627370496L; // 2^52
+        private val PATTERN = 
Regex("([A-Z]{1,11}):([0-9]+)(?:\\.([0-9]{1,8}))?");
+    }
+}
+
+
+sealed class PaytoUri {
+    abstract val amount: TalerAmount?
+    abstract val message: String?
+    abstract val receiverName: String?
+}
+
+// TODO x-taler-bank Payto
+
+@Serializable(with = IbanPayto.Serializer::class)
+class IbanPayto: PaytoUri {
+    val parsed: URI
+    val canonical: String
+    val iban: String
+    override val amount: TalerAmount?
+    override val message: String?
+    override val receiverName: String?
+
+    constructor(raw: String) {
+        println(raw)
+        try {
+            parsed = URI(raw)
+        } catch (e: Exception) {
+            throw CommonError.IbanPayto("expecteda valid URI")
+        }
+        
+        if (parsed.scheme != "payto") throw CommonError.IbanPayto("expect a 
payto URI")
+        if (parsed.host != "iban") throw CommonError.IbanPayto("expect a IBAN 
payto URI")
+
+        val splitPath = parsed.path.split("/").filter { it.isNotEmpty() }
+        val rawIban = when (splitPath.size) {
+            1 -> splitPath[0]
+            2 -> splitPath[1]
+            else -> throw CommonError.IbanPayto("too many path segments")
+        }
+        iban = rawIban.uppercase().replace(SEPARATOR, "")
+        checkIban(iban)
+        canonical = "payto://iban/$iban"
+    
+        val params = (parsed.query ?: "").parseUrlEncodedParameters();
+        amount = params["amount"]?.run { TalerAmount(this) }
+        message = params["message"]
+        receiverName = params["receiver-name"]
+    }
+
+    /** Full IBAN payto with receiver-name parameter if present */
+    fun maybeFull(): FullIbanPayto? {
+        return FullIbanPayto(this, receiverName ?: return null)
+    }
+
+    /** Full IBAN payto with receiver-name parameter if present, fail if 
absent */
+    fun requireFull(): FullIbanPayto {
+        return maybeFull() ?: throw Exception("Missing receiver-name")
+    }
+
+    /** Full IBAN payto with receiver-name parameter set to [defaultName] if 
absent */
+    fun full(defaultName: String): FullIbanPayto {
+        return FullIbanPayto(this, receiverName ?: defaultName)
+    }
+
+    override fun toString(): String = canonical
+
+    internal object Serializer : KSerializer<IbanPayto> {
+        override val descriptor: SerialDescriptor =
+            PrimitiveSerialDescriptor("IbanPayto", PrimitiveKind.STRING)
+
+        override fun serialize(encoder: Encoder, value: IbanPayto) {
+            encoder.encodeString(value.parsed.toString())
+        }
+
+        override fun deserialize(decoder: Decoder): IbanPayto {
+            return IbanPayto(decoder.decodeString())
+        }
+    }
+
+    companion object {
+        private val SEPARATOR = Regex("[\\ \\-]");
+
+        fun checkIban(iban: String) {
+            val builder = StringBuilder(iban.length + iban.asSequence().map { 
if (it.isDigit()) 1 else 2 }.sum())
+            (iban.subSequence(4, iban.length).asSequence() + 
iban.subSequence(0, 4).asSequence()).forEach {
+                if (it.isDigit()) {
+                    builder.append(it)
+                } else {
+                    builder.append((it.code - 'A'.code) + 10)
+                }
+            }
+            val str = builder.toString()
+            val mod = str.toBigInteger().mod(97.toBigInteger()).toInt();
+            if (mod != 1) throw CommonError.IbanPayto("Iban malformed, modulo 
is $mod expected 1")
+        }
+    }
+}
+
+class FullIbanPayto(val payto: IbanPayto, val receiverName: String) {
+    val full = payto.canonical + "?receiver-name=" + 
receiverName.encodeURLParameter()
+}
\ No newline at end of file
diff --git a/util/src/main/kotlin/TalerConfig.kt 
b/common/src/main/kotlin/TalerConfig.kt
similarity index 99%
rename from util/src/main/kotlin/TalerConfig.kt
rename to common/src/main/kotlin/TalerConfig.kt
index b9435d19..a45b33e8 100644
--- a/util/src/main/kotlin/TalerConfig.kt
+++ b/common/src/main/kotlin/TalerConfig.kt
@@ -17,6 +17,8 @@
  * <http://www.gnu.org/licenses/>
  */
 
+package tech.libeufin.common
+
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import java.io.File
diff --git a/util/src/main/kotlin/TalerErrorCode.kt 
b/common/src/main/kotlin/TalerErrorCode.kt
similarity index 99%
rename from util/src/main/kotlin/TalerErrorCode.kt
rename to common/src/main/kotlin/TalerErrorCode.kt
index ff1e6af8..e37f2eb5 100644
--- a/util/src/main/kotlin/TalerErrorCode.kt
+++ b/common/src/main/kotlin/TalerErrorCode.kt
@@ -20,7 +20,7 @@
      Note: the LGPL does not apply to all components of GNU Taler,
      but it does apply to this file.
  */
-package net.taler.common.errorcodes
+package tech.libeufin.common
 
 enum class TalerErrorCode(val code: Int) {
 
diff --git a/util/src/main/kotlin/iban.kt b/common/src/main/kotlin/iban.kt
similarity index 98%
rename from util/src/main/kotlin/iban.kt
rename to common/src/main/kotlin/iban.kt
index 00c635ac..499ad8e7 100644
--- a/util/src/main/kotlin/iban.kt
+++ b/common/src/main/kotlin/iban.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 fun getIban(): String {
     val ccNoCheck = "131400" // DE00
diff --git a/util/src/main/kotlin/strings.kt b/common/src/main/kotlin/strings.kt
similarity index 97%
rename from util/src/main/kotlin/strings.kt
rename to common/src/main/kotlin/strings.kt
index 43639cd5..7dc10d1e 100644
--- a/util/src/main/kotlin/strings.kt
+++ b/common/src/main/kotlin/strings.kt
@@ -17,9 +17,8 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
-import net.taler.wallet.crypto.Base32Crockford
 import java.math.BigInteger
 import java.util.*
 
diff --git a/util/src/main/kotlin/time.kt b/common/src/main/kotlin/time.kt
similarity index 99%
rename from util/src/main/kotlin/time.kt
rename to common/src/main/kotlin/time.kt
index d1ed8b21..fee05d01 100644
--- a/util/src/main/kotlin/time.kt
+++ b/common/src/main/kotlin/time.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.common
 
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
diff --git a/util/src/main/resources/xsd/camt.052.001.02.xsd 
b/common/src/main/resources/xsd/camt.052.001.02.xsd
similarity index 100%
copy from util/src/main/resources/xsd/camt.052.001.02.xsd
copy to common/src/main/resources/xsd/camt.052.001.02.xsd
diff --git a/util/src/main/resources/xsd/camt.053.001.02.xsd 
b/common/src/main/resources/xsd/camt.053.001.02.xsd
similarity index 100%
copy from util/src/main/resources/xsd/camt.053.001.02.xsd
copy to common/src/main/resources/xsd/camt.053.001.02.xsd
diff --git a/util/src/main/resources/xsd/camt.054.001.02.xsd 
b/common/src/main/resources/xsd/camt.054.001.02.xsd
similarity index 100%
copy from util/src/main/resources/xsd/camt.054.001.02.xsd
copy to common/src/main/resources/xsd/camt.054.001.02.xsd
diff --git a/util/src/main/resources/xsd/ebics_H004.xsd 
b/common/src/main/resources/xsd/ebics_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_H004.xsd
copy to common/src/main/resources/xsd/ebics_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_H005.xsd 
b/common/src/main/resources/xsd/ebics_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_H005.xsd
copy to common/src/main/resources/xsd/ebics_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_hev.xsd 
b/common/src/main/resources/xsd/ebics_hev.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_hev.xsd
copy to common/src/main/resources/xsd/ebics_hev.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd 
b/common/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
copy to common/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd 
b/common/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
copy to common/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd 
b/common/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
copy to common/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd 
b/common/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
copy to common/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_orders_H004.xsd 
b/common/src/main/resources/xsd/ebics_orders_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_orders_H004.xsd
copy to common/src/main/resources/xsd/ebics_orders_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_orders_H005.xsd 
b/common/src/main/resources/xsd/ebics_orders_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_orders_H005.xsd
copy to common/src/main/resources/xsd/ebics_orders_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_request_H004.xsd 
b/common/src/main/resources/xsd/ebics_request_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_request_H004.xsd
copy to common/src/main/resources/xsd/ebics_request_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_request_H005.xsd 
b/common/src/main/resources/xsd/ebics_request_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_request_H005.xsd
copy to common/src/main/resources/xsd/ebics_request_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_response_H004.xsd 
b/common/src/main/resources/xsd/ebics_response_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_response_H004.xsd
copy to common/src/main/resources/xsd/ebics_response_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_response_H005.xsd 
b/common/src/main/resources/xsd/ebics_response_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_response_H005.xsd
copy to common/src/main/resources/xsd/ebics_response_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_signature_S002.xsd 
b/common/src/main/resources/xsd/ebics_signature_S002.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_signature_S002.xsd
copy to common/src/main/resources/xsd/ebics_signature_S002.xsd
diff --git a/util/src/main/resources/xsd/ebics_signatures.xsd 
b/common/src/main/resources/xsd/ebics_signatures.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_signatures.xsd
copy to common/src/main/resources/xsd/ebics_signatures.xsd
diff --git a/util/src/main/resources/xsd/ebics_types_H004.xsd 
b/common/src/main/resources/xsd/ebics_types_H004.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_types_H004.xsd
copy to common/src/main/resources/xsd/ebics_types_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_types_H005.xsd 
b/common/src/main/resources/xsd/ebics_types_H005.xsd
similarity index 100%
copy from util/src/main/resources/xsd/ebics_types_H005.xsd
copy to common/src/main/resources/xsd/ebics_types_H005.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.03.ch.02.xsd 
b/common/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
similarity index 100%
copy from util/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
copy to common/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.03.xsd 
b/common/src/main/resources/xsd/pain.001.001.03.xsd
similarity index 100%
copy from util/src/main/resources/xsd/pain.001.001.03.xsd
copy to common/src/main/resources/xsd/pain.001.001.03.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.09.ch.03.xsd 
b/common/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
similarity index 100%
copy from util/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
copy to common/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
diff --git a/util/src/main/resources/xsd/pain.002.001.13.xsd 
b/common/src/main/resources/xsd/pain.002.001.13.xsd
similarity index 100%
copy from util/src/main/resources/xsd/pain.002.001.13.xsd
copy to common/src/main/resources/xsd/pain.002.001.13.xsd
diff --git a/util/src/main/resources/xsd/xmldsig-core-schema.xsd 
b/common/src/main/resources/xsd/xmldsig-core-schema.xsd
similarity index 100%
copy from util/src/main/resources/xsd/xmldsig-core-schema.xsd
copy to common/src/main/resources/xsd/xmldsig-core-schema.xsd
diff --git a/common/src/test/kotlin/AmountTest.kt 
b/common/src/test/kotlin/AmountTest.kt
new file mode 100644
index 00000000..ccd836aa
--- /dev/null
+++ b/common/src/test/kotlin/AmountTest.kt
@@ -0,0 +1,62 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+
+ * LibEuFin 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.
+
+ * LibEuFin is distributed in the hope that 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 LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import java.time.Instant
+import java.util.*
+import kotlin.test.*
+import org.junit.Test
+import tech.libeufin.common.*
+
+class AmountTest {
+    @Test
+    fun parse() {
+        assertEquals(TalerAmount("EUR:4"), TalerAmount(4L, 0, "EUR"))
+        assertEquals(TalerAmount("EUR:0.02"), TalerAmount(0L, 2000000, "EUR"))
+        assertEquals(TalerAmount("EUR:4.12"), TalerAmount(4L, 12000000, "EUR"))
+        assertEquals(TalerAmount("LOCAL:4444.1000"), TalerAmount(4444L, 
10000000, "LOCAL"))
+        assertEquals(TalerAmount("EUR:${TalerAmount.MAX_VALUE}.99999999"), 
TalerAmount(TalerAmount.MAX_VALUE, 99999999, "EUR"))
+
+        assertException("Invalid amount format") {TalerAmount("")}
+        assertException("Invalid amount format") {TalerAmount("EUR")}
+        assertException("Invalid amount format") {TalerAmount("eur:12")}
+        assertException("Invalid amount format") {TalerAmount(" EUR:12")}
+        assertException("Invalid amount format") {TalerAmount("EUR:1.")}
+        assertException("Invalid amount format") {TalerAmount("EUR:.1")}
+        assertException("Invalid amount format") 
{TalerAmount("AZERTYUIOPQSD:12")}
+        assertException("Value specified in amount is too large") 
{TalerAmount("EUR:${Long.MAX_VALUE}")}
+        assertException("Invalid amount format") 
{TalerAmount("EUR:4.000000000")}
+        assertException("Invalid amount format") {TalerAmount("EUR:4.4a")}
+    }
+
+    @Test
+    fun parseRoundTrip() {
+        for (amount in listOf("EUR:4", "EUR:0.02", "EUR:4.12")) {
+            assertEquals(amount, TalerAmount(amount).toString())
+        }
+    }
+
+    fun assertException(msg: String, lambda: () -> Unit) {
+        try {
+            lambda()
+            throw Exception("Expected failure")
+        } catch (e: Exception) {
+            assert(e.message!!.startsWith(msg)) { "${e.message}" }
+        }
+    }
+}
\ No newline at end of file
diff --git a/util/src/test/kotlin/CryptoUtilTest.kt 
b/common/src/test/kotlin/CryptoUtilTest.kt
similarity index 99%
rename from util/src/test/kotlin/CryptoUtilTest.kt
rename to common/src/test/kotlin/CryptoUtilTest.kt
index d29a9fa9..09f17fca 100644
--- a/util/src/test/kotlin/CryptoUtilTest.kt
+++ b/common/src/test/kotlin/CryptoUtilTest.kt
@@ -17,10 +17,9 @@
  * <http://www.gnu.org/licenses/>
  */
 
-import net.taler.wallet.crypto.Base32Crockford
 import org.junit.Ignore
 import org.junit.Test
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.io.File
 import java.security.KeyPairGenerator
 import java.security.interfaces.RSAPrivateCrtKey
diff --git a/common/src/test/kotlin/PaytoTest.kt 
b/common/src/test/kotlin/PaytoTest.kt
new file mode 100644
index 00000000..e1a6b976
--- /dev/null
+++ b/common/src/test/kotlin/PaytoTest.kt
@@ -0,0 +1,80 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2024 Taler Systems S.A.
+
+ * LibEuFin 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.
+
+ * LibEuFin is distributed in the hope that 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 LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+import org.junit.Test
+import tech.libeufin.common.*
+import kotlin.test.*
+
+class PaytoTest {
+    @Test
+    fun wrongCases() {
+        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("http://iban/BIC123/IBAN123?receiver-name=The%20Name";) }
+        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house") }
+        assertFailsWith<CommonError.IbanPayto> { 
IbanPayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo") }
+    }
+
+    @Test
+    fun parsePaytoTest() {
+        val withBic = 
IbanPayto("payto://iban/BIC123/CH9300762011623852957?receiver-name=The%20Name")
+        assertEquals(withBic.iban, "CH9300762011623852957")
+        assertEquals(withBic.receiverName, "The Name")
+        val complete = 
IbanPayto("payto://iban/BIC123/CH9300762011623852957?sender-name=The%20Name&amount=EUR:1&message=donation")
+        assertEquals(withBic.iban, "CH9300762011623852957")
+        assertEquals(withBic.receiverName, "The Name")
+        assertEquals(complete.message, "donation")
+        assertEquals(complete.amount.toString(), "EUR:1")
+        val withoutOptionals = IbanPayto("payto://iban/CH9300762011623852957")
+        assertNull(withoutOptionals.message)
+        assertNull(withoutOptionals.receiverName)
+        assertNull(withoutOptionals.amount)
+    }
+
+    @Test
+    fun normalization() {
+        val canonical = "payto://iban/CH9300762011623852957"
+        val inputs = listOf(
+            "payto://iban/BIC/CH9300762011623852957?receiver-name=NotGiven",
+            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
+            "payto://iban/ch%209300-7620-1162-3852-957",
+        )
+        val names = listOf(
+            "NotGiven", "Grothoff Hans", null
+        )
+        val full = listOf(
+            "payto://iban/CH9300762011623852957?receiver-name=NotGiven",
+            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
+            canonical
+        )
+        for ((i, input) in inputs.withIndex()) {
+            val payto = IbanPayto(input)
+            assertEquals(canonical, payto.canonical)
+            assertEquals(full[i], payto.maybeFull()?.full ?: payto.canonical)
+            assertEquals(names[i], payto.receiverName)
+        }
+        
+        assertEquals(
+            "payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans",
+            
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Grothoff%20Hans").full("Santa
 Claus").full
+        )
+        assertEquals(
+            "payto://iban/CH9300762011623852957?receiver-name=Santa%20Claus",
+            IbanPayto("payto://iban/CH9300762011623852957").full("Santa 
Claus").full
+        )
+    }
+}
\ No newline at end of file
diff --git a/util/src/test/kotlin/TalerConfigTest.kt 
b/common/src/test/kotlin/TalerConfigTest.kt
similarity index 98%
rename from util/src/test/kotlin/TalerConfigTest.kt
rename to common/src/test/kotlin/TalerConfigTest.kt
index 7e51076d..024ca460 100644
--- a/util/src/test/kotlin/TalerConfigTest.kt
+++ b/common/src/test/kotlin/TalerConfigTest.kt
@@ -19,6 +19,7 @@
 
 import org.junit.Test
 import kotlin.test.assertEquals
+import tech.libeufin.common.*
 
 class TalerConfigTest {
 
diff --git a/util/src/test/kotlin/TimeTest.kt 
b/common/src/test/kotlin/TimeTest.kt
similarity index 95%
rename from util/src/test/kotlin/TimeTest.kt
rename to common/src/test/kotlin/TimeTest.kt
index 284e46d4..090843af 100644
--- a/util/src/test/kotlin/TimeTest.kt
+++ b/common/src/test/kotlin/TimeTest.kt
@@ -18,8 +18,8 @@
  */
 
 import org.junit.Test
-import tech.libeufin.util.maxTimestamp
-import tech.libeufin.util.minTimestamp
+import tech.libeufin.common.maxTimestamp
+import tech.libeufin.common.minTimestamp
 import java.time.Instant
 import java.time.temporal.ChronoUnit
 import kotlin.test.assertEquals
diff --git a/util/src/test/resources/ebics_hev.xml 
b/common/src/test/resources/ebics_hev.xml
similarity index 100%
copy from util/src/test/resources/ebics_hev.xml
copy to common/src/test/resources/ebics_hev.xml
diff --git a/util/src/test/resources/ebics_ini_inner_key.xml 
b/common/src/test/resources/ebics_ini_inner_key.xml
similarity index 100%
copy from util/src/test/resources/ebics_ini_inner_key.xml
copy to common/src/test/resources/ebics_ini_inner_key.xml
diff --git a/util/src/test/resources/ebics_ini_request_sample.xml 
b/common/src/test/resources/ebics_ini_request_sample.xml
similarity index 100%
copy from util/src/test/resources/ebics_ini_request_sample.xml
copy to common/src/test/resources/ebics_ini_request_sample.xml
diff --git a/util/src/test/resources/hia_request.xml 
b/common/src/test/resources/hia_request.xml
similarity index 100%
copy from util/src/test/resources/hia_request.xml
copy to common/src/test/resources/hia_request.xml
diff --git a/util/src/test/resources/hia_request_order_data.xml 
b/common/src/test/resources/hia_request_order_data.xml
similarity index 100%
copy from util/src/test/resources/hia_request_order_data.xml
copy to common/src/test/resources/hia_request_order_data.xml
diff --git a/util/src/test/resources/hpb_request.xml 
b/common/src/test/resources/hpb_request.xml
similarity index 100%
copy from util/src/test/resources/hpb_request.xml
copy to common/src/test/resources/hpb_request.xml
diff --git a/util/src/test/resources/signature1/doc.xml 
b/common/src/test/resources/signature1/doc.xml
similarity index 100%
copy from util/src/test/resources/signature1/doc.xml
copy to common/src/test/resources/signature1/doc.xml
diff --git a/util/src/test/resources/signature1/public_key.txt 
b/common/src/test/resources/signature1/public_key.txt
similarity index 100%
copy from util/src/test/resources/signature1/public_key.txt
copy to common/src/test/resources/signature1/public_key.txt
diff --git a/util/build.gradle b/ebics/build.gradle
similarity index 61%
rename from util/build.gradle
rename to ebics/build.gradle
index 0bd9b551..dbd9b36a 100644
--- a/util/build.gradle
+++ b/ebics/build.gradle
@@ -1,5 +1,3 @@
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
 plugins {
     id("java")
     id("kotlin")
@@ -18,18 +16,14 @@ compileTestKotlin.kotlinOptions.jvmTarget = "17"
 sourceSets.main.java.srcDirs = ["src/main/kotlin"]
 
 dependencies {
+    implementation(project(":common"))
+
     implementation("ch.qos.logback:logback-classic:1.4.5")
     // XML Stuff
     implementation("javax.xml.bind:jaxb-api:2.3.1")
     implementation("org.glassfish.jaxb:jaxb-runtime:2.3.1")
     implementation("org.apache.santuario:xmlsec:2.2.2")
-    // Crypto
-    implementation("org.bouncycastle:bcprov-jdk15on:1.69")
-    // Database helper
-    implementation("org.postgresql:postgresql:$postgres_version")
     
-    implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
-    implementation("io.ktor:ktor-server-test-host:$ktor_version")
+    implementation("io.ktor:ktor-http:$ktor_version")
     implementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
-    implementation("com.github.ajalt.clikt:clikt:$clikt_version")
 }
\ No newline at end of file
diff --git a/util/import.py b/ebics/import.py
similarity index 98%
rename from util/import.py
rename to ebics/import.py
index e1389052..f9e90e10 100644
--- a/util/import.py
+++ b/ebics/import.py
@@ -56,7 +56,7 @@ kt = f"""/*
 
 // THIS FILE IS GENERATED, DO NOT EDIT
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
 {extractCodeSet("ExternalStatusReason1Code", "ExternalStatusReasonCode")}
 
diff --git a/util/src/main/kotlin/Ebics.kt b/ebics/src/main/kotlin/Ebics.kt
similarity index 96%
rename from util/src/main/kotlin/Ebics.kt
rename to ebics/src/main/kotlin/Ebics.kt
index b0b45197..558ceaee 100644
--- a/util/src/main/kotlin/Ebics.kt
+++ b/ebics/src/main/kotlin/Ebics.kt
@@ -22,12 +22,13 @@
  * used to implement both an EBICS server and EBICS client.
  */
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
+import tech.libeufin.common.CryptoUtil
 import io.ktor.http.HttpStatusCode
-import tech.libeufin.util.ebics_h004.*
-import tech.libeufin.util.ebics_h005.Ebics3Response
-import tech.libeufin.util.ebics_s001.UserSignatureData
+import tech.libeufin.ebics.ebics_h004.*
+import tech.libeufin.ebics.ebics_h005.Ebics3Response
+import tech.libeufin.ebics.ebics_s001.UserSignatureData
 import java.security.SecureRandom
 import java.security.interfaces.RSAPrivateCrtKey
 import java.security.interfaces.RSAPublicKey
@@ -173,14 +174,14 @@ fun signOrderEbics3(
     signKey: RSAPrivateCrtKey,
     partnerId: String,
     userId: String
-): tech.libeufin.util.ebics_s002.UserSignatureDataEbics3 {
+): tech.libeufin.ebics.ebics_s002.UserSignatureDataEbics3 {
     val ES_signature = CryptoUtil.signEbicsA006(
         CryptoUtil.digestEbicsOrderA006(orderBlob),
         signKey
     )
-    val userSignatureData = 
tech.libeufin.util.ebics_s002.UserSignatureDataEbics3().apply {
+    val userSignatureData = 
tech.libeufin.ebics.ebics_s002.UserSignatureDataEbics3().apply {
         orderSignatureList = listOf(
-            
tech.libeufin.util.ebics_s002.UserSignatureDataEbics3.OrderSignatureData().apply
 {
+            
tech.libeufin.ebics.ebics_s002.UserSignatureDataEbics3.OrderSignatureData().apply
 {
                 signatureVersion = "A006"
                 signatureValue = ES_signature
                 partnerID = partnerId
diff --git a/util/src/main/kotlin/EbicsCodeSets.kt 
b/ebics/src/main/kotlin/EbicsCodeSets.kt
similarity index 99%
rename from util/src/main/kotlin/EbicsCodeSets.kt
rename to ebics/src/main/kotlin/EbicsCodeSets.kt
index 4e0f95da..5f8b2b08 100644
--- a/util/src/main/kotlin/EbicsCodeSets.kt
+++ b/ebics/src/main/kotlin/EbicsCodeSets.kt
@@ -19,7 +19,7 @@
 
 // THIS FILE IS GENERATED, DO NOT EDIT
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
 enum class ExternalStatusReasonCode(val isoCode: String, val description: 
String) {
        AB01("AbortedClearingTimeout", "Clearing process aborted due to 
timeout."),
diff --git a/util/src/main/kotlin/EbicsOrderUtil.kt 
b/ebics/src/main/kotlin/EbicsOrderUtil.kt
similarity index 97%
rename from util/src/main/kotlin/EbicsOrderUtil.kt
rename to ebics/src/main/kotlin/EbicsOrderUtil.kt
index a7090295..ef7ed1f7 100644
--- a/util/src/main/kotlin/EbicsOrderUtil.kt
+++ b/ebics/src/main/kotlin/EbicsOrderUtil.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
 import java.lang.IllegalArgumentException
 import java.security.SecureRandom
@@ -50,6 +50,7 @@ object EbicsOrderUtil {
         }
     }
 
+    @kotlin.ExperimentalStdlibApi
     fun generateTransactionId(): String {
         val rng = SecureRandom()
         val res = ByteArray(16)
diff --git a/util/src/main/kotlin/XMLUtil.kt b/ebics/src/main/kotlin/XMLUtil.kt
similarity index 99%
rename from util/src/main/kotlin/XMLUtil.kt
rename to ebics/src/main/kotlin/XMLUtil.kt
index c0fe0638..c294b7ef 100644
--- a/util/src/main/kotlin/XMLUtil.kt
+++ b/ebics/src/main/kotlin/XMLUtil.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
 import com.sun.xml.bind.marshaller.NamespacePrefixMapper
 import io.ktor.http.*
@@ -32,7 +32,7 @@ import org.xml.sax.ErrorHandler
 import org.xml.sax.InputSource
 import org.xml.sax.SAXException
 import org.xml.sax.SAXParseException
-import tech.libeufin.util.ebics_h004.EbicsResponse
+import tech.libeufin.ebics.ebics_h004.EbicsResponse
 import java.io.*
 import java.security.PrivateKey
 import java.security.PublicKey
diff --git a/util/src/main/kotlin/XmlCombinators.kt 
b/ebics/src/main/kotlin/XmlCombinators.kt
similarity index 99%
rename from util/src/main/kotlin/XmlCombinators.kt
rename to ebics/src/main/kotlin/XmlCombinators.kt
index 3d154e96..d9cc77b2 100644
--- a/util/src/main/kotlin/XmlCombinators.kt
+++ b/ebics/src/main/kotlin/XmlCombinators.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util
+package tech.libeufin.ebics
 
 import com.sun.xml.txw2.output.IndentingXMLStreamWriter
 import org.w3c.dom.Document
diff --git a/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
index 6db6eab4..74d99c94 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsKeyManagementResponse.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt
index 34bfc8b2..2330ca0b 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsNpkdRequest.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
 import javax.xml.bind.annotation.*
diff --git a/util/src/main/kotlin/ebics_h004/EbicsRequest.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h004/EbicsRequest.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt
index e221a502..263fb71a 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsRequest.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsRequest.kt
@@ -1,7 +1,7 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
-import tech.libeufin.util.CryptoUtil
+import tech.libeufin.common.CryptoUtil
 import java.math.BigInteger
 import java.security.interfaces.RSAPublicKey
 import java.util.*
diff --git a/util/src/main/kotlin/ebics_h004/EbicsResponse.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsResponse.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h004/EbicsResponse.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsResponse.kt
index d0a88ae5..57841e28 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsResponse.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsResponse.kt
@@ -1,9 +1,9 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
 import org.apache.xml.security.binding.xmldsig.SignedInfoType
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.XMLUtil
+import tech.libeufin.common.CryptoUtil
+import tech.libeufin.ebics.XMLUtil
 import java.math.BigInteger
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h004/EbicsTypes.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsTypes.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h004/EbicsTypes.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsTypes.kt
index 1713684a..7451607f 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsTypes.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsTypes.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
 import org.w3c.dom.Element
diff --git a/util/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt 
b/ebics/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt
rename to ebics/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt
index efef020c..fbb60acd 100644
--- a/util/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt
+++ b/ebics/src/main/kotlin/ebics_h004/EbicsUnsecuredRequest.kt
@@ -1,8 +1,8 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
-import tech.libeufin.util.EbicsOrderUtil
-import tech.libeufin.util.ebics_s001.SignatureTypes
+import tech.libeufin.ebics.EbicsOrderUtil
+import tech.libeufin.ebics.ebics_s001.SignatureTypes
 import java.security.interfaces.RSAPrivateCrtKey
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h004/HIARequestOrderData.kt 
b/ebics/src/main/kotlin/ebics_h004/HIARequestOrderData.kt
similarity index 96%
rename from util/src/main/kotlin/ebics_h004/HIARequestOrderData.kt
rename to ebics/src/main/kotlin/ebics_h004/HIARequestOrderData.kt
index 95bd2b18..6268099b 100644
--- a/util/src/main/kotlin/ebics_h004/HIARequestOrderData.kt
+++ b/ebics/src/main/kotlin/ebics_h004/HIARequestOrderData.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt 
b/ebics/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt
similarity index 92%
rename from util/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt
rename to ebics/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt
index 999fe94c..c3e4b706 100644
--- a/util/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt
+++ b/ebics/src/main/kotlin/ebics_h004/HKDResponseOrderData.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import java.security.Permission
 import javax.xml.bind.annotation.*
diff --git a/util/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt 
b/ebics/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt
similarity index 95%
rename from util/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt
rename to ebics/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt
index 9c380478..65b4098b 100644
--- a/util/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt
+++ b/ebics/src/main/kotlin/ebics_h004/HPBResponseOrderData.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt 
b/ebics/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt
similarity index 91%
rename from util/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt
rename to ebics/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt
index 4162379b..46954355 100644
--- a/util/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt
+++ b/ebics/src/main/kotlin/ebics_h004/HTDResponseOrderData.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_h004
+package tech.libeufin.ebics.ebics_h004
 
 import javax.xml.bind.annotation.*
 
diff --git a/util/src/main/kotlin/ebics_h004/package-info.java 
b/ebics/src/main/kotlin/ebics_h004/package-info.java
similarity index 76%
rename from util/src/main/kotlin/ebics_h004/package-info.java
rename to ebics/src/main/kotlin/ebics_h004/package-info.java
index 662a1605..d12a4727 100644
--- a/util/src/main/kotlin/ebics_h004/package-info.java
+++ b/ebics/src/main/kotlin/ebics_h004/package-info.java
@@ -7,6 +7,6 @@
         namespace = "urn:org:ebics:H004",
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_h004;
+package tech.libeufin.ebics.ebics_h004;
 import javax.xml.bind.annotation.XmlSchema;
 import javax.xml.bind.annotation.XmlNsForm;
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Request.kt 
b/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h005/Ebics3Request.kt
rename to ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt
index f46c38f1..54987c8b 100644
--- a/util/src/main/kotlin/ebics_h005/Ebics3Request.kt
+++ b/ebics/src/main/kotlin/ebics_h005/Ebics3Request.kt
@@ -1,7 +1,7 @@
-package tech.libeufin.util.ebics_h005
+package tech.libeufin.ebics.ebics_h005
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
-import tech.libeufin.util.CryptoUtil
+import tech.libeufin.common.CryptoUtil
 import java.math.BigInteger
 import java.security.interfaces.RSAPublicKey
 import java.util.*
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Response.kt 
b/ebics/src/main/kotlin/ebics_h005/Ebics3Response.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_h005/Ebics3Response.kt
rename to ebics/src/main/kotlin/ebics_h005/Ebics3Response.kt
index f22c0b85..12bf8a11 100644
--- a/util/src/main/kotlin/ebics_h005/Ebics3Response.kt
+++ b/ebics/src/main/kotlin/ebics_h005/Ebics3Response.kt
@@ -1,10 +1,10 @@
-package tech.libeufin.util.ebics_h005
+package tech.libeufin.ebics.ebics_h005
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
 import org.apache.xml.security.binding.xmldsig.SignedInfoType
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h004.EbicsTypes
+import tech.libeufin.common.CryptoUtil
+import tech.libeufin.ebics.XMLUtil
+import tech.libeufin.ebics.ebics_h004.EbicsTypes
 import java.math.BigInteger
 import javax.xml.bind.annotation.*
 import javax.xml.bind.annotation.adapters.CollapsedStringAdapter
diff --git a/util/src/main/kotlin/ebics_h005/Ebics3Types.kt 
b/ebics/src/main/kotlin/ebics_h005/Ebics3Types.kt
similarity index 99%
rename from util/src/main/kotlin/ebics_h005/Ebics3Types.kt
rename to ebics/src/main/kotlin/ebics_h005/Ebics3Types.kt
index 2bb0659a..298dc655 100644
--- a/util/src/main/kotlin/ebics_h005/Ebics3Types.kt
+++ b/ebics/src/main/kotlin/ebics_h005/Ebics3Types.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util.ebics_h005
+package tech.libeufin.ebics.ebics_h005
 
 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
 import org.w3c.dom.Element
diff --git a/util/src/main/kotlin/ebics_h005/package-info.java 
b/ebics/src/main/kotlin/ebics_h005/package-info.java
similarity index 78%
rename from util/src/main/kotlin/ebics_h005/package-info.java
rename to ebics/src/main/kotlin/ebics_h005/package-info.java
index 6f65a4b2..42b565b7 100644
--- a/util/src/main/kotlin/ebics_h005/package-info.java
+++ b/ebics/src/main/kotlin/ebics_h005/package-info.java
@@ -7,7 +7,7 @@
         namespace = "urn:org:ebics:H005",
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_h005;
+package tech.libeufin.ebics.ebics_h005;
 import javax.xml.bind.annotation.XmlNs;
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_hev/EbicsMessages.kt 
b/ebics/src/main/kotlin/ebics_hev/EbicsMessages.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_hev/EbicsMessages.kt
rename to ebics/src/main/kotlin/ebics_hev/EbicsMessages.kt
index 0d4302b3..506ba9fa 100644
--- a/util/src/main/kotlin/ebics_hev/EbicsMessages.kt
+++ b/ebics/src/main/kotlin/ebics_hev/EbicsMessages.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util.ebics_hev
+package tech.libeufin.ebics.ebics_hev
 
 import java.util.*
 import javax.xml.bind.annotation.*
diff --git a/util/src/main/kotlin/ebics_hev/package-info.java 
b/ebics/src/main/kotlin/ebics_hev/package-info.java
similarity index 89%
rename from util/src/main/kotlin/ebics_hev/package-info.java
rename to ebics/src/main/kotlin/ebics_hev/package-info.java
index ad7de333..8d2c7b54 100644
--- a/util/src/main/kotlin/ebics_hev/package-info.java
+++ b/ebics/src/main/kotlin/ebics_hev/package-info.java
@@ -7,7 +7,7 @@
         namespace = "http://www.ebics.org/H000";,
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_hev;
+package tech.libeufin.ebics.ebics_hev;
 
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
diff --git a/util/src/main/kotlin/ebics_s001/SignatureTypes.kt 
b/ebics/src/main/kotlin/ebics_s001/SignatureTypes.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_s001/SignatureTypes.kt
rename to ebics/src/main/kotlin/ebics_s001/SignatureTypes.kt
index ea072527..4781ba26 100644
--- a/util/src/main/kotlin/ebics_s001/SignatureTypes.kt
+++ b/ebics/src/main/kotlin/ebics_s001/SignatureTypes.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util.ebics_s001
+package tech.libeufin.ebics.ebics_s001
 
 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
 import org.apache.xml.security.binding.xmldsig.X509DataType
diff --git a/util/src/main/kotlin/ebics_s001/UserSignatureData.kt 
b/ebics/src/main/kotlin/ebics_s001/UserSignatureData.kt
similarity index 95%
rename from util/src/main/kotlin/ebics_s001/UserSignatureData.kt
rename to ebics/src/main/kotlin/ebics_s001/UserSignatureData.kt
index cbb9ce6c..7eb5e0ed 100644
--- a/util/src/main/kotlin/ebics_s001/UserSignatureData.kt
+++ b/ebics/src/main/kotlin/ebics_s001/UserSignatureData.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_s001
+package tech.libeufin.ebics.ebics_s001
 
 import javax.xml.bind.annotation.*
 
diff --git a/util/src/main/kotlin/ebics_s001/package-info.java 
b/ebics/src/main/kotlin/ebics_s001/package-info.java
similarity index 76%
rename from util/src/main/kotlin/ebics_s001/package-info.java
rename to ebics/src/main/kotlin/ebics_s001/package-info.java
index 0a2af713..adda5875 100644
--- a/util/src/main/kotlin/ebics_s001/package-info.java
+++ b/ebics/src/main/kotlin/ebics_s001/package-info.java
@@ -7,7 +7,7 @@
         namespace = "http://www.ebics.org/S001";,
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_s001;
+package tech.libeufin.ebics.ebics_s001;
 
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/util/src/main/kotlin/ebics_s002/SignatureTypes.kt 
b/ebics/src/main/kotlin/ebics_s002/SignatureTypes.kt
similarity index 98%
rename from util/src/main/kotlin/ebics_s002/SignatureTypes.kt
rename to ebics/src/main/kotlin/ebics_s002/SignatureTypes.kt
index 9e367fc9..c1d48e9b 100644
--- a/util/src/main/kotlin/ebics_s002/SignatureTypes.kt
+++ b/ebics/src/main/kotlin/ebics_s002/SignatureTypes.kt
@@ -17,7 +17,7 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.util.ebics_s002
+package tech.libeufin.ebics.ebics_s002
 
 import org.apache.xml.security.binding.xmldsig.RSAKeyValueType
 import org.apache.xml.security.binding.xmldsig.X509DataType
diff --git a/util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt 
b/ebics/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
similarity index 95%
rename from util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
rename to ebics/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
index 6d7012a1..082d0681 100644
--- a/util/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
+++ b/ebics/src/main/kotlin/ebics_s002/UserSignatureDataEbics3.kt
@@ -1,4 +1,4 @@
-package tech.libeufin.util.ebics_s002
+package tech.libeufin.ebics.ebics_s002
 
 import javax.xml.bind.annotation.*
 
diff --git a/util/src/main/kotlin/ebics_s002/package-info.java 
b/ebics/src/main/kotlin/ebics_s002/package-info.java
similarity index 76%
rename from util/src/main/kotlin/ebics_s002/package-info.java
rename to ebics/src/main/kotlin/ebics_s002/package-info.java
index a9f7729a..5ddb1f77 100644
--- a/util/src/main/kotlin/ebics_s002/package-info.java
+++ b/ebics/src/main/kotlin/ebics_s002/package-info.java
@@ -7,7 +7,7 @@
         namespace = "http://www.ebics.org/S002";,
         elementFormDefault = XmlNsForm.QUALIFIED
 )
-package tech.libeufin.util.ebics_s002;
+package tech.libeufin.ebics.ebics_s002;
 
 import javax.xml.bind.annotation.XmlNsForm;
 import javax.xml.bind.annotation.XmlSchema;
\ No newline at end of file
diff --git a/ebics/src/main/resources/version.txt 
b/ebics/src/main/resources/version.txt
new file mode 100644
index 00000000..359d0539
--- /dev/null
+++ b/ebics/src/main/resources/version.txt
@@ -0,0 +1 @@
+v0.9.4-git-8aeffb3f
\ No newline at end of file
diff --git a/util/src/main/resources/xsd/camt.052.001.02.xsd 
b/ebics/src/main/resources/xsd/camt.052.001.02.xsd
similarity index 100%
rename from util/src/main/resources/xsd/camt.052.001.02.xsd
rename to ebics/src/main/resources/xsd/camt.052.001.02.xsd
diff --git a/util/src/main/resources/xsd/camt.053.001.02.xsd 
b/ebics/src/main/resources/xsd/camt.053.001.02.xsd
similarity index 100%
rename from util/src/main/resources/xsd/camt.053.001.02.xsd
rename to ebics/src/main/resources/xsd/camt.053.001.02.xsd
diff --git a/util/src/main/resources/xsd/camt.054.001.02.xsd 
b/ebics/src/main/resources/xsd/camt.054.001.02.xsd
similarity index 100%
rename from util/src/main/resources/xsd/camt.054.001.02.xsd
rename to ebics/src/main/resources/xsd/camt.054.001.02.xsd
diff --git a/util/src/main/resources/xsd/ebics_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_hev.xsd 
b/ebics/src/main/resources/xsd/ebics_hev.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_hev.xsd
rename to ebics/src/main/resources/xsd/ebics_hev.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_keymgmt_request_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_keymgmt_request_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_keymgmt_response_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_keymgmt_response_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_orders_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_orders_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_orders_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_orders_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_orders_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_orders_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_orders_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_orders_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_request_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_request_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_request_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_request_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_request_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_request_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_request_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_request_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_response_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_response_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_response_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_response_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_response_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_response_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_response_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_response_H005.xsd
diff --git a/util/src/main/resources/xsd/ebics_signature_S002.xsd 
b/ebics/src/main/resources/xsd/ebics_signature_S002.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_signature_S002.xsd
rename to ebics/src/main/resources/xsd/ebics_signature_S002.xsd
diff --git a/util/src/main/resources/xsd/ebics_signatures.xsd 
b/ebics/src/main/resources/xsd/ebics_signatures.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_signatures.xsd
rename to ebics/src/main/resources/xsd/ebics_signatures.xsd
diff --git a/util/src/main/resources/xsd/ebics_types_H004.xsd 
b/ebics/src/main/resources/xsd/ebics_types_H004.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_types_H004.xsd
rename to ebics/src/main/resources/xsd/ebics_types_H004.xsd
diff --git a/util/src/main/resources/xsd/ebics_types_H005.xsd 
b/ebics/src/main/resources/xsd/ebics_types_H005.xsd
similarity index 100%
rename from util/src/main/resources/xsd/ebics_types_H005.xsd
rename to ebics/src/main/resources/xsd/ebics_types_H005.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.03.ch.02.xsd 
b/ebics/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
similarity index 100%
rename from util/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
rename to ebics/src/main/resources/xsd/pain.001.001.03.ch.02.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.03.xsd 
b/ebics/src/main/resources/xsd/pain.001.001.03.xsd
similarity index 100%
rename from util/src/main/resources/xsd/pain.001.001.03.xsd
rename to ebics/src/main/resources/xsd/pain.001.001.03.xsd
diff --git a/util/src/main/resources/xsd/pain.001.001.09.ch.03.xsd 
b/ebics/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
similarity index 100%
rename from util/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
rename to ebics/src/main/resources/xsd/pain.001.001.09.ch.03.xsd
diff --git a/util/src/main/resources/xsd/pain.002.001.13.xsd 
b/ebics/src/main/resources/xsd/pain.002.001.13.xsd
similarity index 100%
rename from util/src/main/resources/xsd/pain.002.001.13.xsd
rename to ebics/src/main/resources/xsd/pain.002.001.13.xsd
diff --git a/util/src/main/resources/xsd/xmldsig-core-schema.xsd 
b/ebics/src/main/resources/xsd/xmldsig-core-schema.xsd
similarity index 100%
rename from util/src/main/resources/xsd/xmldsig-core-schema.xsd
rename to ebics/src/main/resources/xsd/xmldsig-core-schema.xsd
diff --git a/util/src/test/kotlin/EbicsMessagesTest.kt 
b/ebics/src/test/kotlin/EbicsMessagesTest.kt
similarity index 98%
rename from util/src/test/kotlin/EbicsMessagesTest.kt
rename to ebics/src/test/kotlin/EbicsMessagesTest.kt
index c426d8fb..5d0f8f5d 100644
--- a/util/src/test/kotlin/EbicsMessagesTest.kt
+++ b/ebics/src/test/kotlin/EbicsMessagesTest.kt
@@ -23,12 +23,12 @@ import junit.framework.TestCase.assertEquals
 import org.apache.xml.security.binding.xmldsig.SignatureType
 import org.junit.Test
 import org.w3c.dom.Element
-import tech.libeufin.util.ebics_h004.*
-import tech.libeufin.util.ebics_hev.HEVResponse
-import tech.libeufin.util.ebics_hev.SystemReturnCodeType
-import tech.libeufin.util.ebics_s001.SignatureTypes
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.XMLUtil
+import tech.libeufin.ebics.ebics_h004.*
+import tech.libeufin.ebics.ebics_hev.HEVResponse
+import tech.libeufin.ebics.ebics_hev.SystemReturnCodeType
+import tech.libeufin.ebics.ebics_s001.SignatureTypes
+import tech.libeufin.common.CryptoUtil
+import tech.libeufin.ebics.XMLUtil
 import javax.xml.datatype.DatatypeFactory
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
diff --git a/util/src/test/kotlin/EbicsOrderUtilTest.kt 
b/ebics/src/test/kotlin/EbicsOrderUtilTest.kt
similarity index 99%
rename from util/src/test/kotlin/EbicsOrderUtilTest.kt
rename to ebics/src/test/kotlin/EbicsOrderUtilTest.kt
index 4a772ab5..05f7f72d 100644
--- a/util/src/test/kotlin/EbicsOrderUtilTest.kt
+++ b/ebics/src/test/kotlin/EbicsOrderUtilTest.kt
@@ -18,9 +18,9 @@
  */
 
 import org.junit.Test
-import tech.libeufin.util.EbicsOrderUtil
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h004.HTDResponseOrderData
+import tech.libeufin.ebics.EbicsOrderUtil
+import tech.libeufin.ebics.XMLUtil
+import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData
 import kotlin.test.assertEquals
 
 
diff --git a/util/src/test/kotlin/SignatureDataTest.kt 
b/ebics/src/test/kotlin/SignatureDataTest.kt
similarity index 95%
rename from util/src/test/kotlin/SignatureDataTest.kt
rename to ebics/src/test/kotlin/SignatureDataTest.kt
index 936ea91b..1a429ade 100644
--- a/util/src/test/kotlin/SignatureDataTest.kt
+++ b/ebics/src/test/kotlin/SignatureDataTest.kt
@@ -19,10 +19,10 @@
 
 import org.apache.xml.security.binding.xmldsig.SignatureType
 import org.junit.Test
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h004.EbicsRequest
-import tech.libeufin.util.ebics_h004.EbicsTypes
+import tech.libeufin.common.CryptoUtil
+import tech.libeufin.ebics.XMLUtil
+import tech.libeufin.ebics.ebics_h004.EbicsRequest
+import tech.libeufin.ebics.ebics_h004.EbicsTypes
 import java.math.BigInteger
 import java.util.*
 import javax.xml.datatype.DatatypeFactory
diff --git a/util/src/test/kotlin/XmlCombinatorsTest.kt 
b/ebics/src/test/kotlin/XmlCombinatorsTest.kt
similarity index 96%
rename from util/src/test/kotlin/XmlCombinatorsTest.kt
rename to ebics/src/test/kotlin/XmlCombinatorsTest.kt
index 4ecaa622..c261187a 100644
--- a/util/src/test/kotlin/XmlCombinatorsTest.kt
+++ b/ebics/src/test/kotlin/XmlCombinatorsTest.kt
@@ -18,8 +18,8 @@
  */
 
 import org.junit.Test
-import tech.libeufin.util.XmlElementBuilder
-import tech.libeufin.util.constructXml
+import tech.libeufin.ebics.XmlElementBuilder
+import tech.libeufin.ebics.constructXml
 
 class XmlCombinatorsTest {
 
diff --git a/util/src/test/kotlin/XmlUtilTest.kt 
b/ebics/src/test/kotlin/XmlUtilTest.kt
similarity index 95%
rename from util/src/test/kotlin/XmlUtilTest.kt
rename to ebics/src/test/kotlin/XmlUtilTest.kt
index e059c78e..b8639d7a 100644
--- a/util/src/test/kotlin/XmlUtilTest.kt
+++ b/ebics/src/test/kotlin/XmlUtilTest.kt
@@ -22,16 +22,16 @@ import org.junit.Test
 import org.junit.Assert.*
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
-import tech.libeufin.util.ebics_h004.EbicsKeyManagementResponse
-import tech.libeufin.util.ebics_h004.EbicsResponse
-import tech.libeufin.util.ebics_h004.EbicsTypes
-import tech.libeufin.util.ebics_h004.HTDResponseOrderData
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.XMLUtil
+import tech.libeufin.ebics.ebics_h004.EbicsKeyManagementResponse
+import tech.libeufin.ebics.ebics_h004.EbicsResponse
+import tech.libeufin.ebics.ebics_h004.EbicsTypes
+import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData
+import tech.libeufin.common.CryptoUtil
+import tech.libeufin.ebics.XMLUtil
 import java.security.KeyPairGenerator
 import java.util.*
 import javax.xml.transform.stream.StreamSource
-import tech.libeufin.util.XMLUtil.Companion.signEbicsResponse
+import tech.libeufin.ebics.XMLUtil.Companion.signEbicsResponse
 
 class XmlUtilTest {
 
diff --git a/util/src/test/resources/ebics_hev.xml 
b/ebics/src/test/resources/ebics_hev.xml
similarity index 100%
rename from util/src/test/resources/ebics_hev.xml
rename to ebics/src/test/resources/ebics_hev.xml
diff --git a/util/src/test/resources/ebics_ini_inner_key.xml 
b/ebics/src/test/resources/ebics_ini_inner_key.xml
similarity index 100%
rename from util/src/test/resources/ebics_ini_inner_key.xml
rename to ebics/src/test/resources/ebics_ini_inner_key.xml
diff --git a/util/src/test/resources/ebics_ini_request_sample.xml 
b/ebics/src/test/resources/ebics_ini_request_sample.xml
similarity index 100%
rename from util/src/test/resources/ebics_ini_request_sample.xml
rename to ebics/src/test/resources/ebics_ini_request_sample.xml
diff --git a/util/src/test/resources/hia_request.xml 
b/ebics/src/test/resources/hia_request.xml
similarity index 100%
rename from util/src/test/resources/hia_request.xml
rename to ebics/src/test/resources/hia_request.xml
diff --git a/util/src/test/resources/hia_request_order_data.xml 
b/ebics/src/test/resources/hia_request_order_data.xml
similarity index 100%
rename from util/src/test/resources/hia_request_order_data.xml
rename to ebics/src/test/resources/hia_request_order_data.xml
diff --git a/util/src/test/resources/hpb_request.xml 
b/ebics/src/test/resources/hpb_request.xml
similarity index 100%
rename from util/src/test/resources/hpb_request.xml
rename to ebics/src/test/resources/hpb_request.xml
diff --git a/util/src/test/resources/signature1/doc.xml 
b/ebics/src/test/resources/signature1/doc.xml
similarity index 100%
rename from util/src/test/resources/signature1/doc.xml
rename to ebics/src/test/resources/signature1/doc.xml
diff --git a/util/src/test/resources/signature1/public_key.txt 
b/ebics/src/test/resources/signature1/public_key.txt
similarity index 100%
rename from util/src/test/resources/signature1/public_key.txt
rename to ebics/src/test/resources/signature1/public_key.txt
diff --git a/nexus/build.gradle b/nexus/build.gradle
index 75491bb3..5c082385 100644
--- a/nexus/build.gradle
+++ b/nexus/build.gradle
@@ -21,8 +21,8 @@ dependencies {
     // Core language libraries
     
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
 
-    // LibEuFin util library
-    implementation(project(":util"))
+    implementation(project(":common"))
+    implementation(project(":ebics"))
 
     // XML parsing/binding and encryption
     implementation("javax.xml.bind:jaxb-api:2.3.0")
@@ -35,7 +35,6 @@ dependencies {
     // Command line parsing
     implementation("com.github.ajalt.clikt:clikt:$clikt_version")
     implementation("org.postgresql:postgresql:$postgres_version")
-    implementation("com.zaxxer:HikariCP:5.0.1")
     // Ktor client library
     implementation("io.ktor:ktor-client-apache:$ktor_version")
 
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index 430d5d83..82d493e8 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -22,8 +22,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 import org.postgresql.jdbc.PgConnection
 import org.postgresql.util.PSQLState
-import com.zaxxer.hikari.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.sql.PreparedStatement
 import java.sql.SQLException
 import java.time.Instant
@@ -40,24 +39,6 @@ fun Instant.fmtDateTime(): String {
     return formatter.format(Date.from(this))
 }
 
-
-// Remove this once TalerAmount from the bank
-// module gets moved to the 'util' module (#7987).
-data class TalerAmount(
-    val value: Long,
-    val fraction: Int, // has at most 8 digits.
-    val currency: String
-) {
-    override fun toString(): String {
-        if (fraction == 0) {
-            return "$currency:$value"
-        } else {
-            return "$currency:$value.${fraction.toString().padStart(8, '0')}"
-                .dropLastWhile { it == '0' } // Trim useless fractional 
trailing 0
-        }
-    }
-}
-
 // INCOMING PAYMENTS STRUCTS
 
 /**
@@ -107,7 +88,7 @@ enum class DatabaseSubmissionState {
 data class InitiatedPayment(
     val amount: TalerAmount,
     val wireTransferSubject: String,
-    val creditPaytoUri: String,
+    val creditPaytoUri: FullIbanPayto,
     val initiationTime: Instant,
     val requestUid: String
 )
@@ -117,16 +98,6 @@ data class InitiatedPayment(
  * into the database.
  */
 enum class PaymentInitiationOutcome {
-    /**
-     * The Payto address to send the payment to was invalid.
-     */
-    BAD_CREDIT_PAYTO,
-
-    /**
-     * The receiver payto address lacks the name, that would
-     * cause the bank to reject the pain.001.
-     */
-    RECEIVER_NAME_MISSING,
 
     /**
      * The row contains a client_request_uid that exists
@@ -198,39 +169,7 @@ private fun PreparedStatement.maybeUpdate(): Boolean {
 /**
  * Collects database connection steps and any operation on the Nexus tables.
  */
-class Database(dbConfig: String): java.io.Closeable {
-    val dbPool: HikariDataSource
-
-    init {
-        val pgSource = pgDataSource(dbConfig)
-        val config = HikariConfig();
-        config.dataSource = pgSource
-        config.connectionInitSql = "SET search_path TO libeufin_nexus;"
-        config.validate()
-        dbPool = HikariDataSource(config);
-    }
-
-    /**
-     * Closes the database connection.
-     */
-    override fun close() {
-        dbPool.close()
-    }
-
-    /**
-     * Moves the database operations where they can block, without
-     * blocking the whole process.
-     *
-     * @param lambda actual statement preparation and execution logic.
-     * @return what lambda returns.
-     */
-    suspend fun <R> runConn(lambda: suspend (PgConnection) -> R): R {
-        // Use a coroutine dispatcher that we can block as JDBC API is blocking
-        return withContext(Dispatchers.IO) {
-            val conn = dbPool.getConnection()
-            conn.use { it -> lambda(it.unwrap(PgConnection::class.java)) }
-        }
-    }
+class Database(dbConfig: String): DbPool(dbConfig, "libeufin_nexus") {
 
     // OUTGOING PAYMENTS METHODS
 
@@ -241,7 +180,7 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param paymentData information about the outgoing payment.
      * @return operation outcome enum.
      */
-    suspend fun registerOutgoing(paymentData: OutgoingPayment): 
OutgoingRegistrationResult = runConn {        
+    suspend fun registerOutgoing(paymentData: OutgoingPayment): 
OutgoingRegistrationResult = conn {        
         val stmt = it.prepareStatement("""
             SELECT out_tx_id, out_initiated, out_found
               FROM register_outgoing(
@@ -255,7 +194,7 @@ class Database(dbConfig: String): java.io.Closeable {
         val executionTime = paymentData.executionTime.toDbMicros()
             ?: throw Exception("Could not convert outgoing payment 
execution_time to microseconds")
         stmt.setLong(1, paymentData.amount.value)
-        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
         stmt.setLong(4, executionTime)
         stmt.setString(5, paymentData.creditPaytoUri)
@@ -289,7 +228,7 @@ class Database(dbConfig: String): java.io.Closeable {
         paymentData: IncomingPayment,
         bounceAmount: TalerAmount,
         now: Instant
-    ): IncomingBounceRegistrationResult = runConn {       
+    ): IncomingBounceRegistrationResult = conn {       
         val stmt = it.prepareStatement("""
             SELECT out_found, out_tx_id, out_bounce_id
               FROM register_incoming_and_bounce(
@@ -307,13 +246,13 @@ class Database(dbConfig: String): java.io.Closeable {
         val executionTime = paymentData.executionTime.toDbMicros()
             ?: throw Exception("Could not convert payment execution time from 
Instant to microseconds.")
         stmt.setLong(1, paymentData.amount.value)
-        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
         stmt.setLong(4, executionTime)
         stmt.setString(5, paymentData.debitPaytoUri)
         stmt.setString(6, paymentData.bankId)
         stmt.setLong(7, bounceAmount.value)
-        stmt.setInt(8, bounceAmount.fraction)
+        stmt.setInt(8, bounceAmount.frac)
         stmt.setLong(9, refundTimestamp)
         stmt.executeQuery().use {
             when {
@@ -337,7 +276,7 @@ class Database(dbConfig: String): java.io.Closeable {
     suspend fun registerTalerableIncoming(
         paymentData: IncomingPayment,
         reservePub: ByteArray
-    ): IncomingRegistrationResult = runConn { conn ->
+    ): IncomingRegistrationResult = conn { conn ->
         val stmt = conn.prepareStatement("""
             SELECT out_found, out_tx_id
               FROM register_incoming_and_talerable(
@@ -352,7 +291,7 @@ class Database(dbConfig: String): java.io.Closeable {
         val executionTime = paymentData.executionTime.toDbMicros()
             ?: throw Exception("Could not convert payment execution time from 
Instant to microseconds.")
         stmt.setLong(1, paymentData.amount.value)
-        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
         stmt.setLong(4, executionTime)
         stmt.setString(5, paymentData.debitPaytoUri)
@@ -374,15 +313,15 @@ class Database(dbConfig: String): java.io.Closeable {
      *
      * @return [Instant] or null if no results were found
      */
-    suspend fun outgoingPaymentLastExecTime(): Instant? = runConn { conn ->
+    suspend fun outgoingPaymentLastExecTime(): Instant? = conn { conn ->
         val stmt = conn.prepareStatement(
             "SELECT MAX(execution_time) as latest_execution_time FROM 
outgoing_transactions"
         )
         stmt.executeQuery().use {
-            if (!it.next()) return@runConn null
+            if (!it.next()) return@conn null
             val timestamp = it.getLong("latest_execution_time")
-            if (timestamp == 0L) return@runConn null
-            return@runConn timestamp.microsToJavaInstant()
+            if (timestamp == 0L) return@conn null
+            return@conn timestamp.microsToJavaInstant()
                 ?: throw Exception("Could not convert latest_execution_time to 
Instant")
         }
     }
@@ -392,15 +331,15 @@ class Database(dbConfig: String): java.io.Closeable {
      *
      * @return [Instant] or null if no results were found
      */
-    suspend fun incomingPaymentLastExecTime(): Instant? = runConn { conn ->
+    suspend fun incomingPaymentLastExecTime(): Instant? = conn { conn ->
         val stmt = conn.prepareStatement(
             "SELECT MAX(execution_time) as latest_execution_time FROM 
incoming_transactions"
         )
         stmt.executeQuery().use {
-            if (!it.next()) return@runConn null
+            if (!it.next()) return@conn null
             val timestamp = it.getLong("latest_execution_time")
-            if (timestamp == 0L) return@runConn null
-            return@runConn timestamp.microsToJavaInstant()
+            if (timestamp == 0L) return@conn null
+            return@conn timestamp.microsToJavaInstant()
                 ?: throw Exception("Could not convert latest_execution_time to 
Instant")
         }
     }
@@ -411,7 +350,7 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param maybeReservePub reserve public key to look up
      * @return true if found, false otherwise
      */
-    suspend fun isReservePubFound(maybeReservePub: ByteArray): Boolean = 
runConn { conn ->
+    suspend fun isReservePubFound(maybeReservePub: ByteArray): Boolean = conn 
{ conn ->
         val stmt = conn.prepareStatement("""
              SELECT 1
                FROM talerable_incoming_transactions
@@ -420,7 +359,7 @@ class Database(dbConfig: String): java.io.Closeable {
         stmt.setBytes(1, maybeReservePub)
         val res = stmt.executeQuery()
         res.use {
-            return@runConn it.next()
+            return@conn it.next()
         }
     }
 
@@ -445,7 +384,7 @@ class Database(dbConfig: String): java.io.Closeable {
     suspend fun initiatedPaymentSetSubmittedState(
         rowId: Long,
         submissionState: DatabaseSubmissionState
-    ): Boolean = runConn { conn ->
+    ): Boolean = conn { conn ->
         val stmt = conn.prepareStatement("""
              UPDATE initiated_outgoing_transactions
                       SET submitted = submission_state(?), 
last_submission_time = ?
@@ -458,7 +397,7 @@ class Database(dbConfig: String): java.io.Closeable {
             throw Exception("Submission time could not be converted to 
microseconds for the database.")
         })
         stmt.setLong(3, rowId)
-        return@runConn stmt.maybeUpdate()
+        return@conn stmt.maybeUpdate()
     }
 
     /**
@@ -468,7 +407,7 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param failureMessage error associated to this initiated payment.
      * @return true on success, false if no payment was affected.
      */
-    suspend fun initiatedPaymentSetFailureMessage(rowId: Long, failureMessage: 
String): Boolean = runConn { conn ->
+    suspend fun initiatedPaymentSetFailureMessage(rowId: Long, failureMessage: 
String): Boolean = conn { conn ->
         val stmt = conn.prepareStatement("""
              UPDATE initiated_outgoing_transactions
                       SET failure_message = ?
@@ -477,7 +416,7 @@ class Database(dbConfig: String): java.io.Closeable {
         )
         stmt.setString(1, failureMessage)
         stmt.setLong(2, rowId)
-        return@runConn stmt.maybeUpdate()
+        return@conn stmt.maybeUpdate()
     }
 
     /**
@@ -487,7 +426,7 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param currency in which currency should the payment be submitted to 
the bank.
      * @return [Map] of the initiated payment row ID and [InitiatedPayment]
      */
-    suspend fun initiatedPaymentsSubmittableGet(currency: String): Map<Long, 
InitiatedPayment> = runConn { conn ->
+    suspend fun initiatedPaymentsSubmittableGet(currency: String): Map<Long, 
InitiatedPayment> = conn { conn ->
         val stmt = conn.prepareStatement("""
             SELECT
               initiated_outgoing_transaction_id
@@ -511,19 +450,15 @@ class Database(dbConfig: String): java.io.Closeable {
                     throw Exception("Found invalid timestamp at initiated 
payment with ID: $rowId")
                 }
                 maybeMap[rowId] = InitiatedPayment(
-                    amount = TalerAmount(
-                        value = it.getLong("amount_val"),
-                        fraction = it.getInt("amount_frac"),
-                        currency = currency
-                    ),
-                    creditPaytoUri = it.getString("credit_payto_uri"),
+                    amount = it.getAmount("amount", currency),
+                    creditPaytoUri = 
IbanPayto(it.getString("credit_payto_uri")).requireFull(),
                     wireTransferSubject = 
it.getString("wire_transfer_subject"),
                     initiationTime = initiationTime,
                     requestUid = it.getString("request_uid")
                 )
             } while (it.next())
         }
-        return@runConn maybeMap
+        return@conn maybeMap
     }
     /**
      * Initiate a payment in the database.  The "submit"
@@ -533,7 +468,7 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param paymentData any data that's used to prepare the payment.
      * @return true if the insertion went through, false in case of errors.
      */
-    suspend fun initiatedPaymentCreate(paymentData: InitiatedPayment): 
PaymentInitiationOutcome = runConn { conn ->
+    suspend fun initiatedPaymentCreate(paymentData: InitiatedPayment): 
PaymentInitiationOutcome = conn { conn ->
         val stmt = conn.prepareStatement("""
            INSERT INTO initiated_outgoing_transactions (
              amount
@@ -550,25 +485,21 @@ class Database(dbConfig: String): java.io.Closeable {
            )
         """)
         stmt.setLong(1, paymentData.amount.value)
-        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setInt(2, paymentData.amount.frac)
         stmt.setString(3, paymentData.wireTransferSubject)
-        parsePayto(paymentData.creditPaytoUri).apply {
-            if (this == null) return@runConn 
PaymentInitiationOutcome.BAD_CREDIT_PAYTO
-            if (this.receiverName == null) return@runConn 
PaymentInitiationOutcome.RECEIVER_NAME_MISSING
-        }
-        stmt.setString(4, paymentData.creditPaytoUri)
+        stmt.setString(4, paymentData.creditPaytoUri.full)
         val initiationTime = paymentData.initiationTime.toDbMicros() ?: run {
             throw Exception("Initiation time could not be converted to 
microseconds for the database.")
         }
         stmt.setLong(5, initiationTime)
         stmt.setString(6, paymentData.requestUid) // can be null.
         if (stmt.maybeUpdate())
-            return@runConn PaymentInitiationOutcome.SUCCESS
+            return@conn PaymentInitiationOutcome.SUCCESS
         /**
          * _very_ likely, Nexus didn't check the request idempotency,
          * as the row ID would never fall into the following problem.
          */
-        return@runConn PaymentInitiationOutcome.UNIQUE_CONSTRAINT_VIOLATION
+        return@conn PaymentInitiationOutcome.UNIQUE_CONSTRAINT_VIOLATION
     }
 
     /**
@@ -583,7 +514,7 @@ class Database(dbConfig: String): java.io.Closeable {
      *         null gets returned even when the initiated payment exists,
      *         *but* it was NOT flagged as submitted.
      */
-    suspend fun initiatedPaymentGetFromUid(uid: String): Long? = runConn { 
conn ->
+    suspend fun initiatedPaymentGetFromUid(uid: String): Long? = conn { conn ->
         val stmt = conn.prepareStatement("""
            SELECT initiated_outgoing_transaction_id
              FROM initiated_outgoing_transactions
@@ -592,8 +523,8 @@ class Database(dbConfig: String): java.io.Closeable {
         stmt.setString(1, uid)
         val res = stmt.executeQuery()
         res.use {
-            if (!it.next()) return@runConn null
-            return@runConn it.getLong("initiated_outgoing_transaction_id")
+            if (!it.next()) return@conn null
+            return@conn it.getLong("initiated_outgoing_transaction_id")
         }
     }
 }
\ No newline at end of file
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
index 95b532b3..a05d781a 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
@@ -21,7 +21,7 @@ package tech.libeufin.nexus
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.parameters.options.*
 import com.github.ajalt.clikt.parameters.groups.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 /**
  * This subcommand tries to load the SQL files that define
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index 294a8f51..55a3e401 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -23,11 +23,9 @@ import com.github.ajalt.clikt.parameters.options.*
 import com.github.ajalt.clikt.parameters.groups.*
 import io.ktor.client.*
 import kotlinx.coroutines.*
-import net.taler.wallet.crypto.Base32Crockford
-import net.taler.wallet.crypto.EncodingException
 import tech.libeufin.nexus.ebics.*
-import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.common.*
+import tech.libeufin.ebics.ebics_h005.Ebics3Request
 import java.io.File
 import java.io.IOException
 import java.nio.file.Path
@@ -145,37 +143,6 @@ private fun makeTalerFrac(bankFrac: String): Int {
     return buf
 }
 
-/**
- * Gets Taler amount from a currency-agnostic value.
- *
- * @param noCurrencyAmount currency-agnostic value coming from the bank.
- * @param currency currency to set to the result.
- * @return [TalerAmount]
- */
-fun getTalerAmount(
-    noCurrencyAmount: String,
-    currency: String,
-    errorMessagePrefix: String = ""
-): TalerAmount {
-    if (currency.isEmpty()) throw Exception("Wrong helper invocation: currency 
is empty")
-    val split = noCurrencyAmount.split(".")
-    // only 1 (no fraction) or 2 (with fraction) sizes allowed.
-    if (split.size != 1 && split.size != 2)
-        throw Exception("${errorMessagePrefix}invalid amount: 
$noCurrencyAmount")
-    val value = split[0].toLongOrNull()
-        ?: throw Exception("${errorMessagePrefix}value part '${split[0]}' not 
a long")
-    if (split.size == 1) return TalerAmount(
-        value = value,
-        fraction = 0,
-        currency = currency
-    )
-    return TalerAmount(
-        value = value,
-        fraction = makeTalerFrac(split[1]),
-        currency = currency
-    )
-}
-
 /**
  * Converts valid reserve pubs to its binary representation.
  *
@@ -293,25 +260,6 @@ suspend fun ingestIncomingPayment(
     }
 }
 
-/**
- * Compares amounts.
- *
- * @param a first argument
- * @param b second argument
- * @return true if the first argument
- *         is less than the second
- */
-fun firstLessThanSecond(
-    a: TalerAmount,
-    b: TalerAmount
-): Boolean {
-    if (a.currency != b.currency)
-        throw Exception("different currencies: ${a.currency} vs. 
${b.currency}")
-    if (a.value == b.value)
-        return a.fraction < b.fraction
-    return a.value < b.value
-}
-
 private fun ingestDocument(
     db: Database,
     currency: String,
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index 8c2d9c2e..5278afc1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -24,13 +24,13 @@ import com.github.ajalt.clikt.parameters.options.*
 import com.github.ajalt.clikt.parameters.groups.*
 import io.ktor.client.*
 import kotlinx.coroutines.runBlocking
-import tech.libeufin.util.ebics_h004.EbicsTypes
+import tech.libeufin.ebics.ebics_h004.EbicsTypes
 import java.io.File
-import TalerConfigError
 import kotlinx.serialization.encodeToString
 import tech.libeufin.nexus.ebics.*
-import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.HTDResponseOrderData
+import tech.libeufin.common.*
+import tech.libeufin.ebics.*
+import tech.libeufin.ebics.ebics_h004.HTDResponseOrderData
 import java.time.Instant
 import kotlin.reflect.typeOf
 import java.nio.file.Files
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index 6e8607e3..dbd178b3 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -28,7 +28,7 @@ import tech.libeufin.nexus.ebics.EbicsSideError
 import tech.libeufin.nexus.ebics.EbicsSideException
 import tech.libeufin.nexus.ebics.EbicsUploadException
 import tech.libeufin.nexus.ebics.submitPain001
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.io.File
 import java.nio.file.Path
 import java.time.Instant
@@ -97,18 +97,12 @@ class NexusSubmitException(
 private suspend fun submitInitiatedPayment(
     ctx: SubmissionContext,
     initiatedPayment: InitiatedPayment
-) {
-    val creditor = parsePayto(initiatedPayment.creditPaytoUri)
-    if (creditor?.receiverName == null)
-        throw NexusSubmitException(
-            "Won't create pain.001 without the receiver name",
-            stage = NexusSubmissionStage.pain
-        )
+) { 
     val xml = createPain001(
         requestUid = initiatedPayment.requestUid,
         initiationTimestamp = initiatedPayment.initiationTime,
         amount = initiatedPayment.amount,
-        creditAccount = creditor,
+        creditAccount = initiatedPayment.creditPaytoUri,
         debitAccount = ctx.cfg.myIbanAccount,
         wireTransferSubject = initiatedPayment.wireTransferSubject
     )
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
index 0617e12a..005b010d 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt
@@ -18,7 +18,8 @@
  */
 package tech.libeufin.nexus
 
-import tech.libeufin.util.*
+import tech.libeufin.common.*
+import tech.libeufin.ebics.*
 import java.net.URLEncoder
 import java.time.Instant
 import java.time.ZoneId
@@ -44,12 +45,10 @@ data class Pain001Namespaces(
  * @return [String] of the amount number without the currency.
  */
 fun getAmountNoCurrency(amount: TalerAmount): String {
-    if (amount.fraction.toString().length > 8)
-        throw Exception("Taler amount must have at most 8 fractional digits")
-    if (amount.fraction == 0) {
+    if (amount.frac == 0) {
         return amount.value.toString()
     } else {
-        val fractionFormat = amount.fraction.toString().padStart(8, 
'0').dropLastWhile { it == '0' }
+        val fractionFormat = amount.frac.toString().padStart(8, 
'0').dropLastWhile { it == '0' }
         if (fractionFormat.length > 2) throw Exception("Sub-cent amounts not 
supported")
         return "${amount.value}.${fractionFormat}"
     }
@@ -78,7 +77,7 @@ fun createPain001(
     debitAccount: IbanAccountMetadata,
     amount: TalerAmount,
     wireTransferSubject: String,
-    creditAccount: IbanPayto
+    creditAccount: FullIbanPayto
 ): String {
     val namespace = Pain001Namespaces(
         fullNamespace = "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09",
@@ -86,11 +85,6 @@ fun createPain001(
     )
     val zonedTimestamp = ZonedDateTime.ofInstant(initiationTimestamp, 
ZoneId.of("UTC"))
     val amountWithoutCurrency: String = getAmountNoCurrency(amount)
-    val creditorName: String = creditAccount.receiverName
-        ?: throw NexusSubmitException(
-            "Cannot operate without the creditor name",
-            stage=NexusSubmissionStage.pain
-        )
     return constructXml {
         root("Document") {
             attribute(
@@ -158,10 +152,10 @@ fun createPain001(
                             text(amountWithoutCurrency)
                         }
                         element("Cdtr/Nm") {
-                            text(creditorName)
+                            text(creditAccount.receiverName)
                         }
                         element("CdtrAcct/Id/IBAN") {
-                            text(creditAccount.iban)
+                            text(creditAccount.payto.iban)
                         }
                         element("RmtInf/Ustrd") {
                             text(wireTransferSubject)
@@ -332,7 +326,7 @@ fun parseTxNotif(
              * FIXME: test by sending non-CHF to PoFi and see which currency 
gets here.
              */
             if (currency != acceptedCurrency) throw Exception("Currency 
$currency not supported")
-            getTalerAmount(focusElement.textContent, currency)
+            TalerAmount("$currency:${focusElement.textContent}")
         }
         when (kind) {
             "CRDT" -> {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
index 25ed95af..e59b65f4 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Log.kt
@@ -20,7 +20,7 @@
 package tech.libeufin.nexus
 
 import tech.libeufin.nexus.ebics.unzipForEach
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.io.*
 import java.nio.file.*
 import kotlin.io.path.*
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
index da6e6237..699af965 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -23,8 +23,7 @@
  * kept in their respective files.
  */
 package tech.libeufin.nexus
-import ConfigSource
-import TalerConfig
+
 import com.github.ajalt.clikt.core.CliktCommand
 import com.github.ajalt.clikt.core.subcommands
 import com.github.ajalt.clikt.parameters.options.versionOption
@@ -43,9 +42,8 @@ import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
-import net.taler.wallet.crypto.Base32Crockford
 import tech.libeufin.nexus.ebics.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.security.interfaces.RSAPrivateCrtKey
 import java.security.interfaces.RSAPublicKey
 import java.io.FileNotFoundException
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
index 19fe88e6..ff59fa7e 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics2.kt
@@ -30,9 +30,9 @@ import org.slf4j.LoggerFactory
 import tech.libeufin.nexus.BankPublicKeysFile
 import tech.libeufin.nexus.ClientPrivateKeysFile
 import tech.libeufin.nexus.EbicsSetupConfig
-import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h004.*
-import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.ebics.*
+import tech.libeufin.ebics.ebics_h004.*
+import tech.libeufin.ebics.ebics_h005.Ebics3Request
 import java.security.interfaces.RSAPrivateCrtKey
 import java.time.Instant
 import java.time.ZoneId
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
index 647aca74..16e5daba 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/Ebics3.kt
@@ -23,11 +23,11 @@ import tech.libeufin.nexus.BankPublicKeysFile
 import tech.libeufin.nexus.ClientPrivateKeysFile
 import tech.libeufin.nexus.EbicsSetupConfig
 import tech.libeufin.nexus.logger
-import tech.libeufin.util.PreparedUploadData
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h005.Ebics3Request
-import tech.libeufin.util.getNonce
-import tech.libeufin.util.getXmlDate
+import tech.libeufin.ebics.PreparedUploadData
+import tech.libeufin.ebics.XMLUtil
+import tech.libeufin.ebics.ebics_h005.Ebics3Request
+import tech.libeufin.ebics.getNonce
+import tech.libeufin.ebics.getXmlDate
 import java.math.BigInteger
 import java.time.Instant
 import java.util.*
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
index 642b60ca..167954d0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsCommon.kt
@@ -47,8 +47,9 @@ import 
org.apache.commons.compress.utils.SeekableInMemoryByteChannel
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.nexus.*
-import tech.libeufin.util.*
-import tech.libeufin.util.ebics_h005.Ebics3Request
+import tech.libeufin.common.*
+import tech.libeufin.ebics.*
+import tech.libeufin.ebics.ebics_h005.Ebics3Request
 import java.io.ByteArrayOutputStream
 import java.security.interfaces.RSAPrivateCrtKey
 import java.time.LocalDateTime
diff --git a/nexus/src/test/kotlin/CliTest.kt b/nexus/src/test/kotlin/CliTest.kt
index 9ad737e3..a57088c7 100644
--- a/nexus/src/test/kotlin/CliTest.kt
+++ b/nexus/src/test/kotlin/CliTest.kt
@@ -24,7 +24,7 @@ import kotlin.test.*
 import java.io.*
 import java.nio.file.*
 import kotlin.io.path.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 
 val nexusCmd = LibeufinNexusCommand()
 
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index 5adbf157..f5c2f07e 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -23,7 +23,7 @@ import io.ktor.client.request.*
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
 import tech.libeufin.nexus.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.security.interfaces.RSAPrivateCrtKey
 import java.time.Instant
 
@@ -103,7 +103,7 @@ fun genInitPay(
 ) =
     InitiatedPayment(
         amount = TalerAmount(44, 0, "KUDOS"),
-        creditPaytoUri = "payto://iban/TEST-IBAN?receiver-name=Test",
+        creditPaytoUri = 
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Test").requireFull(),
         wireTransferSubject = subject,
         initiationTime = Instant.now(),
         requestUid = requestUid
@@ -123,7 +123,7 @@ fun genInPay(subject: String) =
 fun genOutPay(subject: String, messageId: String) =
     OutgoingPayment(
         amount = TalerAmount(44, 0, "KUDOS"),
-        creditPaytoUri = "payto://iban/TEST-IBAN?receiver-name=Test",
+        creditPaytoUri = 
"payto://iban/CH9300762011623852957?receiver-name=Test",
         wireTransferSubject = subject,
         executionTime = Instant.now(),
         messageId = messageId
diff --git a/nexus/src/test/kotlin/ConfigLoading.kt 
b/nexus/src/test/kotlin/ConfigLoading.kt
index bd636f6a..cb9159b0 100644
--- a/nexus/src/test/kotlin/ConfigLoading.kt
+++ b/nexus/src/test/kotlin/ConfigLoading.kt
@@ -24,6 +24,7 @@ import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE
 import tech.libeufin.nexus.getFrequencyInSeconds
 import kotlin.test.assertEquals
 import kotlin.test.assertNull
+import tech.libeufin.common.*
 
 class ConfigLoading {
     /**
@@ -43,7 +44,6 @@ class ConfigLoading {
         val handle = TalerConfig(NEXUS_CONFIG_SOURCE)
         handle.load()
         val cfg = EbicsSetupConfig(handle)
-        cfg.config.requirePath("nexus-fetch", "statement_log_directory")
     }
 
 
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt 
b/nexus/src/test/kotlin/DatabaseTest.kt
index ed46598f..58fa9735 100644
--- a/nexus/src/test/kotlin/DatabaseTest.kt
+++ b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -20,6 +20,7 @@
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
 import tech.libeufin.nexus.*
+import tech.libeufin.common.*
 import java.time.Instant
 import kotlin.random.Random
 import kotlin.test.*
@@ -70,19 +71,19 @@ class IncomingPaymentsTest {
             val payment = genInPay("incoming and bounced")
             db.registerMalformedIncoming(
                 payment,
-                TalerAmount(2, 53000000, "KUDOS"),
+                TalerAmount("KUDOS:2.53"),
                 Instant.now()
             ).run {
                 assertTrue(new)
             }
             db.registerMalformedIncoming(
                 payment,
-                TalerAmount(2, 53000000, "KUDOS"),
+                TalerAmount("KUDOS:2.53"),
                 Instant.now()
             ).run {
                 assertFalse(new)
             }
-            db.runConn {
+            db.conn {
                 // Checking one incoming got created
                 val checkIncoming = it.prepareStatement("""
                     SELECT (amount).val as amount_value, (amount).frac as 
amount_frac 
@@ -90,7 +91,7 @@ class IncomingPaymentsTest {
                 """).executeQuery()
                 assertTrue(checkIncoming.next())
                 assertEquals(payment.amount.value, 
checkIncoming.getLong("amount_value"))
-                assertEquals(payment.amount.fraction, 
checkIncoming.getInt("amount_frac"))
+                assertEquals(payment.amount.frac, 
checkIncoming.getInt("amount_frac"))
                 // Checking the bounced table got its row.
                 val checkBounced = it.prepareStatement("""
                     SELECT 1 FROM bounced_transactions 
@@ -150,7 +151,7 @@ class PaymentInitiationsTest {
             assertFalse(db.initiatedPaymentSetFailureMessage(3, "3 not 
existing"))
             assertTrue(db.initiatedPaymentSetFailureMessage(1, "expired"))
             // Checking the value from the database.
-            db.runConn { conn ->
+            db.conn { conn ->
                 val idOne = conn.execSQLQuery("""
                     SELECT failure_message
                       FROM initiated_outgoing_transactions
@@ -178,7 +179,7 @@ class PaymentInitiationsTest {
                 db.initiatedPaymentCreate(genInitPay("not submitted, has row 
ID == 1")),
             )
             // Asserting on the false default submitted state.
-            db.runConn { conn ->
+            db.conn { conn ->
                 val isSubmitted = conn.execSQLQuery(getRowOne)
                 assertTrue(isSubmitted.next())
                 assertEquals("unsubmitted", isSubmitted.getString("submitted"))
@@ -186,7 +187,7 @@ class PaymentInitiationsTest {
             // Switching the submitted state to success.
             assertTrue(db.initiatedPaymentSetSubmittedState(1, 
DatabaseSubmissionState.success))
             // Asserting on the submitted state being TRUE now.
-            db.runConn { conn ->
+            db.conn { conn ->
                 val isSubmitted = conn.execSQLQuery(getRowOne)
                 assertTrue(isSubmitted.next())
                 assertEquals("success", isSubmitted.getString("submitted"))
@@ -205,7 +206,7 @@ class PaymentInitiationsTest {
         }
         val initPay = InitiatedPayment(
             amount = TalerAmount(44, 0, "KUDOS"),
-            creditPaytoUri = "payto://iban/TEST-IBAN?receiver-name=Test",
+            creditPaytoUri = 
IbanPayto("payto://iban/CH9300762011623852957?receiver-name=Test").requireFull(),
             wireTransferSubject = "test",
             requestUid = "unique",
             initiationTime = Instant.now()
@@ -275,7 +276,7 @@ class PaymentInitiationsTest {
             assertEquals(db.initiatedPaymentCreate(genInitPay("#4", 
"unique4")), PaymentInitiationOutcome.SUCCESS)
 
             // Marking one as submitted, hence not expecting it in the results.
-            db.runConn { conn ->
+            db.conn { conn ->
                 conn.execSQLUpdate("""
                     UPDATE initiated_outgoing_transactions
                       SET submitted='success'
diff --git a/nexus/src/test/kotlin/Ebics.kt b/nexus/src/test/kotlin/Ebics.kt
index 44978095..5f3a3782 100644
--- a/nexus/src/test/kotlin/Ebics.kt
+++ b/nexus/src/test/kotlin/Ebics.kt
@@ -25,8 +25,8 @@ import org.junit.Ignore
 import org.junit.Test
 import tech.libeufin.nexus.*
 import tech.libeufin.nexus.ebics.*
-import tech.libeufin.util.XMLUtil
-import tech.libeufin.util.ebics_h004.EbicsUnsecuredRequest
+import tech.libeufin.ebics.XMLUtil
+import tech.libeufin.ebics.ebics_h004.EbicsUnsecuredRequest
 import java.io.File
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
diff --git a/nexus/src/test/kotlin/Keys.kt b/nexus/src/test/kotlin/Keys.kt
index 189de0db..41b6b11f 100644
--- a/nexus/src/test/kotlin/Keys.kt
+++ b/nexus/src/test/kotlin/Keys.kt
@@ -19,7 +19,7 @@
 
 import org.junit.Test
 import tech.libeufin.nexus.*
-import tech.libeufin.util.CryptoUtil
+import tech.libeufin.common.CryptoUtil
 import java.io.File
 import kotlin.test.*
 
diff --git a/nexus/src/test/kotlin/MySerializers.kt 
b/nexus/src/test/kotlin/MySerializers.kt
index 0076dd55..2c109fd7 100644
--- a/nexus/src/test/kotlin/MySerializers.kt
+++ b/nexus/src/test/kotlin/MySerializers.kt
@@ -20,13 +20,13 @@
 import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 import kotlinx.serialization.modules.SerializersModule
-import net.taler.wallet.crypto.Base32Crockford
 import org.junit.Test
 import tech.libeufin.nexus.ClientPrivateKeysFile
 import tech.libeufin.nexus.RSAPrivateCrtKeySerializer
-import tech.libeufin.util.CryptoUtil
+import tech.libeufin.common.CryptoUtil
 import java.security.interfaces.RSAPrivateCrtKey
 import kotlin.test.assertEquals
+import tech.libeufin.common.*
 
 class MySerializers {
     // Testing deserialization of RSA private keys.
diff --git a/nexus/src/test/kotlin/Parsing.kt b/nexus/src/test/kotlin/Parsing.kt
index 4196f3cc..d71f42b2 100644
--- a/nexus/src/test/kotlin/Parsing.kt
+++ b/nexus/src/test/kotlin/Parsing.kt
@@ -20,8 +20,9 @@
 import org.junit.Test
 import org.junit.jupiter.api.assertThrows
 import tech.libeufin.nexus.*
-import tech.libeufin.util.parseBookDate
-import tech.libeufin.util.parseCamtTime
+import tech.libeufin.common.*
+import tech.libeufin.common.parseBookDate
+import tech.libeufin.common.parseCamtTime
 import java.lang.StringBuilder
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
@@ -30,18 +31,6 @@ import kotlin.test.assertTrue
 
 class Parsing {
 
-    @Test // move eventually to util (#7987)
-    fun amountComparison() {
-        val one = TalerAmount(1, 0, "KUDOS")
-        val two = TalerAmount(2, 0, "KUDOS")
-        val moreFrac = TalerAmount(2, 4, "KUDOS")
-        val lessFrac = TalerAmount(2, 3, "KUDOS")
-        val zeroMoreFrac = TalerAmount(0, 4, "KUDOS")
-        val zeroLessFrac = TalerAmount(0, 3, "KUDOS")
-        assertTrue(firstLessThanSecond(one, two))
-        assertTrue(firstLessThanSecond(lessFrac, moreFrac))
-        assertTrue(firstLessThanSecond(zeroLessFrac, zeroMoreFrac))
-    }
     @Test
     fun gregorianTime() {
         parseCamtTime("2023-11-06T20:00:00")
@@ -119,50 +108,6 @@ class Parsing {
             getAmountNoCurrency(TalerAmount(0, 10000000, "KUDOS"))
         )
     }
-    @Test // parses amounts as found in the camt.05x documents.
-    fun parseCurrencyAgnosticAmount() {
-        assertTrue {
-            getTalerAmount("1.00", "KUDOS").run {
-                this.value == 1L && this.fraction == 0 && this.currency == 
"KUDOS"
-            }
-        }
-        assertTrue {
-            getTalerAmount("1", "KUDOS").run {
-                this.value == 1L && this.fraction == 0 && this.currency == 
"KUDOS"
-            }
-        }
-        assertTrue {
-            getTalerAmount("0.99", "KUDOS").run {
-                this.value == 0L && this.fraction == 99000000 && this.currency 
== "KUDOS"
-            }
-        }
-        assertTrue {
-            getTalerAmount("0.01", "KUDOS").run {
-                this.value == 0L && this.fraction == 1000000 && this.currency 
== "KUDOS"
-            }
-        }
-        assertThrows<Exception> {
-            getTalerAmount("", "")
-        }
-        assertThrows<Exception> {
-            getTalerAmount(".1", "KUDOS")
-        }
-        assertThrows<Exception> {
-            getTalerAmount("1.", "KUDOS")
-        }
-        assertThrows<Exception> {
-            getTalerAmount("0.123", "KUDOS")
-        }
-        assertThrows<Exception> {
-            getTalerAmount("noise", "KUDOS")
-        }
-        assertThrows<Exception> {
-            getTalerAmount("1.noise", "KUDOS")
-        }
-        assertThrows<Exception> {
-            getTalerAmount("5", "")
-        }
-    }
 
     // Checks that the input decodes to a 32-bytes value.
     @Test
diff --git a/settings.gradle b/settings.gradle
index b3b8fcad..48e088f8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,6 @@
 rootProject.name = 'libeufin'
 include("bank")
 include("nexus")
-include("util")
-include("integration")
+include("common")
+include("testbench")
+include("ebics")
\ No newline at end of file
diff --git a/integration/build.gradle b/testbench/build.gradle
similarity index 86%
rename from integration/build.gradle
rename to testbench/build.gradle
index 7c366e4c..93fb6a46 100644
--- a/integration/build.gradle
+++ b/testbench/build.gradle
@@ -16,7 +16,7 @@ sourceSets.main.java.srcDirs = ["src/main/kotlin"]
 dependencies {
     
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version")
 
-    implementation(project(":util"))
+    implementation(project(":common"))
     implementation(project(":bank"))
     implementation(project(":nexus"))
 
@@ -30,8 +30,8 @@ dependencies {
 }
 
 application {
-    mainClass = "tech.libeufin.integration.MainKt"
-    applicationName = "libeufin-integration-test"
+    mainClass = "tech.libeufin.testbench.MainKt"
+    applicationName = "libeufin-testbench-test"
 }
 
 run {
diff --git a/integration/conf/integration.conf b/testbench/conf/integration.conf
similarity index 100%
rename from integration/conf/integration.conf
rename to testbench/conf/integration.conf
diff --git a/integration/conf/mini.conf b/testbench/conf/mini.conf
similarity index 100%
rename from integration/conf/mini.conf
rename to testbench/conf/mini.conf
diff --git a/integration/conf/netzbon.conf b/testbench/conf/netzbon.conf
similarity index 100%
rename from integration/conf/netzbon.conf
rename to testbench/conf/netzbon.conf
diff --git a/integration/conf/postfinance.conf b/testbench/conf/postfinance.conf
similarity index 100%
rename from integration/conf/postfinance.conf
rename to testbench/conf/postfinance.conf
diff --git a/integration/src/main/kotlin/Main.kt 
b/testbench/src/main/kotlin/Main.kt
similarity index 92%
rename from integration/src/main/kotlin/Main.kt
rename to testbench/src/main/kotlin/Main.kt
index 854b8c45..fef935b9 100644
--- a/integration/src/main/kotlin/Main.kt
+++ b/testbench/src/main/kotlin/Main.kt
@@ -17,13 +17,12 @@
  * <http://www.gnu.org/licenses/>
  */
 
-package tech.libeufin.integration
+package tech.libeufin.testbench
 
 import tech.libeufin.nexus.Database as NexusDb
-import tech.libeufin.nexus.TalerAmount as NexusAmount
 import tech.libeufin.nexus.*
 import tech.libeufin.bank.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import com.github.ajalt.clikt.core.*
 import com.github.ajalt.clikt.parameters.arguments.*
 import com.github.ajalt.clikt.parameters.types.*
@@ -36,7 +35,6 @@ import java.nio.file.*
 import java.time.Instant
 import kotlinx.coroutines.runBlocking
 import io.ktor.client.request.*
-import net.taler.wallet.crypto.Base32Crockford
 import kotlin.io.path.*
 
 fun randBytes(lenght: Int): ByteArray {
@@ -125,7 +123,7 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                     val payto = 
"payto://iban/CH2989144971918294289?receiver-name=Test"
         
                     step("Test fetch transactions")
-                    nexusCmd.test("ebics-fetch $ebicsFlag --pinned-start 
2022-01-01").assertOk()
+                    nexusCmd.test("ebics-fetch $ebicsFlags --pinned-start 
2022-01-01").assertOk()
 
                     while (true) {
                         when (ask("Run 'fetch', 'submit', 'tx', 'txs', 'logs', 
'ack' or 'exit'>")) {
@@ -136,8 +134,8 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                             "tx" -> {
                                 step("Test submit one transaction")
                                 
nexusDb.initiatedPaymentCreate(InitiatedPayment(
-                                    amount = NexusAmount(42L, 0, "CFH"),
-                                    creditPaytoUri = payto,
+                                    amount = TalerAmount("CFH:42"),
+                                    creditPaytoUri = 
IbanPayto(payto).requireFull(),
                                     wireTransferSubject = "single transaction 
test",
                                     initiationTime = Instant.now(),
                                     requestUid = 
Base32Crockford.encode(randBytes(16))
@@ -148,8 +146,8 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                                 step("Test submit many transaction")
                                 repeat(4) {
                                     
nexusDb.initiatedPaymentCreate(InitiatedPayment(
-                                        amount = NexusAmount(100L + it, 0, 
"CFH"),
-                                        creditPaytoUri = payto,
+                                        amount = TalerAmount("CFH:${100L+it}"),
+                                        creditPaytoUri = 
IbanPayto(payto).requireFull(),
                                         wireTransferSubject = "multi 
transaction test $it",
                                         initiationTime = Instant.now(),
                                         requestUid = 
Base32Crockford.encode(randBytes(16))
@@ -199,8 +197,8 @@ class Cli : CliktCommand("Run integration tests on banks 
provider") {
                                 step("Submit new transaction")
                                 // TODO interactive payment editor
                                 
nexusDb.initiatedPaymentCreate(InitiatedPayment(
-                                    amount = getTalerAmount("1.1", "CFH"),
-                                    creditPaytoUri = 
"payto://iban/CH6208704048981247126?receiver-name=Grothoff%20Hans",
+                                    amount = TalerAmount("CFH:1.1"),
+                                    creditPaytoUri = 
IbanPayto("payto://iban/CH6208704048981247126?receiver-name=Grothoff%20Hans").requireFull(),
                                     wireTransferSubject = "single transaction 
test",
                                     initiationTime = Instant.now(),
                                     requestUid = 
Base32Crockford.encode(randBytes(16))
diff --git a/integration/src/test/kotlin/IntegrationTest.kt 
b/testbench/src/test/kotlin/IntegrationTest.kt
similarity index 84%
rename from integration/src/test/kotlin/IntegrationTest.kt
rename to testbench/src/test/kotlin/IntegrationTest.kt
index a1fb79b6..dd4023aa 100644
--- a/integration/src/test/kotlin/IntegrationTest.kt
+++ b/testbench/src/test/kotlin/IntegrationTest.kt
@@ -18,14 +18,11 @@
  */
 
 import org.junit.Test
-import net.taler.wallet.crypto.Base32Crockford
 import tech.libeufin.bank.*
-import tech.libeufin.bank.TalerAmount as BankAmount
 import tech.libeufin.nexus.*
 import tech.libeufin.nexus.Database as NexusDb
-import tech.libeufin.nexus.TalerAmount as NexusAmount
 import tech.libeufin.bank.db.AccountDAO.*
-import tech.libeufin.util.*
+import tech.libeufin.common.*
 import java.io.File
 import java.time.Instant
 import java.util.Arrays
@@ -104,12 +101,13 @@ class IntegrationTest {
 
     @Test
     fun mini() {
-        bankCmd.run("dbinit -c conf/mini.conf -r")
-        bankCmd.run("passwd admin password -c conf/mini.conf")
-        bankCmd.run("dbinit -c conf/mini.conf") // Indempotent
+        val flags = "-c conf/mini.conf -L DEBUG"
+        bankCmd.run("dbinit $flags -r")
+        bankCmd.run("passwd admin password $flags")
+        bankCmd.run("dbinit $flags") // Indempotent
         
         server {
-            bankCmd.run("serve -c conf/mini.conf")
+            bankCmd.run("serve $flags")
         }
         
         setup { _ ->
@@ -120,12 +118,13 @@ class IntegrationTest {
 
     @Test
     fun errors() {
-        nexusCmd.run("dbinit -c conf/integration.conf -r")
-        bankCmd.run("dbinit -c conf/integration.conf -r")
-        bankCmd.run("passwd admin password -c conf/integration.conf")
+        val flags = "-c conf/integration.conf -L DEBUG"
+        nexusCmd.run("dbinit $flags -r")
+        bankCmd.run("dbinit $flags -r")
+        bankCmd.run("passwd admin password $flags")
 
         suspend fun checkCount(db: NexusDb, nbIncoming: Int, nbBounce: Int, 
nbTalerable: Int) {
-            db.runConn { conn ->
+            db.conn { conn ->
                 conn.prepareStatement("SELECT count(*) FROM 
incoming_transactions").oneOrNull {
                     assertEquals(nbIncoming, it.getInt(1))
                 }
@@ -139,19 +138,19 @@ class IntegrationTest {
         }
 
         setup { db ->
-            val userPayTo = IbanPayTo(genIbanPaytoUri())
-            val fiatPayTo = IbanPayTo(genIbanPaytoUri())
+            val userPayTo = IbanPayto(genIbanPaytoUri())
+            val fiatPayTo = IbanPayto(genIbanPaytoUri())
     
             // Load conversion setup manually as the server would refuse to 
start without an exchange account
             val sqlProcedures = 
File("../database-versioning/libeufin-conversion-setup.sql")
-            db.runConn { 
+            db.conn { 
                 it.execSQLUpdate(sqlProcedures.readText())
                 it.execSQLUpdate("SET search_path TO libeufin_nexus;")
             }
 
             val reservePub = randBytes(32)
             val payment = IncomingPayment(
-                amount = NexusAmount(10, 0, "EUR"),
+                amount = TalerAmount("EUR:10"),
                 debitPaytoUri = userPayTo.canonical,
                 wireTransferSubject = "Error test 
${Base32Crockford.encode(reservePub)}",
                 executionTime = Instant.now(),
@@ -163,7 +162,7 @@ class IntegrationTest {
             }
 
             // Create exchange account
-            bankCmd.run("create-account -c conf/integration.conf -u exchange 
-p password --name 'Mr Money' --exchange")
+            bankCmd.run("create-account $flags -u exchange -p password --name 
'Mr Money' --exchange")
     
             assertException("ERROR: cashin currency conversion failed: missing 
conversion rates") {
                 ingestIncomingPayment(db, payment)
@@ -171,7 +170,7 @@ class IntegrationTest {
 
             // Start server
             server {
-                bankCmd.run("serve -c conf/integration.conf")
+                bankCmd.run("serve $flags")
             }
 
             // Set conversion rates
@@ -196,12 +195,12 @@ class IntegrationTest {
             }
 
             // Allow admin debt
-            bankCmd.run("edit-account admin --debit_threshold KUDOS:100 -c 
conf/integration.conf")
+            bankCmd.run("edit-account admin --debit_threshold KUDOS:100 
$flags")
 
             // Too small amount
             checkCount(db, 0, 0, 0)
             ingestIncomingPayment(db, payment.copy(
-                amount = NexusAmount(0, 10, "EUR"),
+                amount = TalerAmount("EUR:0.01"),
             ))
             checkCount(db, 1, 1, 0)
             client.get("http://0.0.0.0:8080/accounts/exchange/transactions";) {
@@ -210,7 +209,7 @@ class IntegrationTest {
 
             // Check success
             ingestIncomingPayment(db, IncomingPayment(
-                amount = NexusAmount(10, 0, "EUR"),
+                amount = TalerAmount("EUR:10"),
                 debitPaytoUri = userPayTo.canonical,
                 wireTransferSubject = "Success 
${Base32Crockford.encode(randBytes(32))}",
                 executionTime = Instant.now(),
@@ -227,21 +226,22 @@ class IntegrationTest {
 
     @Test
     fun conversion() {
-        nexusCmd.run("dbinit -c conf/integration.conf -r")
-        bankCmd.run("dbinit -c conf/integration.conf -r")
-        bankCmd.run("passwd admin password -c conf/integration.conf")
-        bankCmd.run("edit-account admin --debit_threshold KUDOS:1000 -c 
conf/integration.conf")
-        bankCmd.run("create-account -c conf/integration.conf -u exchange -p 
password --name 'Mr Money' --exchange")
-        nexusCmd.run("dbinit -c conf/integration.conf") // Idempotent
-        bankCmd.run("dbinit -c conf/integration.conf") // Idempotent
+        val flags = "-c conf/integration.conf -L DEBUG"
+        nexusCmd.run("dbinit $flags -r")
+        bankCmd.run("dbinit $flags -r")
+        bankCmd.run("passwd admin password $flags")
+        bankCmd.run("edit-account admin --debit_threshold KUDOS:1000 $flags")
+        bankCmd.run("create-account $flags -u exchange -p password --name 'Mr 
Money' --exchange")
+        nexusCmd.run("dbinit $flags") // Idempotent
+        bankCmd.run("dbinit $flags") // Idempotent
 
         server {
-            bankCmd.run("serve -c conf/integration.conf")
+            bankCmd.run("serve $flags")
         }
         
         setup { db -> 
-            val userPayTo = IbanPayTo(genIbanPaytoUri())
-            val fiatPayTo = IbanPayTo(genIbanPaytoUri())
+            val userPayTo = IbanPayto(genIbanPaytoUri())
+            val fiatPayTo = IbanPayto(genIbanPaytoUri())
 
             // Create user
             client.post("http://0.0.0.0:8080/accounts";) {
@@ -279,7 +279,7 @@ class IntegrationTest {
             // Cashin
             repeat(3) { i ->
                 val reservePub = randBytes(32);
-                val amount = NexusAmount(20L + i, 0, "EUR")
+                val amount = TalerAmount("EUR:${20+i}")
                 val subject = "cashin test $i: 
${Base32Crockford.encode(reservePub)}"
                 ingestIncomingPayment(db, 
                     IncomingPayment(
@@ -311,7 +311,7 @@ class IntegrationTest {
             // Cashout
             repeat(3) { i ->  
                 val requestUid = randBytes(32);
-                val amount = BankAmount("KUDOS:${10+i}")
+                val amount = TalerAmount("KUDOS:${10+i}")
                 val convert = 
client.get("http://0.0.0.0:8080/conversion-info/cashout-rate?amount_debit=$amount";)
                     .assertOkJson<ConversionResponse>().amount_credit;
                 client.post("http://0.0.0.0:8080/accounts/customer/cashouts";) {
diff --git a/util/src/main/kotlin/IbanPayto.kt 
b/util/src/main/kotlin/IbanPayto.kt
deleted file mode 100644
index b1712258..00000000
--- a/util/src/main/kotlin/IbanPayto.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
-
- * LibEuFin 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.
-
- * LibEuFin is distributed in the hope that 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 LibEuFin; see the file COPYING.  If not, see
- * <http://www.gnu.org/licenses/>
- */
-
-package tech.libeufin.util
-
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import java.net.URI
-import java.net.URLDecoder
-
-private val logger: Logger = LoggerFactory.getLogger("libeufin-common")
-
-// Payto information.
-data class IbanPayto(
-    // represent query param "sender-name" or "receiver-name".
-    val receiverName: String?,
-    val iban: String,
-    val bic: String?,
-    // Typically, a wire transfer's subject.
-    val message: String?,
-    val amount: String?
-)
-
-// Return the value of query string parameter 'name', or null if not found.
-// 'params' is the list of key-value elements of all the query parameters 
found in the URI.
-private fun getQueryParamOrNull(name: String, params: List<Pair<String, 
String>>?): String? {
-    if (params == null) return null
-    return params.firstNotNullOfOrNull { pair ->
-        URLDecoder.decode(pair.second, Charsets.UTF_8).takeIf { pair.first == 
name }
-    }
-}
-
-// Parses a Payto URI, returning null if the input is invalid.
-fun parsePayto(payto: String): IbanPayto? {
-    /**
-     * This check is due because URIs having a "payto:" prefix without
-     * slashes are correctly parsed by the Java 'URI' class.  'mailto'
-     * for example lacks the double-slash part.
-     */
-    if (!payto.startsWith("payto://")) {
-        logger.error("Invalid payto URI: $payto")
-        return null
-    }
-
-    val javaParsedUri = try {
-        URI(payto)
-    } catch (e: java.lang.Exception) {
-        logger.error("'${payto}' is not a valid URI")
-        return null
-    }
-    if (javaParsedUri.scheme != "payto") {
-        logger.error("'${payto}' is not payto")
-        return null
-    }
-    val wireMethod = javaParsedUri.host
-    if (wireMethod != "iban") {
-        logger.error("Only 'iban' is supported, not '$wireMethod'")
-        return null
-    }
-    val splitPath = javaParsedUri.path.split("/").filter { it.isNotEmpty() }
-    if (splitPath.size > 2) {
-        logger.error("too many path segments in iban payto URI: $payto")
-        return null
-    }
-    val (iban, bic) = if (splitPath.size == 1) {
-        Pair(splitPath[0], null)
-    } else Pair(splitPath[1], splitPath[0])
-
-    val params: List<Pair<String, String>>? = if (javaParsedUri.query != null) 
{
-        val queryString: List<String> = javaParsedUri.query.split("&")
-        queryString.map {
-            val split = it.split("=");
-            if (split.size != 2) {
-                logger.error("parameter '$it' was malformed")
-                return null
-            }
-            Pair(split[0], split[1])
-        }
-    } else null
-
-    return IbanPayto(
-        iban = iban,
-        bic = bic,
-        amount = getQueryParamOrNull("amount", params),
-        message = getQueryParamOrNull("message", params),
-        receiverName = getQueryParamOrNull("receiver-name", params)
-    )
-}
\ No newline at end of file
diff --git a/util/src/test/kotlin/PaytoTest.kt 
b/util/src/test/kotlin/PaytoTest.kt
deleted file mode 100644
index ff075b58..00000000
--- a/util/src/test/kotlin/PaytoTest.kt
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2024 Taler Systems S.A.
-
- * LibEuFin 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.
-
- * LibEuFin is distributed in the hope that 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 LibEuFin; see the file COPYING.  If not, see
- * <http://www.gnu.org/licenses/>
- */
-
-import org.junit.Test
-import tech.libeufin.util.IbanPayto
-import tech.libeufin.util.parsePayto
-
-class PaytoTest {
-
-    @Test
-    fun wrongCases() {
-        
assert(parsePayto("http://iban/BIC123/IBAN123?receiver-name=The%20Name";) == 
null)
-        
assert(parsePayto("payto:iban/BIC123/IBAN123?receiver-name=The%20Name&address=house")
 == null)
-        
assert(parsePayto("payto://wrong/BIC123/IBAN123?sender-name=Foo&receiver-name=Foo")
 == null)
-    }
-
-    @Test
-    fun parsePaytoTest() {
-        val withBic: IbanPayto = 
parsePayto("payto://iban/BIC123/IBAN123?receiver-name=The%20Name")!!
-        assert(withBic.iban == "IBAN123")
-        assert(withBic.bic == "BIC123")
-        assert(withBic.receiverName == "The Name")
-        val complete = 
parsePayto("payto://iban/BIC123/IBAN123?sender-name=The%20Name&amount=EUR:1&message=donation")!!
-        assert(withBic.iban == "IBAN123")
-        assert(withBic.bic == "BIC123")
-        assert(withBic.receiverName == "The Name")
-        assert(complete.message == "donation")
-        assert(complete.amount == "EUR:1")
-        val withoutOptionals = parsePayto("payto://iban/IBAN123")!!
-        assert(withoutOptionals.bic == null)
-        assert(withoutOptionals.message == null)
-        assert(withoutOptionals.receiverName == null)
-        assert(withoutOptionals.amount == null)
-    }
-}
\ 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]