summaryrefslogtreecommitdiff
path: root/design-documents
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents')
-rw-r--r--design-documents/001-new-browser-integration.rst4
-rw-r--r--design-documents/002-wallet-exchange-management.rst9
-rw-r--r--design-documents/003-tos-rendering.rst22
-rw-r--r--design-documents/004-wallet-withdrawal-flow.rst8
-rw-r--r--design-documents/005-wallet-backup-sync.rst8
-rw-r--r--design-documents/006-extensions.rst40
-rw-r--r--design-documents/007-payment.rst4
-rw-r--r--design-documents/008-fees.rst8
-rw-r--r--design-documents/009-backup.rst4
-rw-r--r--design-documents/010-exchange-helpers.rst4
-rw-r--r--design-documents/011-auditor-db-sync.rst4
-rw-r--r--design-documents/012-fee-schedule-metrics.rst4
-rw-r--r--design-documents/013-peer-to-peer-payments.rst6
-rw-r--r--design-documents/014-merchant-backoffice-ui.rst12
-rw-r--r--design-documents/015-merchant-backoffice-routing.rst4
-rw-r--r--design-documents/016-backoffice-order-management.rst28
-rw-r--r--design-documents/017-backoffice-inventory-management.rst12
-rw-r--r--design-documents/018-contract-json.rst4
-rw-r--r--design-documents/019-wallet-backup-merge.rst4
-rw-r--r--design-documents/020-backoffice-rewards-management.rst (renamed from design-documents/020-backoffice-tips-management.rst)36
-rw-r--r--design-documents/021-exchange-key-continuity.rst4
-rw-r--r--design-documents/022-wallet-auditor-reports.rst4
-rw-r--r--design-documents/023-taler-kyc.rst2138
-rw-r--r--design-documents/024-age-restriction.rst240
-rw-r--r--design-documents/025-withdraw-from-wallet.rst8
-rw-r--r--design-documents/026-refund-fees.rst8
-rw-r--r--design-documents/027-sandboxing-taler.rst4
-rw-r--r--design-documents/028-deposit-policies.rst207
-rw-r--r--design-documents/028-proof-of-escrow.rst128
-rw-r--r--design-documents/029-mobile-ui.rst6
-rw-r--r--design-documents/030-offline-payments.rst4
-rw-r--r--design-documents/031-invoicing.rst19
-rw-r--r--design-documents/032-brandt-vickrey-auctions.rst (renamed from design-documents/032-auctions.rst)116
-rw-r--r--design-documents/033-database.rst152
-rw-r--r--design-documents/034-wallet-db-migration.rst78
-rw-r--r--design-documents/035-regional-currencies.rst196
-rw-r--r--design-documents/036-currency-conversion-service.rst97
-rw-r--r--design-documents/037-wallet-transactions-lifecycle.rst1049
-rw-r--r--design-documents/038-demobanks-protocol-suppliers.rst158
-rw-r--r--design-documents/039-taler-browser-integration.rst195
-rw-r--r--design-documents/040-distro-packaging.rst139
-rw-r--r--design-documents/041-wallet-balance-amount-definitions.rst410
-rw-r--r--design-documents/042-synthetic-wallet-errors.rst57
-rw-r--r--design-documents/043-managing-prebuilt-artifacts.rst107
-rw-r--r--design-documents/044-ci-system.rst130
-rw-r--r--design-documents/045-kyc-inheritance.rst186
-rw-r--r--design-documents/046-mumimo-contracts.rst709
-rw-r--r--design-documents/047-stefan.rst172
-rw-r--r--design-documents/048-wallet-exchange-lifecycle.rst144
-rw-r--r--design-documents/049-auth.rst154
-rw-r--r--design-documents/050-libeufin-nexus.rst354
-rw-r--r--design-documents/051-fractional-digits.rst211
-rw-r--r--design-documents/052-libeufin-bank-2fa.rst136
-rw-r--r--design-documents/053-wallet-ui.rst1220
-rw-r--r--design-documents/054-dynamic-form.rst201
-rw-r--r--design-documents/055-wallet-problem-report.rst114
-rw-r--r--design-documents/999-template.rst11
-rw-r--r--design-documents/index.rst32
-rw-r--r--design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-01.pngbin0 -> 199631 bytes
l---------design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-latest.png1
-rw-r--r--design-documents/wallet-screenshots/ios-wallet/11-balances-list-01.pngbin0 -> 320043 bytes
l---------design-documents/wallet-screenshots/ios-wallet/11-balances-list-latest.png1
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/READE22
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/1-balance.pngbin0 -> 30910 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/10-testkudos-in-the-list.pngbin0 -> 32709 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/11-select-test-kudos.pngbin0 -> 49363 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/2-click-get-cash.pngbin0 -> 36330 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/3-click-add-exchange.pngbin0 -> 42139 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/4-input-URL.pngbin0 -> 56001 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/5-click-next.pngbin0 -> 37919 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/6-review-tos.pngbin0 -> 72501 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/7-accept-tos.pngbin0 -> 37051 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/8-confirm.pngbin0 -> 30910 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/add-exchange/9-click-get-cash.pngbin0 -> 36330 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/1-balance.pngbin0 -> 35087 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/10-balance.pngbin0 -> 47824 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/2-click-send.pngbin0 -> 47464 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/3-click-deposit.pngbin0 -> 35569 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/4-click-add-account.pngbin0 -> 54335 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/5-complete-form.pngbin0 -> 60639 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/6-click-add.pngbin0 -> 59490 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/7-confirm-deposit.pngbin0 -> 60419 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/8-show-transaction.pngbin0 -> 65622 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/deposit/9-after-deposit-confirmed.pngbin0 -> 76645 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/dev-tools.pngbin0 -> 95380 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/1-balance.pngbin0 -> 36026 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/10-confirm-transfer.pngbin0 -> 55732 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/11-show-history.pngbin0 -> 49646 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/2-click-add.pngbin0 -> 48365 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/3-input-six.pngbin0 -> 48827 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/4-click-invoice.pngbin0 -> 62019 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/5-complete-form.pngbin0 -> 64943 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/6-create-invoice.pngbin0 -> 73127 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/7-copy-qr-open-qr-page.pngbin0 -> 48070 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/8-paste-URI.pngbin0 -> 63650 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/invoice/9-click-open.pngbin0 -> 52823 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/1-load-shop.pngbin0 -> 2449960 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/10-click-article-URL.pngbin0 -> 76691 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/2-click-first-article.pngbin0 -> 83759 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/3-confirm-payment.pngbin0 -> 442385 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/4-click-refund.pngbin0 -> 101347 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/5-click-request-refund.pngbin0 -> 41768 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/6-accept-refund.pngbin0 -> 85837 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/7-click-refund-detail.pngbin0 -> 69279 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/8-show-history.pngbin0 -> 81889 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/payment/9-click-receipt.pngbin0 -> 85837 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/scan-qr-code.pngbin0 -> 36358 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/settings-developer-mode.pngbin0 -> 170233 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/settings-normal-mode.pngbin0 -> 47851 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/store-installed.pngbin0 -> 32548 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/store-payment.pngbin0 -> 28060 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/store-withdraw.pngbin0 -> 20772 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/1-initial-balance.pngbin0 -> 36311 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/10-open-URI.pngbin0 -> 51973 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/11-accept-transfer.pngbin0 -> 49787 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/12-show-history.pngbin0 -> 47985 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/2-click-send.pngbin0 -> 47235 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/3-amount-five.pngbin0 -> 47547 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/4-send-wallet.pngbin0 -> 56300 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/5-complete-form.pngbin0 -> 58617 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/6-confirm-transfer.pngbin0 -> 77092 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/7-show-history.pngbin0 -> 55329 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/8-scan-qr.pngbin0 -> 48384 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/transfer/9-paste-URI.pngbin0 -> 64510 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/10-transaction-completed.pngbin0 -> 44344 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/11-history-after-withdraw.pngbin0 -> 35260 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/2-empty-wallet.pngbin0 -> 29113 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/3-get-digital-cash.pngbin0 -> 36120 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/4-select-kudos.pngbin0 -> 47910 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/5-set-amount-five.pngbin0 -> 48389 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/6-withdraw-from-bank.pngbin0 -> 52203 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/7-review-tos.pngbin0 -> 69841 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/8-accept-tos.pngbin0 -> 50475 bytes
-rw-r--r--design-documents/wallet-screenshots/webex-wallet/withdrawal/9-confirm-withdraw.pngbin0 -> 148862 bytes
134 files changed, 9028 insertions, 519 deletions
diff --git a/design-documents/001-new-browser-integration.rst b/design-documents/001-new-browser-integration.rst
index 80a08859..fde6d3b2 100644
--- a/design-documents/001-new-browser-integration.rst
+++ b/design-documents/001-new-browser-integration.rst
@@ -1,5 +1,5 @@
-DD1: New Browser Integration
-############################
+XX 01: New Browser Integration
+##############################
.. warning::
diff --git a/design-documents/002-wallet-exchange-management.rst b/design-documents/002-wallet-exchange-management.rst
index 297c23f8..1ce37205 100644
--- a/design-documents/002-wallet-exchange-management.rst
+++ b/design-documents/002-wallet-exchange-management.rst
@@ -1,10 +1,11 @@
-DD2: Wallet Exchange Management
-###############################
+XX 02: Wallet Exchange Management
+#################################
.. note::
- This design document is currently a draft, it
- does not reflect any implementation decisions yet.
+ This design document is deprecated in favor of DD48.
+
+ Trusted exchanges and auditors are no longer something we have.
Summary
=======
diff --git a/design-documents/003-tos-rendering.rst b/design-documents/003-tos-rendering.rst
index 9b19ee02..18f9027b 100644
--- a/design-documents/003-tos-rendering.rst
+++ b/design-documents/003-tos-rendering.rst
@@ -1,5 +1,5 @@
-DD3: ToS rendering
-##################
+DD 03: ToS rendering
+####################
Summary
=======
@@ -22,6 +22,24 @@ way.
Proposed Solution
=================
+Internationalization
+--------------------
+
+The server will parse the ``Accept-Languages`` request header to determine
+which language the user will most likely want to read the terms of service
+in. If multiple languages are given, the server will check against the
+available languages and return the one with the highest preference.
+
+Additionally, the server will return an ``Avail-Languages`` header which
+details what other langauges the terms of service are available in. The
+user interface in the wallet should then allow the user to switch to one
+of these alternatives using some language switcher.
+
+
+Encoding
+--------
+
+
The service providers can output legal agreements in various formats,
determined via the ``"Accept: "`` request header. The format provider **must**
support the ``text/plain`` mime type. The format provider **must** support
diff --git a/design-documents/004-wallet-withdrawal-flow.rst b/design-documents/004-wallet-withdrawal-flow.rst
index a385959b..49e11aaf 100644
--- a/design-documents/004-wallet-withdrawal-flow.rst
+++ b/design-documents/004-wallet-withdrawal-flow.rst
@@ -1,5 +1,5 @@
-DD4: Wallet Withdrawal Flow
-###########################
+DD 04: Wallet Withdrawal Flow
+#############################
Summary
=======
@@ -104,7 +104,7 @@ For those, the wallet should not offer the option to change an exchange.
After confirming the withdrawal,
the user is brought to the list of transactions of the current currency.
-It will include a pending `TransactionWithdrawal` which might require additional user confirmation
+It will include a pending ``TransactionWithdrawal`` which might require additional user confirmation
such as a two-factor-authentication step with the bank.
1. The bank transfer happens immediately
@@ -114,7 +114,7 @@ such as a two-factor-authentication step with the bank.
3. The bank provides a ``bankConfirmationUrl`` that the user needs to visit.
If the withdrawal proceeds very quickly,
-the `TransactionWithdrawal` might not show as pending
+the ``TransactionWithdrawal`` might not show as pending
and its effective amount is added to the displayed balance right away.
Alternatives
diff --git a/design-documents/005-wallet-backup-sync.rst b/design-documents/005-wallet-backup-sync.rst
index 7592242c..1e74914c 100644
--- a/design-documents/005-wallet-backup-sync.rst
+++ b/design-documents/005-wallet-backup-sync.rst
@@ -1,5 +1,5 @@
-DD5: Wallet Backup and Sync
-###########################
+XX 05: Wallet Backup and Sync
+#############################
.. warning::
@@ -44,7 +44,7 @@ The managed entities are:
* set of reserves together with reserve history
* set of accepted bank withdrawal operations
* set of coins together with coin history and blinding secret (both for normal withdrawal and refresh)
- and coin source info (refresh operation, tip, reserve)
+ and coin source info (refresh operation, reward, reserve)
* set of purchases (contract terms, applied refunds, ...)
* assignment of coins to their "primary wallet"
@@ -59,7 +59,7 @@ Entities that **could** be synchronized (to be decided):
* private keys of other sync accounts
* coin planchets
-* tips before the corresponding coins have been withdrawn
+* rewards before the corresponding coins have been withdrawn
* refresh sessions (not only the "meta data" about the operation,
but everything)
diff --git a/design-documents/006-extensions.rst b/design-documents/006-extensions.rst
index 576809ad..25afe3bc 100644
--- a/design-documents/006-extensions.rst
+++ b/design-documents/006-extensions.rst
@@ -1,5 +1,5 @@
-DD6: Extensions for GNU Taler
-#############################
+DD 06: Extensions for GNU Taler
+###############################
Summary
=======
@@ -18,6 +18,7 @@ of 2021 and 2022:
* Peer-to-peer payments
* Anonymous age-restriction
* Escrow service for anonymous auctions
+* A general escrow service
We call a feature an *extension* when it is *optional* for either the
exchange, wallet or merchant to enable and support it. (However, enabling
@@ -47,17 +48,17 @@ The exchange will add two new *optional* fields in response to ``/keys``:
SHA256-hash of the normalized JSON-string of the ``extensions`` object.
-The necessary changes to ``ExchangeKeysResponse`` are highlighted here:
+The necessary changes to ``ExtensionsManifestsResponse`` are highlighted here:
-.. ts:def:: ExchangeKeysResponse
+.. ts:def:: ExtensionsManifestsResponse
- interface ExchangeKeysResponse {
+ interface ExtensionsManifestsResponse {
//...
// Optional field with a dictionary of (name, object) pairs defining the
// supported and enabled extensions.
// The name MUST be non-empty and unique.
- extensions?: { name: Extension };
+ extensions?: { name: ExtensionManifest };
// Signature by the exchange master key of the SHA-256 hash of the
// normalized JSON-object of field ``extensions``, if it was set.
@@ -80,11 +81,11 @@ GANA_ along with a full description of the extension.
versions of the "same" feature in parallel, multiple unique names MUST be used,
f.e. ``age_restriction`` an ``age_restriction.v2``.)
-Extension object
-----------------
+ExtensionManifest object
+---------------------------
-The definition of ``Extension`` object itself is mostly up to the particular
-feature. **However**, it MUST have
+The definition of ``ExtensionManifest`` object itself is mostly up to the
+particular feature. **However**, it MUST have
#. the boolean field ``critical`` that has the same semantics as as "critical"
has for extensions in X.509_: if true, the client must "understand" the
@@ -98,10 +99,9 @@ feature. **However**, it MUST have
.. _`protocol version ranges notation`: https://docs.taler.net/core/api-common.html#protocol-version-ranges
+.. ts:def:: ExtensionManifest
-.. ts:def:: Extension
-
- interface Extension {
+ interface ExtensionManifest {
// The criticality of the extension MUST be provided. It has the same
// semantics as "critical" has for extensions in X.509:
// - if "true", the client must "understand" the extension before
@@ -180,7 +180,9 @@ this:
AGE_GROUPS = "8:10:12:14:16:18:21"
-* TODO: Add examples for p2p.
+ [exchange-extension-policy_brandt_vickery_auction]
+ ENABLED = true
+ REPLAY_PROGRAM = "/usr/local/bin/taler-exchange-auction_replay"
Merchant
@@ -190,6 +192,16 @@ TODO:
* Needs to express support for particular extensions, too. F.e. age-restriction.
+Extension Plugins
+==================
+
+TODO:
+
+* describe ``struct TALER_Extension``
+* describe the plugin loading mechanism for extensions
+* describe the various handlers
+
+
Alternatives
============
diff --git a/design-documents/007-payment.rst b/design-documents/007-payment.rst
index 2750c816..f9b0639a 100644
--- a/design-documents/007-payment.rst
+++ b/design-documents/007-payment.rst
@@ -1,5 +1,5 @@
-DD7: Specification of the Payment Flow
-######################################
+DD 07: Specification of the Payment Flow
+########################################
Summary
=======
diff --git a/design-documents/008-fees.rst b/design-documents/008-fees.rst
index b99190c4..cb0ae7dc 100644
--- a/design-documents/008-fees.rst
+++ b/design-documents/008-fees.rst
@@ -1,10 +1,10 @@
-DD8: Fee Structure Metrics
-##########################
+XX 08: Fee Structure Metrics
+############################
.. note::
- This design document is currently a draft, it
- does not reflect any implementation decisions yet.
+ This design document is deprecated.
+
Summary
=======
diff --git a/design-documents/009-backup.rst b/design-documents/009-backup.rst
index 1cf9bb23..18571198 100644
--- a/design-documents/009-backup.rst
+++ b/design-documents/009-backup.rst
@@ -1,5 +1,5 @@
-DD9: Wallet Backup
-##################
+DD 09: Wallet Backup
+####################
Summary
=======
diff --git a/design-documents/010-exchange-helpers.rst b/design-documents/010-exchange-helpers.rst
index fe9de063..c63921b6 100644
--- a/design-documents/010-exchange-helpers.rst
+++ b/design-documents/010-exchange-helpers.rst
@@ -1,5 +1,5 @@
-DD10: Exchange crypto helper design
-###################################
+DD 10: Exchange crypto helper design
+####################################
Summary
=======
diff --git a/design-documents/011-auditor-db-sync.rst b/design-documents/011-auditor-db-sync.rst
index 8a460ed8..71d0762a 100644
--- a/design-documents/011-auditor-db-sync.rst
+++ b/design-documents/011-auditor-db-sync.rst
@@ -1,5 +1,5 @@
-DD11: Auditor-Exchange Database Synchronization
-###############################################
+DD 11: Auditor-Exchange Database Synchronization
+################################################
Summary
=======
diff --git a/design-documents/012-fee-schedule-metrics.rst b/design-documents/012-fee-schedule-metrics.rst
index 3ab7f565..fb7b295a 100644
--- a/design-documents/012-fee-schedule-metrics.rst
+++ b/design-documents/012-fee-schedule-metrics.rst
@@ -1,5 +1,5 @@
-DD12: Exchange Fee Configuration
-################################
+DD 12: Exchange Fee Configuration
+#################################
.. note::
diff --git a/design-documents/013-peer-to-peer-payments.rst b/design-documents/013-peer-to-peer-payments.rst
index 951f7f81..20124532 100644
--- a/design-documents/013-peer-to-peer-payments.rst
+++ b/design-documents/013-peer-to-peer-payments.rst
@@ -1,5 +1,5 @@
-DD13: Wallet-to-Wallet Payments
-###############################
+DD 13: Wallet-to-Wallet Payments
+################################
Summary
=======
@@ -424,7 +424,7 @@ Payment requests
1. The payee creates a **purse** by computing a public-private key pair.
2. The payee POSTs to the ``/purse/$PURSE_PUB/merge`` endpoint to
- both upload the encrypted contract, associate it with the payer's
+ both upload the encrypted contract, associate it with the payee's
account and signal its agreement to the contract. The
**merge** request must be signed by the purse's private key.
A second signature must be provided by the account private key,
diff --git a/design-documents/014-merchant-backoffice-ui.rst b/design-documents/014-merchant-backoffice-ui.rst
index 63c326cc..1f744a15 100644
--- a/design-documents/014-merchant-backoffice-ui.rst
+++ b/design-documents/014-merchant-backoffice-ui.rst
@@ -1,5 +1,5 @@
-DD14: Merchant backoffice UI
-############################
+DD 14: Merchant backoffice UI
+#############################
Motivation
@@ -139,11 +139,3 @@ Story #4: Manage inventory
- change product description / price / etc.
- delete products from inventory
-
-
-Story #5: Manage tipping
-------------------------
-
-- set up tipping reserve
-
-- check tipping reserve status
diff --git a/design-documents/015-merchant-backoffice-routing.rst b/design-documents/015-merchant-backoffice-routing.rst
index e5110bee..71a0e554 100644
--- a/design-documents/015-merchant-backoffice-routing.rst
+++ b/design-documents/015-merchant-backoffice-routing.rst
@@ -1,5 +1,5 @@
-DD15: Merchant backoffice Routing
-#################################
+DD 15: Merchant backoffice Routing
+##################################
Motivation
diff --git a/design-documents/016-backoffice-order-management.rst b/design-documents/016-backoffice-order-management.rst
index e2fca0fe..09af5eb5 100644
--- a/design-documents/016-backoffice-order-management.rst
+++ b/design-documents/016-backoffice-order-management.rst
@@ -1,5 +1,5 @@
-DD16: Backoffice Order Management
-#################################
+DD 16: Backoffice Order Management
+##################################
Summary
=======
@@ -32,7 +32,7 @@ Proposed Solution
Listing orders
--------------
-.. image:: ../backoffice-order-list.svg
+.. image:: ../images/backoffice-order-list.svg
:width: 800
4 tabs will be show for an easy access to common filter, click on any of this and
@@ -101,7 +101,7 @@ In both cases, the total unit and price of the products will be calculated and
shown in the bottom of the section. If the merchant collapse one of the product
list a line with a resume of the total price and units will be shown.
-.. image:: ../backoffice-order-create.product-section.svg
+.. image:: ../images/backoffice-order-create.product-section.svg
:width: 800
Create order: Price section
@@ -117,7 +117,7 @@ of all products prices will be shown. The ``order price`` will default to
``total products price``. The ``products taxes`` and ``profit`` will be shown
since ``order price`` cannot be less that ``product taxes``.
-.. image:: ../backoffice-order-create.price-section.svg
+.. image:: ../images/backoffice-order-create.price-section.svg
:width: 800
Create order: Payment section
@@ -142,7 +142,7 @@ This section show optional values that can be overwritten by the merchant
* ``wire_fee_amortization``: default value from the instance
-.. image:: ../backoffice-order-create.payment-section.svg
+.. image:: ../images/backoffice-order-create.payment-section.svg
:width: 800
Create order: all section expanded
@@ -150,7 +150,7 @@ Create order: all section expanded
An example of how all section in a page will be shown.
-.. image:: ../backoffice-order-create.all-expanded.svg
+.. image:: ../images/backoffice-order-create.all-expanded.svg
:width: 800
@@ -291,10 +291,10 @@ Ask for:
* drop down options: duplicated, requested by customer, other
* after selecting, free text for additional information
-.. image:: ../backoffice-order-refund.svg
+.. image:: ../images/backoffice-order-refund.svg
:width: 800
-.. image:: ../backoffice-order-refund.already.svg
+.. image:: ../images/backoffice-order-refund.already.svg
:width: 800
Example of details by status
@@ -302,16 +302,16 @@ Example of details by status
-.. image:: ../backoffice-order-details.paid.svg
+.. image:: ../images/backoffice-order-details.paid.svg
:width: 800
-.. image:: ../backoffice-order-details.unpaid.svg
+.. image:: ../images/backoffice-order-details.unpaid.svg
:width: 800
-.. image:: ../backoffice-order-details.claimed.svg
+.. image:: ../images/backoffice-order-details.claimed.svg
:width: 800
-.. image:: ../backoffice-order-details.refunded.svg
+.. image:: ../images/backoffice-order-details.refunded.svg
:width: 800
@@ -323,7 +323,7 @@ pagination
----------
order list was originally thought with pagination footer
-.. image:: ../backoffice-order-list.pagination.svg
+.. image:: ../images/backoffice-order-list.pagination.svg
:width: 800
ascending boolean flag cloud be eliminated using the load before and load after
diff --git a/design-documents/017-backoffice-inventory-management.rst b/design-documents/017-backoffice-inventory-management.rst
index 30a6d835..9565d53e 100644
--- a/design-documents/017-backoffice-inventory-management.rst
+++ b/design-documents/017-backoffice-inventory-management.rst
@@ -1,5 +1,5 @@
-DD17: Backoffice Inventory Management
-#####################################
+DD 17: Backoffice Inventory Management
+######################################
Summary
=======
@@ -31,7 +31,7 @@ Proposed Solution
Inspecting inventory
--------------------
-.. image:: ../backoffice-product-list.svg
+.. image:: ../images/backoffice-product-list.svg
:width: 800
Listing the product will shown this columns:
@@ -53,7 +53,7 @@ Actions will be
Create and Update Product form
------------------------------
-.. image:: ../backoffice-product-create.svg
+.. image:: ../images/backoffice-product-create.svg
:width: 800
Update product will use the same form except for the ``product_id``
@@ -111,7 +111,7 @@ Stock management
* a label at the end of the section will inform about the final result
-.. image:: ../backoffice-product-create.stock.svg
+.. image:: ../images/backoffice-product-create.stock.svg
:width: 800
@@ -124,7 +124,7 @@ Alternatives
* rows in the table can be expandable when clicked to get access to some common
actions like increase stock or change price
-.. image:: ../backoffice-product-list.actions.svg
+.. image:: ../images/backoffice-product-list.actions.svg
:width: 800
* detail page was intentionally left out since all information can be access
diff --git a/design-documents/018-contract-json.rst b/design-documents/018-contract-json.rst
index f930bcbd..051ed2f1 100644
--- a/design-documents/018-contract-json.rst
+++ b/design-documents/018-contract-json.rst
@@ -1,5 +1,5 @@
-DD18: Forgettable Data in JSON Contract Terms
-#############################################
+DD 18: Forgettable Data in JSON Contract Terms
+##############################################
Summary
=======
diff --git a/design-documents/019-wallet-backup-merge.rst b/design-documents/019-wallet-backup-merge.rst
index f716c7ba..9823f69d 100644
--- a/design-documents/019-wallet-backup-merge.rst
+++ b/design-documents/019-wallet-backup-merge.rst
@@ -1,5 +1,5 @@
-DD19: Wallet Backup Merging
-###########################
+DD 19: Wallet Backup Merging
+############################
Summary
=======
diff --git a/design-documents/020-backoffice-tips-management.rst b/design-documents/020-backoffice-rewards-management.rst
index 6049109e..8345a3b9 100644
--- a/design-documents/020-backoffice-tips-management.rst
+++ b/design-documents/020-backoffice-rewards-management.rst
@@ -1,10 +1,10 @@
-DD20: Backoffice Tips Management
-################################
+XX 20: Backoffice Rewards Management
+####################################
Summary
=======
-This document describe the complete list features for tips and reserve
+This document describe the complete list features for rewards and reserve
management and how will be shown.
Motivation
@@ -19,9 +19,9 @@ User should use the backoffice to:
* creating new reserves
* listing active reserves
-* authorize tips for a reserve
-* list all tips for an active reserve
-* check tips status
+* authorize rewards for a reserve
+* list all rewards for an active reserve
+* check rewards status
Proposed Solution
=================
@@ -29,7 +29,7 @@ Proposed Solution
Listing reserves
----------------
-.. image:: ../backoffice-reserve-list.svg
+.. image:: ../images/backoffice-reserve-list.svg
:width: 400
@@ -47,14 +47,14 @@ columns:
* initial: if the exchange and merchant-backend disagree in the initial balance
(failure) the cell will be red and have a tooltip with more information
-* actions: delete button will be disabled on failure or committed > 0, new_tip
+* actions: delete button will be disabled on failure or committed > 0, new_reward
button will be disabled on picked_up == initial or failure
Create new reserve
------------------
-.. image:: ../backoffice-reserve-create.svg
+.. image:: ../images/backoffice-reserve-create.svg
:width: 400
fields:
@@ -67,29 +67,29 @@ fields:
If there is an error in the creation a Notification message will be shown
-Authorize Tip
--------------
+Authorize Reward
+----------------
-The merchant can authorize tips clicking in the plus (+) button that will bring
+The merchant can authorize rewards clicking in the plus (+) button that will bring
the next popup
-.. image:: ../backoffice-tip-create.svg
+.. image:: ../images/backoffice-reward-create.svg
:width: 400
after confirm it will continue with a success page:
-.. image:: ../backoffice-tip-create.confirmation.svg
+.. image:: ../images/backoffice-reward-create.confirmation.svg
:width: 400
Details of reserve
-----------------------------
+------------------
-.. image:: ../backoffice-reserve-details.svg
+.. image:: ../images/backoffice-reserve-details.svg
:width: 400
-Tips sorted from newer to older
+Rewards sorted from newer to older
When the reserve has not yet funded
-.. image:: ../backoffice-reserve-details.unfunded.svg
+.. image:: ../images/backoffice-reserve-details.unfunded.svg
:width: 400
diff --git a/design-documents/021-exchange-key-continuity.rst b/design-documents/021-exchange-key-continuity.rst
index 877ffdf8..769cff72 100644
--- a/design-documents/021-exchange-key-continuity.rst
+++ b/design-documents/021-exchange-key-continuity.rst
@@ -1,5 +1,5 @@
-DD21: Exchange Key Continuity
-#############################
+DD 21: Exchange Key Continuity
+##############################
Summary
=======
diff --git a/design-documents/022-wallet-auditor-reports.rst b/design-documents/022-wallet-auditor-reports.rst
index 0df5e427..1cbb2a21 100644
--- a/design-documents/022-wallet-auditor-reports.rst
+++ b/design-documents/022-wallet-auditor-reports.rst
@@ -1,5 +1,5 @@
-DD22: Wallet Proofs to Auditor
-##############################
+DD 22: Wallet Proofs to Auditor
+###############################
.. note::
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst
index bfc6e514..c20844b3 100644
--- a/design-documents/023-taler-kyc.rst
+++ b/design-documents/023-taler-kyc.rst
@@ -1,25 +1,24 @@
-DD23: Taler KYC
-###############
+DD 23: Taler KYC
+################
Summary
=======
-This document discusses the Know-your-customer (KYC) processes supported by Taler.
+This document discusses the Know-your-customer (KYC) and Anti-Money Laundering
+(AML) processes supported by Taler.
Motivation
==========
-To legally operate, Taler has to comply with KYC regulation that requires
+To legally operate, Taler has to comply with KYC/AML regulation that requires
banks to identify parties involved in transactions at certain points.
Requirements
============
-The solution should support fees to be paid by the user for the KYC process (#7365).
-
-Taler needs to run KYC checks in the following circumstances:
+Taler needs to take *measures* based on the following primary *triggers*:
* Customer withdraws money over a monthly threshold
@@ -40,99 +39,1229 @@ Taler needs to run KYC checks in the following circumstances:
* key: IBAN (encoded as payto:// URI)
-* Reserve is "opened" for invoicing or tipping.
+* Reserve is "opened" for invoicing.
* key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
+* Import of new sanctions lists and triggering of measures against matches of existing
+ customer records against the list
+
+For the different operation types, there can be both soft and hard
+limits. Soft limits are those that the customer may raise by providing data
+and passing KYC checks. Hard limits cannot be lifted, for example because an
+exchange forbids crossing those limits in its terms of service for all
+customers.
+
+
+Process requirements
+^^^^^^^^^^^^^^^^^^^^
+
+The key consideration here is *plausibilization*: staff needs to
+check that the client-provided information is plausible. As this
+is highly case-dependent, this cannot be automated.
+
+For the different *measures*, there are various different possible KYC/AML
+*checks* that could happen:
+
+* In-person validation by AML staff
+* Various forms to be filled by AML staff
+* Validation involving local authorities and post-office
+* Online validation, sometimes with multiple options (like KYC for multiple people):
+
+ * Forms to be supplied by user (different types of ID)
+ * Interactive video
+ * Documents to be supplied (business register)
+ * Address validation (e-mail or phone or postal)
+
+Additionally, the process is dynamic and conditional upon various decisions:
+
+* Individual vs. business
+* PEP or non-PEP
+* Hit on sanctions list
+* Type of business (trust, foundation, listed on stock market, etc.)
+* Need for plausibilization (via documents by user or staff research)
+* Periodic updates (of customer data, of sanction lists) and re-assessment
+
+There are also various *outcomes*:
+
+* normal operation (with expiration date)
+* normal operation but with AML staff investigating (new measure)
+* held, requesting customer documentation (new measure)
+* held, AML staff reviewing evidence for plausibilization (new measure)
+* automatically frozen until certain day (due to sanctions)
+* institutionally frozen until certain day (due to order by state authority)
+* operation is categorically not allowed (at least above certain limits)
+
+Outcomes may also be (partially) public, that is exposed to the client. For
+example, we may want to tell a wallet that it has hit a hard withdraw limit,
+but might succeed at withdrawing a smaller amount.
+
+The outcome of a *check* can set new rules or trigger another *measure* (the
+latter is conditional on reaching the expiration time of the outcome).
+
+As a result, we largely end up in a large state machine where the AML staff has
+serious flexibiltiy while the user needs guidance as to the possible next moves
+and/or to the current state of their account (where some information must not be
+disclosed).
+
+
+Documentation requirements
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For each account we must:
+
+* define risk-profile (902.4, 905.1)
+* document the specific setup, likely not just the INI file
+* should have some key Anti-Money-Laundering Act (AMLA)
+ file attributes, such as:
+
+ * File opened, file closed (keep data for X years afterwards!)
+ * low-risk or high-risk business relationship
+ * PEP status
+ * business domain
+ * authority notification dates (possibly multiple) with
+ voluntary or mandatory notification classification
+
+Finally, we need to produce statistics:
+
+* There must be a page with an overview of AMLA files with opening
+ and closing dates and an easy way to determine for any day the
+ number of open AMLA files
+* Technically, we also need a list of at-risk transactions and of
+ frozen transactions, but given that we can really only freeze
+ on an account-basis, I think there is nothing to do here
+* number of incidents reported (voluntarily, required)
+* number of business relationships at any point in time
+* number of risky business relationships (PEP, etc.)
+* number of frozen transactions (authority vs. sanction) with start-date and end-date
+* start-data and end-date of relationships (data retained for X years after end of relationship)
+
+For this high-level monitoring, we need certain designated critical events to
+be tracked in the system statistics:
+
+* account opened
+* set to high risk
+* set to low risk
+* suspicious activity report filed with authority
+* account frozen
+* account unfrozen
+* account closed
+* sanction list import / update
+
+
+TODO: Sanction lists
+^^^^^^^^^^^^^^^^^^^^
+
+We need to be able to import new sanction lists (whenever they are published)
+and then check existing AMLA files against those lists. Additionally, newly
+created AMLA files must be checked against the current list and some "measure"
+applied in case of a match.
+
+This will primarily require us to define an endpoint to upload a sanction list
+and to define a new table to track the list of sanctioned entities. As it is
+expected that sanction lists will not permit fully automated determinations in
+all cases, an external "sanction check" program should be configured which
+compares records against the current list and determines the correct measure,
+such as no change, further manual review by AML staff, or even automatic
+freeze (and report) depending on how well the records match.
+
+Basically, the "sanction check" program takes the sanction list and an
+attribute set to compute the same kind of `AmlOutcome` that an AML program
+outputs given a context and an attribute set.
+
+
+Security requirements
+^^^^^^^^^^^^^^^^^^^^^
+
+IBANs are predictable. We (probably) do not want random people to be able to
+initate KYC processes for other parties. Similarly, the attestation API
+requires us to somehow *authenticate* the user to ensure we only give out
+attestation data to the data subject themselves. For P2P payments and
+withdrawals, we have the reserve public key that is only known to the data
+subject and thus can be used to authenticate the client via a signature. Only
+pure deposits (by merchants or directly from a wallet) are a problem as the
+only thing we know about the receiver is the IBAN at that time, and literally
+any user could just deposit money into some bank account, so knowledge of the
+IBAN is insufficient to determine that we actually are communicating with the
+owner of the bank account.
+
+
+Further considerations
+^^^^^^^^^^^^^^^^^^^^^^
+
+On top of all of this, we need to plan some *diagnostics* to determine when
+components fail (such as scripts or external services providing malformed
+results).
+
+Optionally, in the future, the solution should support fees to be paid by the
+user for *voluntary* KYC processes related to attestation (#7365).
Proposed Solution
=================
+The main state of an account is represented by a set of `KycRules` (the
+`LegitimizationRuleSet`) which specify the current *rules* to apply to
+transactions involving the account. Rules can *exposed* to the account owner,
+or can be secret. Each *rule* specifies certain *conditions* which, if met,
+*trigger* a single specific *measure*. After a *rule* was *triggered* and
+before the *outcome* of the respective *measure* has been produced (say
+because the user did not yet enter their data or the AML officer is still
+reviewing the case), the existing rules remain in force. Rules have a display
+priority, and if a second rule with a higher display priority is also
+triggered, the *measures* of the higher-priority rule become the active
+*measures*. Except for the default rule set, every legitimization rule set
+also has an *expiration* time after which a successor *measure* (or the
+default rule set) is automatically triggered.
+
+For any possible *measures*, we define:
+
+* Contextual input data to be provided (with dynamic inputs,
+ e.g. amount set dynamically based on the *trigger* could be
+ in the context)
+* A *check* to be performed (checks can be user-interactive (LINK, FORM)
+ or staff-interactive (INFO))
+* A fallback *measure* to take on failure of a user-interactive check
+ (if the check fails, we cannot run the AML *program* as required inputs
+ might be missing!)
+* An (AML) *program* that uses *attribtes* from the *check* as well as
+ *context* data to determine an *outcome* represented as the
+ `AmlOutcome`.
+
+"verboten" is the name of a special *measure*, which means that crossing the
+respective transaction threshold is categorically not allowed (for this
+account). "verboten" with a threshold of zero can be used to freeze funds.
+
+Possible *outcomes* of a measure include:
+
+* The next operational state (normal, AML investigation) of the account
+ (basically, whether to add it to the work list of AML staff).
+* A new set of *rules* in the form of a `LegitimizationRuleSet` that
+ determines custom rules to apply to transactions involving the account;
+ such rules may be used to block certain transactions by using the
+ "verboten" measure. The `LegitimizationRuleSet` also must specify
+ an *expiration* time by which we fall back to a successor measure
+ *or* to the default rules.
+* A (largely) free-form set of `AccountProperties` that AML staff can
+ use to tag accounts with. Some default properties are defined, but
+ the exchange does not do anything with these and AML SPAs are free to
+ use any properties they like. Account properties are only exposed
+ to AML staff and never to the customer.
+* A set of *events* that are to be added to the timeline of the
+ operator for statistical purposes.
+
+For the user-interactive *checks* we need a KYC SPA that is given:
+
+* instructions to render (with either a form to fill or links to external checks);
+ here the context could provide an array of choices!
+* possibly an external check that was set up (if any); for cost-reasons, we
+ should only do one at a time, and probably should then always redirect the
+ browser to that check.
+
+For the staff-interactive *checks* we need an AML SPA:
+
+* to file forms and upload documentation (without state transition)
+* to decide on next measure (providing context); here, the exchange needs
+ to expose the list of available *measures* and required *context* for each
+
+We need some customer-driven interactivity in KYB/KYC process, for example the
+user may need to be given choices (address vs. phone, individual vs. business,
+order in which to provide KYC data of beneficiaries). As a result, the
+exchange needs to serve some SPA for *measures* where the user is shown the
+next step(s) or choices (which person to collect KYC data on, whether to run
+challenger on phone number of physical address, etc.). The SPA should also
+potentially contain a form to allow the customer to directly upload documents
+to us (like business registration) instead of to some KYC provider. This is
+because KYC providers may not be flexible enough. The SPA should also allow
+the customer to perform KYC checks voluntarily.
+
+Similarly, the AML staff will need to be able to trigger rather complex
+KYB/KYC processes, like "need KYC on X and Y and Z" or "phone number or
+mailing address" or "please upload form A/T/S". Here in particular it
+should be possible to request not only filled forms, but arbitrary
+documents.
+
+
Terminology
^^^^^^^^^^^
-* **Check**: A check establishes a particular attribute of a user, such as their name based on an ID document and lifeness, mailing address, phone number, taxpayer identity, etc.
+* **Attributes**: Attributes are used to represent KYC data obtained about
+ an account holder. Attributes include passport images, address data,
+ business registration documents, and indeed arbitrary forms filed by
+ AML staff or the customer themselves. Attribute data is considered
+ sensitive private information and is thus stored encrypted within the
+ exchange database.
+
+* **Check**: A check establishes a particular attribute of a user, such as
+ their name based on an ID document and lifeness, mailing address, phone
+ number, taxpayer identity, etc. Checks may be given *context* (such as
+ whether a customer is an individual or a business) to run correctly. Checks
+ can also be AML staff inserting information for plausibilization. Checks
+ result in *attributes* about the account's owner which are given to an
+ external AML *program* together with the *context* to determine an *outcome*.
+ KYC checks are always specified with a fallback *measure* to be taken if
+ the check fails.
+
+* **Condition**: A condition specifies when KYC is required. Conditions
+ include the *type of operation*, a threshold amount (e.g. above EUR:1000)
+ and possibly a time period (e.g. over the last month).
+
+* **Configuration**: The configuration determines the *legitimization rules*,
+ and specifies which providers offer which *checks*.
+
+* **Context**: Context is information provided as input into a *check* and
+ *program* to customize their execution. The context is initially set by the
+ *measure* (possibly including data from the *trigger*). Naturally, the
+ *program* may use its `AmlProgramInput` which includes *context* and
+ *attribute* data to compute an update *context* for the next set of
+ *measures* that it specifies in the `LegitimizationRuleSet` as part
+ of the `AmlOutcome`. Thus, *context* is something that typically
+ evolves as the *account* undergoes *measures*. Context is lost if
+ an account transitions to default *legitimization rules* due to
+ *expiration*.
+
+* **Display priority**: Every rule has a *display priority*. If a second
+ *rule* is *triggered* before the *outcome* of a *rule* could be determined,
+ the *rule* with the larger *display priority* becomes the requirement that
+ the account owner has to satisfy (and that thus will be displayed by the
+ KYC SPA).
+
+* **Expiration**: Except for the default rules, any set of KYC rules is
+ subject to *expiration*. This can be because *attributes* become outdated or
+ because sanctions have a time limit. The expiration time thus determines
+ when a new *measure* is triggered in the absence of a transaction crossing
+ thresholds in the current set of *legtimization rules*.
+
+* **Legitimization rules**: The *legitimization rules* determine under which
+ *conditions* which *measures* will be taken. A `LegitimizationRuleSet`
+ always also includes an *expiration* time period for (custom, non-default)
+ *legitimization rules* after which a fallback measure* will automatically
+ apply. Legitimization rules may be *exposed* to the client (for example,
+ to allow a wallet to stay below hard withdraw thresholds) or could be secret.
+
+* **Logic**: Logic refers to a specific bit of code (realized as an exchange
+ plugin) that enables the interaction with a specific *provider*. Logic
+ typically requires *configuration* for access control (such as an
+ authorization token) and possibly the endpoint of the specific *provider*
+ implementing the respective API.
+
+* **Measure**: Describes the possible outgoing edges from one state in the
+ state machine (including how to show the current state). Each edge is given
+ some *context* and a *check* to be performed as well as an AML *program*
+ which determines the *outcome*. We generally distinguish between
+ "original" measures (defined globally in the exchange configuration) and
+ "custom" measures (defined specifically for an account by AML staff).
+
+* **Outcome**: An `AmlOutcome` describes the account state that an account
+ ends up in due to either an AML staff action or an AML *program* doing some
+ computation over the attributes resulting from a *check*. Outcomes can be
+ that certain types of transactions are "verboten", that the account is (or
+ remains) under investigation by AML staff, that the account is given certain
+ properties, and/or that certain events are to be logged. Outcomes also
+ include a new set of *legitimization rules* to apply (and an *expiration*
+ time at which point a successor *measure* will be automatically taken).
+
+* **Provider**: A provider performs a specific set of *checks* at a certain
+ *cost*. Interaction with a provider is performed by provider-specific
+ *logic*.
+
+* **Program**: An AML helper *program* is given *context* about the current
+ state of an account and the attribute data from a *check* to compute the
+ *outcome*. For example, a *program* may look at the "PEP" field of a KYC
+ check and decide if the outcome is to put the account into ``normal`` or
+ ``held-for-manual-review`` state. AML programs are always specified
+ with a fallback *measure* to be taken if the program fails.
+
+* **Trigger**: A specific transaction that satisfies a **Condition**.
+
+* **Type of operation**: The operation type determines which Taler-specific
+ operation has triggered the KYC requirement. We support four types of
+ operation: withdraw (by customer), deposit (by merchant), P2P receive (by
+ wallet) and (high) wallet balance.
+
+
+Account owner authentication
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Access to the KYC SPA (or rather, its account-specific state) is controlled by
+a *target token* (which is effectively like a bearer token, except passed
+inside the URL). The *target token* ensures that only the account owner has
+access to the KYC processes. It can be obtained by authenticating using
+either the merchant private key or reserve private key, depending on the type
+of the account (IBAN or wallet-reserve respectively).
+
+When we need to authenticate a bank account owner, we will simply require them
+to make an outgoing wire transfer into the exchange bank account with a public
+key in the wire transfer subject (just like when withdrawing), but augmented
+with the string "KYC" so we can distinguish the wire transfer from a regular
+withdrawal. Typically, we would put the merchant public key into the wire
+transfer subject; wallets MAY put their long-term reserve public key instead.
+The amount to be transferred is the *KYC fee*.
+
+This has several advantages:
+
+* Only the account owner can provide us with the public key, so we already
+ have also one super-hard piece of KYC evidence.
+* If the account owner looses their public key, it's not a problem: they
+ would just have to do the transfer again with a new key. No need for
+ us to do any kind of intervention for key management.
+* We could theoretically get paid to do the KYC process, or just "charge" a
+ nominal amount.
+* This also somewhat addresses the payment for voluntary KYC processes where
+ a merchant wants to do KYC to get us to attest their identity for their
+ customers even if we do not yet have a legal need. The only issue here
+ is that this does not work if voluntary KYC is invoiced while mandatory
+ KYC is gratis. But, that kind of configuration is a business decision
+ and there is no hard need to support it immediately.
+* This definitively addresses the need for authentication to access the
+ attestation API, which so far was only available for P2P payments as
+ we could not authenticate merchants.
+* The "KYC" string allows us to distinguish the authentication transfers from
+ withdrawal transfers; by keeping the KYC fee at or below the closing fee,
+ we can even deploy this without fully updating the logic everywhere to
+ distinguish KYC transfers
+
+TODO: update wire gateway specification, update/new tables for KYC wire
+transfers, update API spec for attestation, update exchange API (below) to
+signal need for auth-payment via wire transfer, update merchant logic to
+expose merchant public key to SPA for wire transfer if needed for KYC.
+
+
+451 Response
+^^^^^^^^^^^^
+
+When KYC operations are required, various endpoints may respond with a
+``451 Unavailable for Legal Reasons`` status code and a `KycNeededRedirect`
+body.
+
+ .. ts:def:: KycNeededRedirect
+
+ interface KycNeededRedirect {
+
+ // Numeric `error code <error-codes>` unique to the condition.
+ // Should always be ``TALER_EC_EXCHANGE_GENERIC_KYC_REQUIRED``.
+ code: number;
+
+ // Human-readable description of the error, i.e. "missing parameter",
+ // "commitment violation", ... Should give a human-readable hint
+ // about the error's nature. Optional, may change without notice!
+ hint?: string;
+
+ // Hash of the payto:// account URI for which KYC
+ // is required.
+ h_payto: PaytoHash;
+
+ // Public key associated with the account. The client must sign
+ // the initial request for the KYC status using the corresponding
+ // private key. Will be either a reserve public key or a merchant
+ // (instance) public key.
+ //
+ // Absent if no public key is currently associated
+ // with the account and the client MUST thus first
+ // credit the exchange via an inbound wire transfer
+ // to associate a public key with the debited account.
+ account_pub?: EddsaPublicKey;
+
+ // Identifies a set of measures that were triggered and that are
+ // now preventing this operation from proceeding. Gives the
+ // account holder a starting point for understanding why the
+ // transaction was blocked and how to lift it. The account holder
+ // should use the number to check for the account's AML/KYC status
+ // using the ``/kyc-check/$REQUIREMENT_ROW`` endpoint.
+ requirement_row: Integer;
+
+ }
+
+
+New endpoints
+^^^^^^^^^^^^^
-* **Condition**: A condition specifies when KYC is required. Conditions include the *type of operation*, a threshold amount (e.g. above EUR:1000) and possibly a time period (e.g. over the last month).
+.. http:get:: /kyc-check/$REQUIREMENT_ROW
+
+ Checks the KYC status of a particular payment target and possibly begins a
+ KYC process by allowing the customer to choose the next KYC measure to
+ satisfy. This endpoint is typically used by wallets or merchants that
+ have been told that a transaction is not happening because it triggered
+ some KYC/AML measure and now want to check how the KYC/AML
+ requirement could be fulfilled (or whether it already has been
+ statisfied and the operation can now proceed). Long-polling may be used
+ to instantly observe a change in the KYC requirement status.
+
+ The requirement row of the ``/kyc-check/`` endpoint encodes the
+ legitimization measure's serial number. It is returned in
+ `KycNeededRedirect` responses via the ``requirement_row`` field.
+
+ Given a valid pair of requirement row and account owner signature, the
+ ``/kyc-check/`` endpoint returns either just the KYC status or redirects the
+ client (202) to the next required stage of the KYC process. The redirection
+ must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. It
+ must always be the same endpoint for the same client, as the wallet/merchant
+ backend are not required to check for changes to this endpoint. Clients
+ that received a 202 status code may repeat the request and use long-polling
+ to detect a change of the HTTP status.
+
+ **Request:**
+
+ *Account-Owner-Signature*:
+
+ The client must provide Base-32 encoded EdDSA signature with
+ ``$ACCOUNT_PRIV``, affirming the desire to obtain KYC data. Note that
+ this is merely a simple authentication mechanism, the details of the
+ request are not protected by the signature. The ``$ACCOUNT_PRIV`` is
+ either the (wallet long-term) reserve private key or the merchant instance
+ private key.
+
+ :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
+ wait up to ``timeout_ms`` milliseconds if the requirement continues
+ to be mandatory provisioning of KYC data by the client.
+ Ignored if the HTTP status code is already ``200 Ok``. Note that
+ clients cannot long-poll for AML staff actions, so status information
+ about an account being under AML review needs to be requested
+ periodically.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ No mandatory KYC actions are required by the client at this time.
+ The client *may* still visit the KYC URL to initiate voluntary checks.
+ The response will be an `AccountKycStatus` object which specifies
+ restrictions that currently apply to the account. If the
+ client attempts to exceed *soft* limits, the status may change
+ to a ``202 Accepted``. Hard limits cannot be lifted by passing KYC checks.
+ :http:statuscode:`202 Accepted`:
+ The account holder performed an operation that would have crossed
+ *soft* limits and must be redirected to the provided location to perform
+ the required KYC checks to satisfy the legal requirements. Afterwards, the
+ ``/kyc-check/`` request should be repeated to check whether the
+ user has completed the process.
+ The response will be an `AccountKycStatus` object.
+ :http:statuscode:`204 No content`:
+ The exchange is not configured to perform KYC and thus
+ the legal requirements are already satisfied.
+ :http:statuscode:`403 Forbidden`:
+ The provided signature is not acceptable for the requirement row.
+ :http:statuscode:`404 Not found`:
+ The requirement row is unknown.
+
+ **Details:**
+
+ .. ts:def:: AccountKycStatus
+
+ interface AccountKycStatus {
+
+ // Current AML state for the target account. True if
+ // operations are not happening due to staff processing
+ // paperwork *or* due to legal requirements (so the
+ // client cannot do anything but wait).
+ //
+ // Note that not every AML staff action may be legally
+ // exposed to the client, so this is merely a hint that
+ // a client should be told that AML staff is currently
+ // reviewing the account. AML staff *may* review
+ // accounts without this flag being set!
+ aml_review: boolean;
+
+ // Access token needed to construct the ``/kyc-spa/``
+ // URL that the user should open in a browser to
+ // proceed with the KYC process (optional if the status
+ // type is ``200 Ok``, mandatory if the HTTP status
+ // is ``202 Accepted``).
+ access_token: AccountAccessToken;
+
+ // Array with limitations that currently apply to this
+ // account and that may be increased or lifted if the
+ // KYC check is passed.
+ // Note that additional limits *may* exist and not be
+ // communicated to the client. If such limits are
+ // reached, this *may* be indicated by the account
+ // going into ``aml_review`` state. However, it is
+ // also possible that the exchange may legally have
+ // to deny operations without being allowed to provide
+ // any justification.
+ // The limits should be used by the client to
+ // possibly structure their operations (e.g. withdraw
+ // what is possible below the limit, ask the user to
+ // pass KYC checks or withdraw the rest after the time
+ // limit is passed, warn the user to not withdraw too
+ // much or even prevent the user from generating a
+ // request that would cause it to exceed hard limits).
+ limits?: AccountLimit[];
+
+ }
+
+ .. ts:def:: AccountLimit
+
+ interface AccountLimit {
+
+ // Operation that is limited.
+ // Must be one of "WITHDRAW", "DEPOSIT", "P2P-RECEIVE"
+ // or "WALLET-BALANCE".
+ operation_type: string;
+
+ // Timeframe during which the limit applies.
+ timeframe: RelativeTime;
+
+ // Maximum amount allowed during the given timeframe.
+ // Zero if the operation is simply forbidden.
+ threshold: Amount;
+
+ // True if this is a soft limit that could be raised
+ // by passing KYC checks. Clients *may* deliberately
+ // try to cross limits and trigger measures resulting
+ // in 451 responses to begin KYC processes.
+ // Clients that are aware of hard limits *should*
+ // inform users about the hard limit and prevent flows
+ // in the UI that would cause violations of hard limits.
+ soft_limit: boolean;
+ }
+
+
+.. http:get:: /kyc-spa/$ACCESS_TOKEN
+.. http:get:: /kyc-spa/$FILENAME
+
+ A set of ``/kyc-spa/$ACCESS_TOKEN`` GET endpoints is created per account
+ hash that serves the KYC SPA. This is where the ``/kyc-check/`` endpoint
+ will in principle redirect clients. The KYC SPA will use the
+ ``$ACCESS_TOKEN`` of its URL to initialize itself via the
+ ``/kyc-info/$ACCESS_TOKEN`` endpoint family. The KYC SPA may download
+ additional resources via ``/kyc-spa/$FILENAME``. The filenames must not
+ match base32-encoded 256-bit values.
+
+.. http:get:: /kyc-info/$ACCESS_TOKEN
+
+ The ``/kyc-info/$ACCESS_TOKEN`` endpoints are created per client
+ account hash (but access controlled via a unique target token)
+ to return information about the state of the KYC or AML process
+ to the KYC SPA. The SPA uses this information to show the user an
+ appropriate dialog. The SPA should also long-poll this endpoint for changes
+ to the AML/KYC state. Note that this is a client-facing endpoint, so it will
+ only provide a restricted amount of information to the customer (as some
+ laws may forbid us to inform particular customers about their true status).
+ The endpoint will typically inform the SPA about possible choices to
+ proceed, such as directly uploading files, contacting AML staff, or
+ proceeding with a particular KYC process at an external provider (such as
+ Challenger). If the user chooses to initate a KYC process at an external
+ provider, the SPA must request the respective process to be set-up by the
+ exchange via the ``/kyc-start/`` endpoint.
+
+ **Request:**
+
+ *If-None-Match*:
+ The client MAY provide an ``If-None-Match`` header with an ETag.
+
+ :query timeout_ms=MILLISECONDS:
+ *Optional.* If specified, the exchange will wait up to MILLISECONDS for
+ a change to a more recent legitimization measure before returning a 304
+ Not Modified status.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The body is a `KycProcessClientInformation`.
+
+ *Etag*: Will be set to the serial ID of the measure. Used for long-polling.
+
+ .. ts:def:: KycProcessClientInformation
+
+ interface KycProcessClientInformation {
-* **Configuration**: The configuration determines the *legitimization rules*, and specifies which providers offer which *checks* at what *cost*.
+ // List of requirements.
+ requirements?: { name : KycRequirementInformation};
-* **Cost**: Metric for the business expense for a KYC check at a certain *provider*. Not in any currency, costs are simply relative and non-negative values. Costs are considered when multiple choices are allowed by the *configuration*.
+ // True if the client is expected to eventually satisfy all requirements.
+ // Default (if missing) is false.
+ is_and_combinator?: boolean
-* **Expiration**: KYC legitimizations may be outdated. Expiration rules determine when *checks* have to be performed again.
+ // List of available voluntary checks the client could pay for.
+ // Since **vATTEST**.
+ voluntary_checks?: { name : KycCheckInformation};
+ }
-* **Legitimization rules**: The legitimization rules determine under which *conditions* which *checks* must be performend and the *expiration* time period for the *checks*.
+ .. ts:def:: KycRequirementInformation
-* **Logic**: Logic refers to a specific bit of code (realized as an exchange plugin) that enables the interaction with a specific *provider*. Logic typically requires *configuration* for access control (such as an authorization token) and possibly the endpoint of the specific *provider* implementing the respective API.
+ interface KycRequirementInformation {
-* **Provider**: A provider performs a specific set of *checks* at a certain *cost*. Interaction with a provider is performed by provider-specific *logic*.
+ // Which form should be used? Common values include "INFO"
+ // (to just show the descriptions but allow no action),
+ // "LINK" (to enable the user to obtain a link via
+ // ``/kyc-start/``) or any build-in form name supported
+ // by the SPA.
+ form: string;
-* **Type of operation**: The operation type determines which Taler-specific operation has triggered the KYC requirement. We support four types of operation: withdraw (by customer), deposit (by merchant), P2P receive (by wallet) and (high) wallet balance.
+ // English description of the requirement.
+ description: string;
+ // Map from IETF BCP 47 language tags to localized
+ // description texts.
+ description_i18n ?: { [lang_tag: string]: string };
-New Endpoints
-^^^^^^^^^^^^^
+ // ID of the requirement, useful to construct the
+ // ``/kyc-upload/$ID`` or ``/kyc-start/$ID`` endpoint URLs.
+ // Present if and only if "form" is not "INFO". The
+ // ``$ID`` value may itself contain ``/`` or ``?`` and
+ // basically encode any URL path (and optional arguments).
+ id?: string;
-We introduce a new ``wire_targets`` table into the exchange database. This
-table is referenced as the source or destination of payments (regular deposits
-and also P2P payments). A positive side-effect is that we reduce duplication
-in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
-reference this table.
+ }
-We introduce a new ``legitimization_processes`` table that tracks the status
-of a legitimization process at a provider, including the configuration section
-name, the user/account name at the provider, and some legitimization
-identifier for the process at the provider. In this table, we additionally
-store information related to the KYC status of the underlying payto://-URI, in
-particular when the KYC expires (0 if it was never done).
+ .. ts:def:: KycCheckInformation
-Finally, we introduce a new ``legitimization_requirements`` table that
-contains a list of checks required for a particular wire target. When KYC is
-triggered (say when some endpoint returns an HTTP status code of 451) a
-new requirement is first put into the requirements table. Then, when the
-client identifies as business or individual the specific legitimization
-process is started. When the taler-exchange-aggregator triggers a KYC check
-the merchant can observe this when a 202 (Accepted) status code is returned
-on GET ``/deposits/`` with the respective legitimization requirement row.
+ // Since **vATTEST**.
+ interface KycCheckInformation {
-The new ``/kyc-check/`` endpoint is based on the legitimization requirements
-serial number and receives the business vs. individual status from the client.
-Access is ``authenticated`` by also passing the hash of the payto://-URI.
-(Weak authentication is acceptable, as the KYC status or the ability to
-initiate a KYC process are not very sensitive.) Given this triplet, the
-``/kyc-check/`` endpoint returns either the (positive) KYC status or redirects
-the client (202) to the next required stage of the KYC process. The
-redirection must be for an HTTP(S) endpoint to be triggered via a simple HTTP GET. As this endpoint is involved in every KYC check at the beginning, this is also the place where we can
-integrate the payment process for the KYC fee.
-
-The specific KYC provider to be executed depends on the configuration (see
-below) which specifies a ``$PROVIDER_SECTION`` for each authentication procedure.
-For each (enabled) provider, the exchange has a logic plugin which
-(asynchronously) determines the redirect URL for a given wire target. See
-below for a description of the high-level process for different providers.
-
-Upon completion of the process at the KYC provider, the provider must trigger
-a GET request to a new ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint.
-This may be done either by redirecting the browser of the user to that
-endpoint. Once this endpoint is triggered, the exchange will pass the
-received arguments to the respective logic plugin. The logic plugin will then
-(asynchronously) update the KYC status of the user. The logic plugin should
-return a human-readable HTML page with the KYC result to the user.
-
-Alternatively, the KYC confirmation may be triggered by a ``/kyc-webhook``
-request. As KYC providers do not necessarily support passing detailed
-information in the URL arguments, the ``/kyc-webhook`` only needs to specify
-either the ``PROVIDER_SECTION`` *or* the ``LOGIC`` (the name of the plugin
-implementing the KYC API). The API-specific webhook logic must then figure
-out what exactly the webhook is about on its own. The ``/kyc-webhook/``
-endpoint works for GET or POST, again as details depend on the KYC provider.
-In contrast to ``kyc-proof``, the response does NOT go to the end-users'
-browser and should thus only indicate success or failure.
-
-
-Legitimization Hooks
-^^^^^^^^^^^^^^^^^^^^
+ // English description of the check.
+ description: string;
+
+ // Map from IETF BCP 47 language tags to localized
+ // description texts.
+ description_i18n ?: { [lang_tag: string]: string };
+
+ }
+
+ :http:statuscode:`204 No Content`:
+ There are no open KYC requirements or possible voluntary checks
+ the client might perform.
+
+ :http:statuscode:`304 Not Modified`:
+ The KYC requirements did not change.
+
+
+.. http:post:: /kyc-upload/$ID
+
+ The ``/kyc-upload/$ID`` POST endpoint allows the SPA to upload
+ client-provided evidence. The ``$ID`` will be provided as part of the
+ ``/kyc-info`` body. This is for checks of type ``FORM``. In practice,
+ ``$ID`` will encode both the ``$ACCESS_TOKEN`` and the index of the selected
+ measure (but this should be irrelevant for the client).
+
+ **Request:**
+
+ Basically oriented along the possible formats of a HTTP form being
+ POSTed. Details will depend on the form. The server will try to decode the
+ uploaded body from whatever format it is provided in.
+
+ **Response:**
+
+ :http:statuscode:`204 No Content`:
+ The information was successfully uploaded. The SPA should fetch
+ an updated ``/kyc-info/``.
+ :http:statuscode:`404 Not Found`:
+ The ``$ID`` is unknown to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The upload conflicts with a previous upload.
+ :http:statuscode:`413 Content Too Large`:
+ The body is too large.
+
+.. http:post:: /kyc-start/$ID
+
+ The ``/kyc-start/$ID`` POST endpoint allows the SPA to set up a new external
+ KYC process. It will return the URL that the client must GET to begin the
+ KYC process. The SPA should probably open this URL in a new window or tab.
+ The ``$ID`` will be provided as part of the ``/kyc-info`` body. In
+ practice, ``$ID`` will encode both the ``$ACCESS_TOKEN`` and the index of
+ the selected measure (but this should be irrelevant for the client).
+
+ **Request:**
+
+ Use empty JSON body for now.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ The KYC process was successfully initiated. The URL is in a
+ `KycProcessStartInformation` object.
+
+ **Details:**
+
+ .. ts:def:: KycProcessStartInformation
+
+ interface KycProcessStartInformation {
+
+ // URL to open.
+ redirect_url: string;
+ }
+
+ :http:statuscode:`404 Not Found`:
+ The ``$ID`` is unknown to the exchange.
+
+ .. note::
+
+ As this endpoint is involved in every KYC check at the beginning, this
+ is also the place where we could integrate the payment process for the KYC fee
+ in the future (since **vATTEST**).
+
+
+.. http:get:: /kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO
+
+ Upon completion of the process at the external KYC provider, the provider
+ must redirect the client (browser) to trigger a GET request to a new
+ ``/kyc-proof/$H_PAYTO/$PROVIDER_SECTION`` endpoint. Once this endpoint is
+ triggered, the exchange will pass the received arguments to the respective
+ logic plugin. The logic plugin will then (asynchronously) update the KYC
+ status of the user. The logic plugin should redirect the user to the KYC
+ SPA. This endpoint deliberately does not use the ``$ACCESS_TOKEN`` as the
+ external KYC provider should not learn that token.
+
+ This endpoint is thus accessed from the user's browser at the *end* of a KYC
+ process, possibly providing the exchange with additional credentials to
+ obtain the results of the KYC process. Specifically, the URL arguments
+ should provide information to the exchange that allows it to verify that the
+ user has completed the KYC process. The details depend on the logic, which
+ is selected by the "$PROVIDER_SECTION".
+
+ While this is a GET (and thus safe, and idempotent), the operation may
+ actually trigger significant changes in the exchange's state. In
+ particular, it may update the KYC status of a particular payment target.
+
+ **Request:**
+
+ Details on the request depend on the specific KYC logic that was used.
+
+ If the KYC plugin logic is OAuth 2.0, the query parameters are:
+
+ :query code=CODE:
+ OAuth 2.0 code argument.
+ :query state=STATE:
+ OAuth 2.0 state argument with the H_PAYTO.
+
+ .. note::
+
+ Depending on the OAuth variant used, additional
+ query parameters may need to be passed here.
+
+ **Response:**
+
+ Given that the response is returned to a user using a browser and **not** to
+ a Taler wallet, the response format is in human-readable HTML and not in
+ machine-readable JSON.
+
+ :http:statuscode:`302 Found`:
+ The KYC operation succeeded and the
+ payment target is now authorized to transact.
+ The browser is redirected to a human-readable
+ page configured by the exchange operator.
+ :http:statuscode:`401 Unauthorized`:
+ The provided authorization token is invalid.
+ :http:statuscode:`404 Not found`:
+ The payment target is unknown.
+ :http:statuscode:`502 Bad Gateway`:
+ The exchange received an invalid reply from the
+ legitimization service.
+ :http:statuscode:`504 Gateway Timeout`:
+ The exchange did not receive a reply from the legitimization
+ service within a reasonable time period.
+
+
+.. http:get:: /kyc-webhook/$PROVIDER_SECTION/*
+.. http:post:: /kyc-webhook/$PROVIDER_SECTION/*
+.. http:get:: /kyc-webhook/$LOGIC/*
+.. http:post:: /kyc-webhook/$LOGIC/*
+
+ Alternatively, the KYC confirmation may be triggered by a ``/kyc-webhook``
+ request. As KYC **providers** do not necessarily support passing detailed
+ information in the URL arguments, the ``/kyc-webhook`` only needs to specify
+ either the ``PROVIDER_SECTION`` *or* the ``LOGIC`` (the name of the plugin
+ implementing the KYC API). The API-specific webhook logic must then figure
+ out what exactly the webhook is about on its own. The ``/kyc-webhook/``
+ endpoint works for GET or POST, again as details depend on the KYC provider.
+ In contrast to ``kyc-proof``, the response does NOT go to the end-users'
+ browser and should thus only indicate success or failure.
+
+ **Request:**
+
+ Details on the request depend on the specific KYC logic that was used.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The operation succeeded.
+ :http:statuscode:`404 Not found`:
+ The specified logic is unknown.
+
+
+.. http:post:: /kyc-wallet
+
+ The ``/wallet-kyc`` POST endpoint allows a wallet to notify an exchange if
+ it will cross a balance threshold. Here, the ``balance`` specified should be
+ the threshold (from the ``wallet_balance_limit_without_kyc`` array) that the
+ wallet would cross, and *not* the *exact* balance of the wallet. The exchange
+ will respond with a wire target UUID. The wallet can then use this UUID to
+ being the KYC process at ``/kyc-check/``. The wallet must only proceed to
+ obtain funds exceeding the threshold after the KYC process has concluded.
+ While wallets could be "hacked" to bypass this measure (we cannot
+ cryptographically enforce this), such modifications are a terms of service
+ violation which may have legal consequences for the user.
+
+ Setup KYC identification for a wallet. Returns the KYC UUID. This endpoint
+ is used by compliant Taler wallets when they are about to hit the balance
+ threshold and thus need to have the customer provide their personal details
+ to the exchange. The wallet is identified by its long-lived reserve public
+ key (which is used for P2P payments, not for withdrawals).
+
+ **Request:**
+
+ The request body must be a `WalletKycRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`204 No Content`:
+ KYC is disabled at this exchange, or the balance is below the
+ threshold that requires KYC, or this wallet already satisfied
+ the KYC check for the given balance.
+ :http:statuscode:`403 Forbidden`:
+ The provided signature is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ The wallet must undergo a KYC check. A KYC ID was created.
+ The response will be a `KycNeededRedirect` object.
+
+ **Details:**
+
+ .. ts:def:: WalletKycRequest
+
+ interface WalletKycRequest {
+
+ // Balance threshold (not necessarily exact balance)
+ // to be crossed by the wallet that (may) trigger
+ // additional KYC requirements.
+ balance: Amount;
+
+ // EdDSA signature of the wallet affirming the
+ // request, must be of purpose
+ // ``TALER_SIGNATURE_WALLET_ACCOUNT_SETUP``
+ reserve_sig: EddsaSignature;
+
+ // long-term wallet reserve-account
+ // public key used to create the signature.
+ reserve_pub: EddsaPublicKey;
+ }
+
+
+.. http:get:: /aml/$OFFICER_PUB/measures
+
+ To enable the AML staff SPA to give AML staff a choice of possible measures, a
+ new endpoint ``/aml/$OFFICER_PUB/measures`` is added that allows the AML SPA
+ to dynamically GET the list of available measures. It returns a list of known
+ KYC checks (by name) with their descriptions and a list of AML programs with
+ information about the required context.
+
+ **Request:**
+
+ *Taler-AML-Officer-Signature*:
+ The client must provide Base-32 encoded EdDSA signature with
+ ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that
+ this is merely a simple authentication mechanism, the details of the
+ request are not protected by the signature.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ Information about possible measures is returned in a
+ `AvailableMeasureSummary` object.
+
+ **Details:**
+
+ .. ts:def:: AvailableMeasureSummary
+
+ interface AvailableMeasureSummary {
+
+ // Available original measures that can be
+ // triggered directly by default rules.
+ roots: { "$measure_name" : MeasureInformation; };
+
+ // Available AML programs.
+ programs: { "$prog_name" : AmlProgramRequirement; };
+
+ // Available KYC checks.
+ checks: { "$check_name" : KycCheckInformation; };
+
+ }
+
+ .. ts:def:: MeasureInformation
+
+ interface MeasureInformation {
+
+ // Name of a KYC check.
+ check_name: string;
+
+ // Name of an AML program.
+ prog_name: string;
+
+ // Context for the check. Optional.
+ context?: Object;
+
+ }
+
+ .. ts:def:: AmlProgramRequirement
+
+ interface AmlProgramRequirement {
+
+ // Description of what the AML program does.
+ description: string;
+
+ // List of required field names in the context to run this
+ // AML program. SPA must check that the AML staff is providing
+ // adequate CONTEXT when defining a measure using this program.
+ context: string[];
+
+ // List of required attribute names in the
+ // input of this AML program. These attributes
+ // are the minimum that the check must produce
+ // (it may produce more).
+ inputs: string[];
+
+ }
+
+ .. ts:def:: KycCheckInformation
+
+ interface KycCheckInformation {
+
+ // Description of the KYC check. Should be shown
+ // to the AML staff but will also be shown to the
+ // client when they initiate the check in the KYC SPA.
+ description: string;
+ description_i18n: {};
+
+ // Names of the fields that the CONTEXT must provide
+ // as inputs to this check.
+ // SPA must check that the AML staff is providing
+ // adequate CONTEXT when defining a measure using
+ // this check.
+ requires: string[];
+
+ // Names of the attributes the check will output.
+ // SPA must check that the outputs match the
+ // required inputs when combining a KYC check
+ // with an AML program into a measure.
+ outputs: string[];
+
+ // Name of a root measure taken when this check fails.
+ fallback: string;
+ }
+
+.. http:get:: /aml/$OFFICER_PUB/kyc-statistics/$NAME
+
+ Returns the number of KYC events matching the given event type ``$NAME`` in
+ the specified time range. Note that this query can be slow as the
+ statistics are computed on-demand. (This is OK as such requests should be
+ rare.)
+
+ **Request:**
+
+ *Taler-AML-Officer-Signature*:
+ The client must provide Base-32 encoded EdDSA signature with
+ ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that this
+ is merely a simple authentication mechanism, the details of the request are
+ not protected by the signature.
+
+ :query start_date=TIMESTAMP:
+ *Optional*. Specifies the date when to
+ start looking (inclusive). If not given, the start time of the
+ exchange operation is used.
+ :query end_date=TIMESTAMP:
+ *Optional*. Specifies the date when to
+ stop looking (exclusive). If not given, the current date is used.
+
+ **Response:**
+
+ .. ts:def:: EventCounter
+
+ interface EventCounter {
+ // Number of events of the specified type in
+ // the given range.
+ counter: Integer;
+ }
+
+.. http:get:: /aml/$OFFICER_PUB/decisions
+
+ **Request:**
+
+ *Taler-AML-Officer-Signature*:
+ The client must provide Base-32 encoded EdDSA signature with
+ ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that
+ this is merely a simple authentication mechanism, the details of the
+ request are not protected by the signature.
+
+ :query limit:
+ *Optional*. takes value of the form ``N (-N)``, so that at
+ most ``N`` values strictly older (younger) than ``start`` are returned.
+ Defaults to ``-20`` to return the last 20 entries (before ``start``).
+ :query offset:
+ *Optional*. Row number threshold, see ``delta`` for its
+ interpretation. Defaults to ``INT64_MAX``, namely the biggest row id
+ possible in the database.
+ :query h_payto:
+ *Optional*. Account selector. All matching accounts are returned if this
+ filter is absent, otherwise only decisions for this account.
+ :query active:
+ *Optional*. If set to yes, only return active decisions, if no only
+ decisions that have been superceeded. Do not give (or use "all") to
+ see all decisions regardless of activity status.
+ :query investigation:
+ *Optional*. If set to yes, only return accounts that are under
+ AML investigation, if no only accounts that are not under investigation.
+ Do not give (or use "all") to see all accounts regardless of
+ investigation status.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The responds will be an `AmlDecisions` message.
+ :http:statuscode:`204 No content`:
+ There are no matching AML records.
+ :http:statuscode:`403 Forbidden`:
+ The signature is invalid.
+ :http:statuscode:`404 Not found`:
+ The designated AML account is not known.
+ :http:statuscode:`409 Conflict`:
+ The designated AML account is not enabled.
+
+ **Details:**
+
+ .. ts:def:: AmlDecisions
+
+ interface AmlDecisions {
+
+ // Array of AML decisions matching the query.
+ records: AmlDecisions[];
+ }
+
+ .. ts:def:: AmlRecord
+
+ interface AmlRecord {
+
+ // Which payto-address is this record about.
+ // Identifies a GNU Taler wallet or an affected bank account.
+ h_payto: PaytoHash;
+
+ // Row ID of the record. Used to filter by offset.
+ rowid: Integer;
+
+ // FIXME: more fields here!
+ }
+
+
+.. http:get:: /aml/$OFFICER_PUB/attributes/$H_PAYTO
+
+ Obtain attributes obtained as part of AML/KYC processes for a
+ given account.
+
+ **Request:**
+
+ *Taler-AML-Officer-Signature*:
+ The client must provide Base-32 encoded EdDSA signature with
+ ``$OFFICER_PRIV``, affirming the desire to obtain AML data. Note that
+ this is merely a simple authentication mechanism, the details of the
+ request are not protected by the signature.
+
+ :query limit:
+ *Optional*. takes value of the form ``N (-N)``, so that at
+ most ``N`` values strictly older (younger) than ``start`` are returned.
+ Defaults to ``-20`` to return the last 20 entries (before ``start``).
+ :query offset:
+ *Optional*. Row number threshold, see ``delta`` for its
+ interpretation. Defaults to ``INT64_MAX``, namely the biggest row id
+ possible in the database.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The responds will be an `KycAttributes` message.
+ :http:statuscode:`204 No content`:
+ There are no matching KYC attributes.
+ :http:statuscode:`403 Forbidden`:
+ The signature is invalid.
+ :http:statuscode:`404 Not found`:
+ The designated AML account is not known.
+ :http:statuscode:`409 Conflict`:
+ The designated AML account is not enabled.
+
+ .. ts:def:: KycAttributes
+
+ interface KycAttributes {
+
+ // Matching KYC attribute history of the account.
+ details: KycDetail[];
+
+ }
+
+ .. ts:def:: KycDetail
+
+ // FIXME: bad name?
+ interface KycDetail {
+
+ // Row ID of the record. Used to filter by offset.
+ rowid: Integer;
+
+ // Name of the configuration section that specifies the provider
+ // which was used to collect the attributes. NULL if they were
+ // just uploaded via a form by the account owner.
+ provider_section?: string;
+
+ // The collected KYC data. NULL if the attribute data could not
+ // be decrypted (internal error of the exchange, likely the
+ // attribute key was changed).
+ attributes?: Object;
+
+ // Time when the KYC data was collected
+ collection_time: Timestamp;
+
+ }
+
+
+ .. http:post:: /aml/$OFFICER_PUB/decision
+
+ Make an AML decision. Triggers the respective action and
+ records the justification.
+
+ **Request:**
+
+ The request body must be an `AmlDecision` message.
+
+ **Response:**
+
+ :http:statuscode:`204 No content`:
+ The AML decision has been executed and recorded successfully.
+ :http:statuscode:`403 Forbidden`:
+ The signature is invalid.
+ :http:statuscode:`404 Not found`:
+ The address the decision was made upon is unknown to the exchange or
+ the designated AML account is not known.
+ :http:statuscode:`409 Conflict`:
+ The designated AML account is not enabled or a more recent
+ decision was already submitted.
+
+ **Details:**
+
+ .. ts:def:: AmlDecision
+
+ interface AmlDecision {
+
+ // Human-readable justification for the decision.
+ justification: string;
+
+ // Which payto-address is the decision about?
+ // Identifies a GNU Taler wallet or an affected bank account.
+ h_payto: PaytoHash;
+
+ // What are the new rules?
+ new_rules: LegitimizationRuleSet;
+
+ // True if the account should remain under investigation by AML staff.
+ bool keep_investigating;
+
+ // When was the decision made?
+ decision_time: Timestamp;
+
+ // Signature by the AML officer over a `TALER_AmlDecisionPS`.
+ // Must have purpose ``TALER_SIGNATURE_MASTER_AML_KEY``.
+ officer_sig: EddsaSignature;
+
+ }
+
+
+Modifications to existing endpoints
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When withdrawing, the exchange checks if the KYC status is acceptable. If no
KYC was done and if either the amount withdrawn over a particular timeframe
@@ -161,132 +1290,728 @@ account-reserve key pair. This should be the same key that is also used to
receive wallet-to-wallet payments. Then, *before* a wallet performs an
operation that would cause it to exceed the balance threshold in terms of
funds held from a particular exchange, it *should* first request the user to
-complete the KYC process.
+complete the KYC process. For that, the wallet should POST to the new
+``/kyc-wallet`` endpoint, providing its long-term reserve-account public key
+and a signature requesting permission to exceed the account limit.
+
+
+Configuration of external KYC providers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For each KYC provider that could contribute to checks the configuration
+specifies a ``$PROVIDER_SECTION`` for each authentication procedure. For each
+(enabled) provider, the exchange has a logic plugin which (asynchronously)
+determines the redirect URL for a given wire target. See below for a
+description of the high-level process for different providers.
+
+.. code-block:: ini
+
+ [kyc-provider-$PROVIDER_ID]
+
+ # Which plugin is responsible for this provider?
+ LOGIC = PLUGIN_NAME
+
+ # Optional cost, useful if clients want to voluntarily
+ # trigger authentication procedures for attestation.
+ # Since **vATTEST**.
+ COST = EUR:5
+
+ # Plus additional logic-specific options, e.g.:
+ AUTHORIZATION_TOKEN = superdupersecret
+
+ # Other logic-specific internal options (example):
+ FORM_ID = business_legi_form
+
+ # Name of a program to run on the output of the plugin
+ # to convert the result into the desired set of attributes.
+ # The converter must create a log for the system administrator
+ # if the provided inputs do not match expectations.
+ # Note that the converter will be expected to output the
+ # set of attributes listed under the respective ``[kyc-check-*]``
+ # sections. Calling the converter with ``--list-outputs``
+ # should generate a (newline-separated) list of attributes
+ # the converter promises to generate in its JSON output
+ # (when run regularly).
+ CONVERTER = taler-exchange-helper-$NAME
+
+
+Configuration of possible KYC/AML checks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The configuration specifies a set of possible KYC checks offered by external
+providers, one per configuration section:
+
+.. code-block:: ini
+
+ [kyc-check-$CHECK_NAME]
+
+ # Which type of check is this? Also determines
+ # the SPA form to show to the user for this check.
+ #
+ # INFO: wait for staff or contact staff out-of band
+ # (only information shown, no SPA action)
+ # FORM: SPA should show an inline (HTML) form
+ # LINK: SPA may start external KYC process or upload
+ #
+ TYPE = INFO|LINK|FORM
+
+ # Optional. Set to YES to allow this check be
+ # done voluntarily by a client (they may then
+ # still have to pay for it). Used to offer the
+ # SPA to display checks even if they are
+ # not required. Default is NO.
+ # Since **vATTEST**.
+ VOLUNTARY = YES/NO
+
+ # Provider id, present only if type is LINK.
+ PROVIDER_ID = id
+
+ # Name of the SPA form, if type is FORM
+ # "INFO" and "LINK" are reserved and must not be used.
+ # The exchange server and the SPA must agree on a list
+ # of supported forms and the resulting attributes.
+ #
+ # The SPA should include a JSON resource file
+ # "forms.json" mapping form names to arrays of
+ # attribute names each form provides.
+ FORM_NAME = name
+
+ # Descriptions to use in the SPA to display the check.
+ DESCRIPTION = "Upload your passport picture"
+ DESCRIPTION_I18N = "{"en":"Upload scan of your passport"}"
+
+ # ';'-separated list of fields that the CONTEXT must
+ # provided as inputs to this check. For example,
+ # for a FORM of type CHOICE, this might state
+ # ``choices: string[];``. The type after the ":"
+ # is for now purely for documentation and is
+ # not checked. However, it may be shown to AML staff
+ # when they configure measures.
+ REQUIRES = requirement;
+
+ # Description of the outputs provided by the check.
+ # Basically, the check's output is expected to
+ # provide the following fields as inputs into
+ # a subsequent AML program.
+ OUTPUTS = business_name street city country registration
+
+ # **original** measure to take if the check fails
+ # (for any reason, e.g. provider or form fail to
+ # satisfy constraints or provider signals user error)
+ # Usually should point to a measure that requests
+ # AML staff to investigate. The fallback measure
+ # context always includes the reasons for the
+ # failure.
+ FALLBACK = MEASURE_NAME
+
+The list of possible FORM names is fixed in the SPA
+for a particular exchange release.
+
+The outcome of *any* check should always be uploaded encrypted into the
+``kyc_attributes`` table. It MUST include an ``expiration_time``.
+
+
+Configuration of legitimization requirement triggers
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The configuration also specifies a set of legitimization rules including the
+condition and the measure the condition triggers, one condition per
+configuration section:
+
+.. code-block:: ini
+
+ [kyc-rule-$RULE_NAME]
+
+ # Operation that triggers this legitimization.
+ # Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
+ # or WALLET-BALANCE.
+ OPERATION_TYPE = WITHDRAW
+
+ # Space-separated list of next measures to be performed.
+ # The SPA should display *all* of these measures to the user.
+ # (They have a choice of either which ones, or in
+ # which order they are to be performed.)
+ # A special measure name "verboten" is used if the
+ # specified threshold may never be crossed
+ # (under this set of rules).
+ NEXT_MEASURES = SWISSNESS KYB
+
+ # "yes" if all NEXT_MEASURES will eventually need
+ # to be satisfied, "no" if the user has a choice between
+ # them. Not actually enforced by the exchange, but
+ # primarily used to inform the user whether this is
+ # an "and" or "or". YES for "and".
+ IS_AND_COMBINATOR = YES
+
+ # YES if the rule (specifically, operation type,
+ # threshold, timeframe) and the general nature of
+ # the next measure (verboten or approval required)
+ # should be exposed to the client.
+ # Defaults to NO if not set.
+ EXPOSED = YES
+
+ # Threshold amount above which the legitimization is
+ # triggered. The total must be exceeded in the given
+ # timeframe.
+ THRESHOLD = KUDOS:100
+
+ # Timeframe over which the amount to be compared to
+ # the THRESHOLD is calculated.
+ # Ignored for WALLET-BALANCE. Can be 'forever'.
+ TIMEFRAME = 30 days
+
+ # Enabled (default is NO)
+ ENABLED = NO
+
+
+AML programs
+^^^^^^^^^^^^
+
+AML programs are helper programs that can:
+
+* Generate a list of *required* context field names
+ for the helper (introspection!) using the "--required-context"
+ command-line switch. The output should use the same
+ syntax as the REQUIRES clause of ``[kyc-check-]``
+ configuration sections, except that new lines
+ MUST be used to separate fields instead of ";".
+* Generate a list of *required* attribute names
+ for the helper (introspection!) using the "--required-attributes"
+ command-line switch. The output should use the same
+ list of names as the ATTRIBUTES in the
+ ``[kyc-provider-]`` configuration section
+ (but may also include FORM field names).
+* Process an input JSON object of type
+ `AmlProgramInput` into a JSON object of
+ type `AmlOutcome`.
+ This is the default behavior if no command-line switches
+ are provided.
+
+.. ts:def:: AmlProgramInput
+
+ interface AmlProgramInput {
+
+ // JSON object that was provided as
+ // part of the *measure*. This JSON object is
+ // provided under "context" in the main JSON object
+ // input to the AML program. This "context" should
+ // satify both the REQUIRES clause of the respective
+ // check and the output of "--requires" from the
+ // AML program's command-line option.
+ context?: Object;
+
+ // JSON object that captures the
+ // output of a ``[kyc-provider-]`` or (HTML) FORM.
+ // The keys in the JSON object will be the attribute
+ // names and the values must be strings representing
+ // the data. In the case of file uploads, the data
+ // MUST be base64-encoded.
+ attributes: Object;
+
+ // JSON array with the results of historic
+ // AML desisions about the account.
+ aml_history: AmlDecisionDetail[];
+
+ // JSON array with the results of historic
+ // KYC data about the account.
+ kyc_history: KycDetail[];
+
+ }
+
+.. ts:def:: AmlOutcome
+
+ interface AmlOutcome {
+
+ // Should the client's account be investigated
+ // by AML staff?
+ // Defaults to false.
+ to_investigate?: boolean;
+
+ // Free-form properties about the account.
+ // Can be used to store properties such as PEP,
+ // risk category, type of business, hits on
+ // sanctions lists, etc.
+ properties?: AccountProperties;
+
+ // Types of events to add to the KYC events table.
+ // (for statistics).
+ events?: string[];
+
+ // KYC rules to apply. Note that this
+ // overrides *all* of the default rules
+ // until the ``expiration_time`` and specifies
+ // the successor measure to apply after the
+ // expiration time.
+ new_rules: LegitimizationRuleSet;
+
+ }
+
+.. ts:def:: KycRule
+
+ interface KycRule {
+
+ // Type of operation to which the rule applies.
+ operation_type: string;
+
+ // The measures will be taken if the given
+ // threshold is crossed over the given timeframe.
+ threshold: Amount;
+
+ // Over which duration should the ``threshold`` be
+ // computed. All amounts of the respective
+ // ``operation_type`` will be added up for this
+ // duration and the sum compared to the ``threshold``.
+ timeframe: RelativeTime;
+
+ // Array of names of measures to apply.
+ // Names listed can be original measures or
+ // custom measures from the `AmlOutcome`.
+ // A special measure "verboten" is used if the
+ // threshold may never be crossed.
+ measures: string[];
+
+ // True if the rule (specifically, operation_type,
+ // threshold, timeframe) and the general nature of
+ // the measures (verboten or approval required)
+ // should be exposed to the client.
+ // Defaults to "false" if not set.
+ exposed?: boolean;
+
+ // True if all the measures will eventually need to
+ // be satisfied, false if any of the measures should
+ // do. Primarily used by the SPA to indicate how
+ // the measures apply when showing them to the user;
+ // in the end, AML programs will decide after each
+ // measure what to do next.
+ // Default (if missing) is false.
+ is_and_combinator?: boolean;
+
+ // If multiple rules apply to the same account
+ // at the same time, the number with the highest
+ // rule determines which set of measures will
+ // be activated and thus become visible for the
+ // user.
+ display_priority: integer;
+ }
+
+If the AML program fails (exits with a failure code or
+does not provide well-formed JSON output) the AML/KYC
+process continues with the FALLBACK measure. This should
+usually be one that asks AML staff to contact the
+systems administrator.
-For that, the wallet should POST to the new ``/wallet-kyc`` endpoint,
-providing its long-term reserve-account public key and a signature requesting
-permission to exceed the account limit. Here, the ``balance`` specified should
-be the threshold (from the ``wallet_balance_limit_without_kyc`` array) that
-the wallet would cross, and *not* the *exact* balance of the wallet. The
-exchange will respond with a wire target UUID. The wallet can then use this
-UUID to being the KYC process at ``/kyc-check/``. The wallet must only proceed
-to obtain funds exceeding the threshold after the KYC process has
-concluded. While wallets could be "hacked" to bypass this measure (we cannot
-cryptographically enforce this), such modifications are a terms of service
-violation which may have legal consequences for the user.
+AML programs are listed in the configuration file, one program per section:
+
+.. code-block:: ini
+ [aml-program-$PROG_NAME]
+ # Program to run.
+ COMMAND = taler-helper-aml-pep
-Configuration Options
-^^^^^^^^^^^^^^^^^^^^^
+ # Human-readable description of what this
+ # AML helper program will do. Used to show
+ # to the AML staff.
+ DESCRIPTION = "check if the customer is a PEP"
+
+ # True if this AML program is enabled (and thus can be
+ # used in measures and exposed to AML staff).
+ # Optional, default is NO.
+ ENABLED = YES
+
+ # **original** measure to take if COMMAND fails
+ # Usually points to a measure that asks AML staff
+ # to contact the systems administrator. The fallback measure
+ # context always includes the reasons for the
+ # failure.
+ FALLBACK = MEASURE_NAME
+
+
+Configuration of measures
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Finally, the configuration specifies a set of
+**original** *measures* one per configuration section:
+
+.. code-block:: ini
+
+ [kyc-measure-$MEASURE_NAME]
+
+ # Possible check for this measure. Optional.
+ # If not given, PROGRAM should be run immediately
+ # (on an empty set of attributes).
+ CHECK_NAME = IB_FORM
+
+ # Context for the check. The context can be
+ # just an empty JSON object if there is none.
+ CONTEXT = {"choices":["individual","business"]}
+
+ # Program to run on the context and check data to
+ # determine the outcome and next measure.
+ PROGRAM = taler-aml-program
+
+If no ``CHECK_NAME`` is provided at all, the AML ``PROGRAM`` is to be run
+immediately. This is useful if no client-interaction is required to arrive at
+a decision.
-The configuration specifies a set of providers, one per configuration section:
-
-[kyc-provider-$PROVIDER_ID]
-# How expensive is it to use this provider?
-# Used to pick the cheapest provider possible.
-COST = NUMBER
-# Which plugin is responsible for this provider?
-LOGIC = PLUGIN_NAME
-# Which type of user does this provider handle?
-# Either INDIVIDUAL or BUSINESS.
-USER_TYPE = INDIVIDUAL
-# Which checks does this provider provide?
-# List of strings, no specific semantics.
-PROVIDED_CHECKS = SMS GOVID PHOTO
-# Plus additional logic-specific options, e.g.:
-AUTHORIZATION_TOKEN = superdupersecret
-FORM_ID = business_legi_form
-# How long is the check considered valid?
-EXPIRATION = DURATION
-
-The configuration also specifies a set of legitimization
-requirements, one per configuration section:
-
-[kyc-legitimization-$RULE_NAME]
-# Operation that triggers this legitimization.
-# Must be one of WITHDRAW, DEPOSIT, P2P-RECEIVE
-# or WALLET-BALANCE.
-OPERATION_TYPE = WITHDRAW
-# Required checks to be performed.
-# List of strings, must individually match the
-# strings in one or more provider's PROVIDED_CHECKS.
-REQUIRED_CHECKS = SMS GOVID
-# Threshold amount above which the legitimization is
-# triggered. The total must be exceeded in the given
-# timeframe. Can be 'forever'.
-THRESHOLD = AMOUNT
-# Timeframe over which the amount to be compared to
-# the THRESHOLD is calculated.
-# Ignored for WALLET-BALANCE.
-TIMEFRAME = DURATION
-
-
-
-Exchange Database Schema
+.. note::
+
+ The list of *measures* is not complete: AML staff may freely define new
+ measures dynamically, usually by selecting checks, an AML program, and
+ providing context.
+
+
+Sanity checking
+^^^^^^^^^^^^^^^
+
+On start-up, ``taler-exchange-httpd`` should sanity-check its
+configuration. Specifically, it should validate that for all AML programs the
+input requirements (attributes and context) are claimed to be satisfied by the
+respective checks that may trigger those programs, and similarly that for all
+checks the original measures satisfy the context requirements for their KYC
+checks.
+
+As a result, any component (AML program, form or external check) is warranted
+to be always called with the declared required inputs. Furthermore, we can
+detect if a component fails to produce the required output and the
+configuration contains (presumably safe) FALLBACKs to address this case. The
+exchange *MUST* detect circular failures, like when a FALLBACK triggers a
+measure that itself immediately triggers again the same FALLBACK.
+
+
+Exchange database schema
^^^^^^^^^^^^^^^^^^^^^^^^
+We introduce a new ``wire_targets`` table into the exchange database. This
+table is referenced as the source or destination of payments (regular deposits
+and also P2P payments). A positive side-effect is that we reduce duplication
+in the ``reserves_in``, ``wire_out`` and ``deposits`` tables as they can
+reference this table.
+
+We introduce a new ``legitimization_processes`` table that tracks the status
+of a legitimization process at a provider, including the configuration section
+name, the user/account name at the provider, and some legitimization
+identifier for the process at the provider. In this table, we additionally
+store information related to the KYC status of the underlying payto://-URI, in
+particular when the KYC expires (0 if it was never done).
+
+Finally, we introduce a new ``legitimization_requirements`` table that
+contains a list of checks required for a particular wire target. When KYC is
+triggered (say when some endpoint returns an HTTP status code of 451) a
+new requirement is first put into the requirements table. Then, when the
+client identifies as business or individual the specific legitimization
+process is started. When the taler-exchange-aggregator triggers a KYC check
+the merchant can observe this when a 202 (Accepted) status code is returned
+on GET ``/deposits/`` with the respective legitimization requirement row.
+
+
.. sourcecode:: sql
- CREATE TABLE IF NOT EXISTS wire_targets
- (wire_target_serial_id BIGSERIAL UNIQUE
- ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64),
- ,payto_uri STRING NOT NULL
- ,PRIMARY KEY (h_payto)
- ) SHARD BY (h_payto);
+ CREATE TABLE wire_targets
+ (wire_target_serial_id BIGSERIAL UNIQUE
+ ,wire_target_h_payto BYTEA PRIMARY KEY CHECK (LENGTH(wire_target_h_payto)=32),
+ ,access_token BYTEA UNIQUE CHECK (LENGTH(access_token)=32) DEFAULT gen_random_bytes(32)
+ ,target_pub BYTEA CHECK (LENGTH(target_pub)=32) DEFAULT NULL
+ ,payto_uri STRING NOT NULL
+ )
+ PARTITION BY HASH (wire_target_h_payto);
+
COMMENT ON TABLE wire_targets
IS 'All recipients of money via the exchange';
- COMMENT ON COLUMN wire_targets.payto_uri
- IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
COMMENT ON COLUMN wire_targets.h_payto
IS 'Unsalted hash of payto_uri';
+ COMMENT ON COLUMN wire_targets.access_token
+ IS 'high-entropy random value that is used as a token to authorize access to the KYC process (without requiring a signature by target_priv)';
+ COMMENT ON COLUMN wire_targets.target_pub
+ IS 'Public key (reserve_pub or merchant_pub) associated with the account; NULL if KYC is not allowed for the account (if there was no incoming KYC wire transfer yet); updated, thus NOT available to the auditor';
+ COMMENT ON COLUMN wire_targets.payto_uri
+ IS 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)';
- CREATE TABLE IF NOT EXISTS legitimization_requirements
- (legitimization_requirement_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
- ,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)
- ,required_checks VARCHAR NOT NULL
- ,UNIQUE (h_payto, required_checks);
- ) PARTITION BY HASH (h_payto);
-
- CREATE TABLE IF NOT EXISTS legitimization_processes
- (legitimization_serial_id BIGSERIAL UNIQUE
+ CREATE TABLE IF NOT EXISTS legitimization_measures
+ (legitimization_measure_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,access_token BYTEA NOT NULL UNIQUE CHECK (LENGTH(access_token)=32)
+ REFERENCES wire_targets (access_token)
+ ,start_time INT8 NOT NULL
+ ,jmeasures TEXT NOT NULL
+ ,display_priority INT4 NOT NULL
+ ,is_finished BOOL NOT NULL DEFAULT(FALSE)
+ )
+ PARTITION BY HASH (access_token);
+
+ COMMENT ON COLUMN legitimization_measures.access_token
+ IS 'Used to uniquely identify the account and as a symmetric access control mechanism for the SPA';
+ COMMENT ON COLUMN legitimization_measures.start_time
+ IS 'Time when the measure was triggered (by decision or rule)';
+ COMMENT ON COLUMN legitimization_measures.jmeasures
+ IS 'JSON object of type LegitimizationMeasures with KYC/AML measures for the account encoded';
+ COMMENT ON COLUMN legitimization_measures.display_priority
+ IS 'Display priority of the rule that triggered this measure; if in the meantime another rule also triggers, the measure is only replaced if the new rule has a higher display priority';
+ COMMENT ON COLUMN legitimization_measures.is_finished
+ IS 'Set to TRUE if this set of measures was processed; used to avoid indexing measures that are done';
+
+ CREATE INDEX ON legitimization_measures (access_token)
+ WHERE NOT is_finished;
+
+ CREATE TABLE legitimization_outcomes
+ (outcome_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,h_payto BYTEA CHECK (LENGTH(h_payto)=32)
+ REFERENCES wire_targets (wire_target_h_payto)
+ ,decision_time INT8 NOT NULL DEFAULT(0)
+ ,expiration_time INT8 NOT NULL DEFAULT(0)
+ ,jproperties TEXT,
+ ,to_investigate BOOL NOT NULL
+ ,is_active BOOL NOT NULL DEFAULT(TRUE)
+ ,jnew_rules TEXT NOT NULL
+ )
+ PARTITION BY HASH (h_payto);
+
+ COMMENT ON TABLE legitimization_outcomes
+ IS 'Outcomes can come from AML programs';
+ COMMENT ON COLUMN legitimization_outcomes.h_payto
+ IS 'hash of the payto://-URI this outcome is about';
+ COMMENT ON COLUMN legitimization_outcomes.decision_time
+ IS 'when was this outcome decided';
+ COMMENT ON COLUMN legitimization_outcomes.expiration_time
+ IS 'time when the decision expires and the expiration jnew_rules should be applied';
+ COMMENT ON COLUMN legitimization_outcomes.jproperties
+ IS 'JSON object of type AccountProperties, such as PEP status, business domain, risk assessment, etc.';
+ COMMENT ON COLUMN legitimization_outcomes.to_investigate
+ IS 'AML staff should investigate the activity of this account';
+ COMMENT ON COLUMN legitimization_outcomes.is_active
+ IS 'TRUE if this is the current authoritative legitimization outcome';
+ COMMENT ON COLUMN legitimization_outcomes.jnew_rules
+ IS 'JSON object of type LegitimizationRuleSet with rules to apply to the various operation types for this account; all KYC checks should first check if active new rules for a given account exist in this table (and apply specified measures); if not, it should check the default rules to decide if a measure is required';
+
+ CREATE INDEX legitimization_outcomes_active
+ ON legitimization_outcomes(h_payto)
+ WHERE is_active;
+
+ CREATE TABLE legitimization_processes
+ (legitimization_process_serial_id BIGSERIAL UNIQUE
,h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=64)
+ REFERENCES wire_targets (wire_target_h_payto)
+ ,start_time INT8 NOT NULL
,expiration_time INT8 NOT NULL DEFAULT (0)
- ,provider_section VARCHAR NOT NULL
- ,provider_user_id VARCHAR DEFAULT NULL
- ,provider_legitimization_id VARCHAR DEFAULT NULL
- ) PARTITION BY HASH (h_payto);
-
- COMMENT ON COLUMN legitimizations.legitimization_serial_id
- IS 'unique ID for this legitimization process at the exchange';
- COMMENT ON COLUMN legitimizations.h_payto
- IS 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)';
- COMMENT ON COLUMN legitimizations.expiration_time
- IS 'in the future if the respective KYC check was passed successfully';
- COMMENT ON COLUMN legitimizations.provider_section
+ ,legitimization_measure_serial_id INT8
+ REFERENCES legitimization_measures (legitimization_measure_serial_id)
+ ,measure_index INT4
+ ,provider_section TEXT NOT NULL
+ ,provider_user_id TEXT DEFAULT NULL
+ ,provider_legitimization_id TEXT DEFAULT NULL
+ ,redirect_url TEXT DEFAULT NULL
+ ,finished BOOLEAN DEFAULT (FALSE)
+ )
+ PARTITION BY HASH (h_payto);
+
+ COMMENT ON TABLE legitimization_processes
+ IS 'here we track KYC processes we initiated with external providers; the main reason is so that we do not initiate a second process when an equivalent one is still active; note that h_payto, provider_section, jcontext must match and the process must not be finished or expired for an existing redirect_url to be re-used; given that clients may voluntarily initiate KYC processes, there may not always be a legitimization_measure that triggered the setup';
+ COMMENT ON COLUMN legitimization_processes.h_payto
+ IS 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple KYC setups are possible per wire target)';
+ COMMENT ON COLUMN legitimization_processes.start_time
+ IS 'when was the legitimization process initiated';
+ COMMENT ON COLUMN legitimization_processes.expiration_time
+ IS 'when does the process expire (and needs to be manually set up again)';
+ COMMENT ON COLUMN legitimization_processes.measure_index
+ IS 'index of the measure in legitimization_measures that was selected for this KYC setup; NULL if legitimization_measure_serial_id is NULL; enables determination of the context data provided to the external process';
+ COMMENT ON COLUMN legitimization_processes.provider_section
IS 'Configuration file section with details about this provider';
- COMMENT ON COLUMN legitimizations.provider_user_id
+ COMMENT ON COLUMN legitimization_processes.provider_user_id
IS 'Identifier for the user at the provider that was used for the legitimization. NULL if provider is unaware.';
- COMMENT ON COLUMN legitimizations.provider_legitimization_id
+ COMMENT ON COLUMN legitimization_processes.provider_legitimization_id
IS 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.';
+ COMMENT ON COLUMN legitimization_processes.legitimization_measure_serial_id
+ IS 'measure that enabled this setup, NULL if client voluntarily initiated the process';
+ COMMENT ON COLUMN legitimization_processes.redirect_url
+ IS 'Where the user should be redirected for this external KYC process';
+ COMMENT ON COLUMN legitimization_processes.finished
+ IS 'set to TRUE when the specific legitimization process is finished';
+
+ CREATE TABLE kyc_attributes
+ (kyc_attributes_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)
+ REFERENCES wire_targets (wire_target_h_payto)
+ ,legitimization_process_serial_id INT8
+ REFERENCES legitimization_processes (legitimization_process_serial_id)
+ DEFAULT NULL
+ ,collection_time INT8 NOT NULL
+ ,expiration_time INT8 NOT NULL
+ ,trigger_outcome_serial INT8 NOT NULL
+ REFERENCES legitimization_outcomes(outcome_serial_id)
+ ,encrypted_attributes BYTEA NOT NULL
+ ) PARTITION BY HASH (h_payto);
+ COMMENT ON COLUMN kyc_attributes.h_payto
+ IS 'identifies the account this is about';
+ COMMENT ON COLUMN kyc_attributes.legitimization_process_serial_id
+ IS 'serial ID of the legitimization process that resulted in these attributes, NULL if the attributes are from a form directly supplied by the account owner via a form';
+ COMMENT ON COLUMN kyc_attributes.collection_time
+ IS 'when were these attributes collected';
+ COMMENT ON COLUMN kyc_attributes.expiration_time
+ IS 'when are these attributes expected to expire';
+ COMMENT ON COLUMN kyc_attributes.trigger_outcome_serial
+ IS 'ID of the outcome that was returned by the AML program based on the KYC data collected';
+ COMMENT ON COLUMN kyc_attributes.encrypted_attributes
+ IS 'encrypted JSON object with the attribute data the check provided';
-Merchant modifications
-^^^^^^^^^^^^^^^^^^^^^^
+ CREATE TABLE aml_history
+ (aml_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,h_payto BYTEA CHECK (LENGTH(h_payto)=32)
+ REFERENCES wire_targets (wire_target_h_payto)
+ ,outcome_serial_id INT8 NOT NULL
+ REFERENCES legitimization_outcomes (outcome_serial_id)
+ ,justification TEXT NOT NULL
+ ,decider_pub BYTEA CHECK (LENGTH(decider_pub)=32)
+ ,decider_sig BYTEA CHECK (LENGTH(decider_sig)=64);
-A new setting is required where the merchant backend
-can be configured for a business (default) or individual.
+ COMMENT ON TABLE aml_history
+ IS 'Records decisions by AML staff with the respective signature and free-form justification.';
+ COMMENT ON COLUMN aml_history.outcome_serial_id
+ IS 'Actual outcome for the account (included in what decider_sig signs over)';
+ COMMENT ON COLUMN aml_history.decider_sig
+ IS 'Signature key of the staff member affirming the AML decision; of type AML_DECISION';
-.. note::
+ CREATE TABLE kyc_events
+ (kyc_event_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,event_timestamp INT8 NOT NULL
+ ,event_type TEXT NOT NULL);
+
+ COMMENT ON TABLE kyc_events
+ IS 'Records of key events for statistics. Populated via triggers.';
+ COMMENT ON COLUMN kyc_events.event_type
+ IS 'Name of the event, such as account-open or sar-filed';
+
+ CREATE INDEX kyc_event_index
+ ON kyc_events(event_type,event_timestamp);
+
+
+The ``jmeasures`` JSON in the ``legitimization_measures``
+table has is of type `LegitimizationMeasures`:
+
+.. ts:def:: LegitimizationMeasures
+
+ interface LegitimizationMeasures {
+
+ // Array of legitimization measures that
+ // are to be applied.
+ measures: MeasureInformation[];
+
+ // True if the client is expected to eventually satisfy all requirements.
+ // Default (if missing) is false.
+ is_and_combinator?: boolean;
+ }
+
+
+The ``jnew_rules`` JSON in the ``legitimization_outcomes``
+table has is of type `LegitimizationRuleSet`:
+
+.. ts:def:: LegitimizationRuleSet
+
+ interface LegitimizationRuleSet {
- This still needs to be done!
+ // When does this set of rules expire and
+ // we automatically transition to the successor
+ // measure?
+ expiration_time: Timestamp;
-We introduce new ``kyc_status``, ``kyc_timestamp`` and ``kyc_serial`` fields
-into a new table with primary keys ``exchange_url`` and ``account``. This
-status is updated whenever a deposit is created or tracked, or whenever the
-mechant backend receives a ``/kyc-check/`` response from the exchange. Initially,
-``kyc_serial`` is zero, indicating that the merchant has not yet made any
-deposits and thus does not have an account at the exchange.
+ // Name of the measure to apply when the expiration time is
+ // reached. If not set, we refer to the default
+ // set of rules (and the default account state).
+ successor_measure?: string;
+
+ // Legitimization rules that are to be applied
+ // to this account.
+ rules: KycRule[];
+
+ // Custom measures that KYC rules and the
+ // ``successor_measure`` may refer to.
+ custom_measures: { "$measure_name" : MeasureInformation; };
+ }
+
+
+The ``jproperties`` JSON in the ``legitimization_outcomes`` table has is of
+type `AccountProperties`. All fields in this object are optional. The actual
+properties collected depend fully on the discretion of the exchange operator;
+however, some common fields are standardized and thus described here.
+
+.. ts:def:: AccountProperties
+
+ interface AccountProperties {
+
+ // True if this is a politically exposed account.
+ // Rules for classifying accounts as politically
+ // exposed are country-dependent.
+ pep?: boolean;
+
+ // True if this is a sanctioned account.
+ // Rules for classifying accounts as sanctioned
+ // are country-dependent.
+ sanctioned?: boolean;
+
+ // True if this is a high-risk account.
+ // Rules for classifying accounts as at-risk
+ // are exchange operator-dependent.
+ high_risk?: boolean;
+
+ // Business domain of the account owner.
+ // The list of possible business domains is
+ // operator- or country-dependent.
+ business_domain?: string;
+
+ // Is the client's account currently frozen?
+ is_frozen?: boolean;
+
+ // Was the client's account reported to the authorities?
+ was_reported?: boolean;
+
+ }
+
+
+
+KYC forms
+^^^^^^^^^
+
+The KYC SPA run by clients needs to support three TYPEs of checks. INFO is
+only about displaying the provided information, LINK is about setting up an
+exteral KYC check and redirecting there. FORM is about displaying a particular
+(HTML) form to the user and POSTing the entered information directly with the
+exchange. Here we describe the forms that must be supported:
+
+* **CHOICE**: Asks the client a multiple-choice question. The context must
+ include "choices: string[]" with a list of choices to show. Used, for
+ example, to ask a client if they are an individual or a business. The
+ resulting HTML FORM field name must be "choice" and it must be mapped to
+ strings from the choices list.
+
+* **UPLOAD**: Asks the client to upload a single file.
+ The context must include a ``validity_duration`` which
+ will be converted to the ``expiration_time`` for
+ the uploaded data. The context may furthermore include
+ ``extensions?: string[]`` with a list of allowed file extensions the client's
+ file must end with (e.g. "png", "pdf", "gif"). In the absence of this
+ context, any file may be uploaded. The context may also include a
+ ``size_limit?: Integer`` with the maximum file size in bytes that can be
+ uploaded. The resulting HTTP POST should provide at least two fields, "filename" and
+ "filedata". "filename" must be set to the basename of the original file (to
+ the extend that it is available), and "filedata" to the base64-encoding of
+ the uploaded data.
+
+As with other SPA checks, the KYC form should also show
+the description of the check.
+
+
+Merchant modifications
+^^^^^^^^^^^^^^^^^^^^^^
+
+A new setting is required where the merchant backend can be configured for a
+business (default) or individual.
+
+We introduce new ``kyc_ok``, ``aml_decision``, ``kyc_timestamp`` and
+``exchange_kyc_serial`` fields into a new table ``merchant_kyc`` with primary
+keys ``exchange_url`` and ``account_serial``. This status is updated whenever
+a deposit is created or tracked, or whenever the mechant backend receives a
+``/kyc-check/`` response from the exchange. Initially,
+``exchange_kyc_serial`` is zero, indicating that the merchant has not yet made
+any deposits and thus does not have an account at the exchange.
A new private endpoint ``/kyc`` is introduced which allows frontends to
request the ``/kyc`` status of any configured account (including with long
@@ -297,18 +2022,18 @@ either that the KYC is OK, or information (same as from the exchange endpoint)
to begin the KYC process.
The merchant backend uses the new field to remember that a KYC is pending
-(after ``/deposit``, or tracing deposits) and the SPA then shows a
+(after detection in ``taler-merchant-depositcheck``) and the SPA then shows a
notification whenever the staff is logged in to the system. The notification
can be hidden for the current day (remembered in local storage).
-The notification links to a (new) KYC status page. When opened, the KYC status
-page first re-checks the KYC status with the exchange. If the KYC is still
-unfinished, that page contains another link to begin the KYC process
-(redirecting to the OAuth 2.0 login page of the legitimization resource
-server), otherwise it shows that the KYC process is done. If the KYC is
-unfinished, the SPA should use long-polling on the KYC status on this page to
-ensure it is always up-to-date, and change to ``KYC satisfied`` should the
-long-poller return with positive news.
+The notification links to a (new) KYC status page. When opened, the KYC SPA
+first re-checks the KYC status with the exchange. If the KYC is still
+unfinished, that SPA will show forms, links or contact information to begin
+the KYC process (for example, redirecting to the OAuth 2.0 login page of the
+legitimization resource server), otherwise it shows that the KYC process is
+done. If the KYC is unfinished, the merchant SPA should use long-polling on
+the KYC status on this page to ensure it is always up-to-date, and change to
+``KYC satisfied`` should the long-poller return with positive news.
..note::
@@ -316,10 +2041,6 @@ long-poller return with positive news.
128-bit salt values (to keep ``deposits`` table small) and checks for salt
to be well-formed should be added "everywhere".
-An additional complication will arise once the exchange can trigger a KYC
-fee (402) on ``/kyc-check/``. In this case, the merchant SPA must show the QR
-code to the merchant to allow the merchant to pay the KYC fee with a wallet.
-
Bank requirements
@@ -338,8 +2059,8 @@ not on the name of the logic plugin (that we call ``$LOGIC``). Using the
configuration section, the exchange then determines the logic plugin to use.
This section describes the general API for all of the supported KYC providers,
-as well as some details of how this general API could be implemented by the logic for
-different APIs.
+as well as some details of how this general API could be implemented by the
+logic for different APIs.
General KYC Logic Plugin API
@@ -351,7 +2072,6 @@ This section provides a sketch of the proposed API for the KYC logic plugins.
- inputs:
+ provider_section (for additional configuration)
- + individual or business user
+ h_payto
- outputs:
+ success/provider-failure
@@ -480,6 +2200,40 @@ For ``/kyc-webhook/``:
to tell us anything for sure. The result is then returned.
+Types of KYC events
+^^^^^^^^^^^^^^^^^^^
+
+The ``/aml/$OFFICER_PUB/kyc-statistics`` endpoint exposes statistics for
+various KYC event types.
+
+We will initially support the use of the following types of KYC events in the
+SPA (and have a dialog to show the total number of any of these for any
+specified time range):
+
+* account-open
+* account-closed
+* voluntary-sar
+* mandatory-sar
+* pep-started
+* pep-ended
+* risky-started
+* risky-ended
+* account-frozen
+* account-unfrozen
+
+Based on these, the SPA should also be albe to show active
+statistics (for any given timestamp) on the total number of:
+
+* open accounts
+* frozen accounts
+* high-risk accounts
+* PEPs served
+
+.. note::
+
+ This can be done by simply running the queries with
+ a start time of zero and subtracting.
+
Alternatives
============
diff --git a/design-documents/024-age-restriction.rst b/design-documents/024-age-restriction.rst
index 6b1004fc..0445aa6d 100644
--- a/design-documents/024-age-restriction.rst
+++ b/design-documents/024-age-restriction.rst
@@ -1,5 +1,5 @@
-DD24: Anonymous Age Restriction Extension for GNU Taler
-#######################################################
+DD 24: Anonymous Age Restriction Extension
+##########################################
Summary
=======
@@ -29,8 +29,6 @@ optional feature that can be switched on by the exchange operator.
Requirements
============
-TODO
-
* legal requirements for merchants must allow for this kind of mechanism
@@ -55,7 +53,9 @@ protocol, that gives the minor/ward a 1/κ chance to raise the minimum age for
the new coin).
The proposed solution maintains the guarantees of GNU Taler with respect to
-anonymity and unlinkability. (TODO: refer to the paper, once published)
+anonymity and unlinkability. We have published a paper
+`Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
+with the details.
¹) Once the feature is enabled and the age groups are defined, the exchange has
to stick to that decision until the support for age restriction is disabled.
@@ -65,31 +65,32 @@ We might reconsider this design decision at some point.
Main ideas and building blocks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-The main ideas are simple:
+The main ideas are as follows:
#. The exchange defines and publishes M+1 different *age groups* of increasing
order: :math:`0 < a_1 < \ldots < a_M` with :math:`a_i \in \mathbb{N}`. The
zeroth age group is :math:`\{0,\ldots,a_1-1\}`.
-#. An **unrestricted** *age commitment* is defined as a vector of length M of
+#. An **unrestricted age commitment** is defined as a vector of length M of
pairs of Edx25519_ public and private keys on Curve25519. In other words: one
- key pair for each age group after the zeroth:
- :math:`\bigl\langle (p_1, s_1), \ldots, (p_M, s_M) \bigr\rangle`
+ key pair for each age group after the zeroth: :math:`\bigl\langle (q_1,
+ p_1), \ldots, (q_M, p_M) \bigr\rangle`. Here, :math:`q_i` are the public keys
+ (mnemonic: **q-mitments**), :math:`p_i` are the private keys.
-#. A **restricted** *age commitment* **to age group m** (or m-th age group) is
- derived from an unrestricted age commitment by removing all private keys for
- indices larger than m: :math:`\bigl\langle (p_1, s_1), \ldots, (p_m, s_m),
- \, (p_{m+1}, \perp), \ldots, (p_M, \perp )\bigr\rangle`.
- F.e. if *none* of the private keys is provided, the age commitment would be
- restricted to the zeroth age group.
+#. A **restricted age commitment** *to age group m* is derived from an
+ unrestricted age commitment by removing all private keys for
+ indices larger than m: :math:`\bigl\langle (q_1, p_1), \ldots, (q_m, p_m),
+ \, (q_{m+1}, \perp), \ldots, (q_M, \perp )\bigr\rangle`. F.e. if *none* of
+ the private keys is provided, the age commitment would be restricted to the
+ zeroth age group.
#. The act of restricting an unrestricted age commitment is performed by the
parent/ward.
#. An *age commitment* (without prefix) is just the vector of public keys:
- :math:`\vec{Q} := \langle p_1, \ldots, p_M \rangle`. Note that from
- just the age commitment one can not deduce if it was originated from an
- unrestricted or restricted age commitment (and what age).
+ :math:`\vec{Q} := \langle q_1, \ldots, q_M \rangle`. Note that from
+ just the age commitment one can not deduce if it originated from an
+ unrestricted or restricted one (and what age).
#. An *attestation of age group k* is essentially the signature to any message
with the private key for slot k, if the corresponding private key is
@@ -100,8 +101,13 @@ The main ideas are simple:
SHA256 hash value of the age commitment (i.e. the M public keys) into the
signature of the coin. So instead of signing :math:`\text{FDH}_N(C_p)` with
the RSA private key of a denomination with support for age restriction, we
- sign :math:`\text{FDH}_N(C_p, h_a)`. Here, :math:`C_p` is the EdDSA public
- key of a coin and :math:`h_a` is the hash of the age commitment.
+ sign :math:`\text{FDH}_N(C_p, h_Q)`. Here, :math:`C_p` is the EdDSA public
+ key of a coin and :math:`h_Q` is the hash of the age commitment :math:`\vec{Q}`.
+ **Note:** A coin with age restriction can only be validated when both, the
+ public key of the coin itself **and** the hash of the age commitment, are
+ present. This needs to be supported in each subsystem: Exchange, Wallet and
+ Merchant.
+
TODO: Summarize the design based on the five functions ``Commit()``,
``Attest()``, ``Verify()``, ``Derive()``, ``Compare()``, once the paper from
@@ -151,7 +157,7 @@ registering the extension ``age_restriction`` with a value type
interface ConfigAgeRestriction {
// The age groups. This field is mandatory and binding in the sense
// that its value is taken into consideration when signing the
- // denominations in `ExchangeKeysResponse`.``age_restricted_denoms``.
+ // age restricted denominations in the `ExchangeKeysResponse`
age_groups: AgeGroups;
}
@@ -193,12 +199,11 @@ in ``ExchangeKeysResponse.age_restricted_denoms``.
Age restricted denominations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If age-restriction is registered as an extension under the name
-``age_restriction``, as described above, the root-object
-``ExchangeKeysResponse`` in response to ``/keys`` MUST be extended by an
-additional field ``age_restricted_denoms``. This is an *additional* list of
-denominations that must be used during the modified ``refresh`` and ``deposit``
-operations (see below).
+If age-restriction is registered as extension ``age_restriction``, as described
+above, the root-object ``ExchangeKeysResponse`` in response to ``/keys`` MUST
+be extended by an additional field ``age_restricted_denoms``. This is an
+*additional* list of denominations that must be used during the modified
+``refresh`` and ``deposit`` operations (see below).
The data structure for those denominations is the same as for the regular ones
in ``ExchangeKeysResponse.denoms``. **However**, the following differences
@@ -242,13 +247,13 @@ changed since the given timestamp.
// Similar as for ``.denoms``, if the query parameter ``last_issue_date``
// was provided by the client, the exchange will only return the keys that
// have changed since the given timestamp.
- age_restricted_denoms: Denom[];
+ age_restricted_denoms: DenomCommon[];
//...
}
-SQL changes
+SQL schema
-----------
The exchange has to mark denominations with support for age restriction as such
@@ -286,9 +291,165 @@ NULL, but only iff the corresponding denomination (indirectly referenced via
table ``known_coins``) has ``.age_restricted`` set to true. This constraint
can not be expressed reliably with SQL.
+
Protocol changes
----------------
+Withdraw
+~~~~~~~~
+
+The withdraw protocol is affected in the following situations:
+
+- A wire transfer to the exchange (to fill a reserve) was marked by the
+ originating bank as coming from a bank account of a minor, belonging to a of
+ a specific age group, or by other means.
+- A KYC-process has been performed with the owner of a reserve and the user has
+ been identified as being a minor.
+- A Peer-to-Peer transaction was performed between customers. The receiving
+ customer's KYC result tells the exchange that the customer belongs to a
+ specific age group.
+
+In these cases, the wallet will have to perform a zero-knowledge protocol with
+exchange as part of the the withdraw protocol, which we sketch here. Let
+
+- :math:`\kappa` be the same cut-and-choose parameter for the refresh-protocol.
+- :math:`\Omega \in E` be a published, nothing-up-my-sleeve, constant
+ group-element on the elliptic curve.
+- :math:`a \in \{1,\ldots,M\}` be the maximum age (group) for which the wallet
+ has to prove its commitment.
+
+The values :math:`\kappa`, :math:`\Omega` and :math:`a` are known to the
+Exchange and the Wallet. Then, Wallet and Exchange run the following protocol
+for the withdrawal of one coin:
+
+- *Wallet*
+ 1. creates planchets :math:`C_i` for :math:`i \in \{1,\ldots,\kappa\}` as candidates for *one* coin.
+ #. creates age-commitments :math:`\vec{Q}^i` for :math:`i \in \{1,\ldots,\kappa\}` as follows:
+
+ a) creates :math:`a`-many Edx25519-keypairs :math:`(p^i_j, q^i_j)`
+ randomly for :math:`j \in \{1,\ldots,a\}` (with public keys :math:`q^i_j`),
+ #) chooses randomly :math:`(M - a)`-many scalars :math:`s^i_j` for :math:`j \in \{a+1,\ldots,M\}`,
+ #) calculates :math:`\omega^i_j = s^i_j*\Omega` for :math:`j \in \{a+1,\ldots,M \}`,
+ #) sets :math:`\vec{Q}^i := (q^i_1,\ldots,q^i_a,\omega^i_{a+1},\ldots,\omega^i_M)`
+
+ #. calculates :math:`f_i := \text{FDH}(C_i, H(\vec{Q}^i))` for :math:`i \in \{ 1,\ldots,\kappa \}`.
+ #. chooses random blindings :math:`\beta_i(.)` for :math:`i \in \{1,\ldots,\kappa\}`. The blinding functions depend on the cipher (RSA, CS).
+ #. sends :math:`(\beta_1(f_1),\ldots,\beta_\kappa(f_\kappa))` to the Exchange
+
+- *Exchange*
+ 7. receives :math:`(b_1,\ldots,b_\kappa)`
+ #. calculates :math:`F := \text{H}(b_1||\ldots||b_\kappa)`
+ #. chooses randomly :math:`\gamma \in \{1,\ldots,\kappa\}` and
+ #. signs :math:`r := b_\gamma` resulting in signature :math:`\sigma_r`
+ #. stores :math:`F \mapsto (r, \sigma_r)`
+ #. sends :math:`\gamma` to the Wallet.
+
+- *Wallet*
+ 10. receives :math:`\gamma`
+ #. sends to the Exchange the tuple :math:`\left(F, \vec{\beta}, \vec{\vec{Q}}, \vec{\vec{S}}\right)` with
+
+ - :math:`F := \text{H}(\beta_1(f_1)||\ldots||\beta_\kappa(f_\kappa))`
+ - :math:`\vec{\beta} := (\beta_1,\ldots,\beta_{\gamma-1},\bot,\beta_{\gamma+1},\ldots,\beta_\kappa)`
+ - :math:`\vec{\vec{Q}} := (\vec{Q}^1,\ldots,\vec{Q}^{\gamma-1},\bot,\vec{Q}^{\gamma+1},\ldots,\vec{Q}^\kappa)`
+ - :math:`\vec{\vec{S}} := (\vec{S}^1,\ldots,\vec{S}^{\gamma-1},\bot,\vec{S}^{\gamma+1},\ldots,\vec{S}^\kappa)`
+ with :math:`\vec{S}^i := (s^i_j)`
+
+- *Exchange*
+ 12. receives :math:`\left(F, (\beta_i), (\vec{Q}^i), (\vec{B}^i) \right)`
+ #. retrieves :math:`(r, \sigma_r)` from :math:`F` or bails out if not present
+ #. calculates :math:`b_i := \beta_i\left(\text{FDH}(\vec{Q}^i)\right)` for :math:`i \neq \gamma`
+ #. compares :math:`F \overset{?}{=} \text{H}(b_1||\ldots||b_{\gamma - 1}||r||b_{\gamma+1}||\ldots||b_\kappa)` and bails out on inequality
+ #. for each :math:`\vec{B}^i, i \neq \gamma`
+
+ i. calculates :math:`\tilde{\omega}^i_j := b^i_j * \Omega` for :math:`j \in \{a+1,\ldots,M\}`
+ #. compares each :math:`\tilde{\omega}^i_j` to :math:`q^i_j` from :math:`\vec{Q}^i = (q^i_1, \ldots, q^i_M)` and bails out on inequality
+ #. sends (blinded) signature :math:`\sigma_r` to Wallet
+
+- *Wallet*
+ 18. receives :math:`\sigma_r`
+ #. calculates (unblinded) signature :math:`\sigma_\gamma := \beta^{-1}_\gamma(\sigma_r)` for coin :math:`C_\gamma`.
+
+
+Note that the batch version of withdraw allows the withdrawal of *multiple*
+coins at once. For that scenario the protocol sketched above is adapted to
+accomodate for handling multiple coins at once -- thus multiplying the amount
+of data by the amount of coins in question--, but all with the same value of
+:math:`\gamma`.
+
+The *actual* implementation of the protocol above will have major optimizations
+to keep the bandwidth usage to a minimum and also ensure that a denomination in
+the commitment doesn't expire before the reveal.
+
+Instead of generating and sending the age commitment (array of public keys) and
+blindings for each coin, the wallet *MUST* derive the corresponding blindings
+and the age commitments from the coin's private key itself as follows:
+
+Let
+
+- :math:`s` be the master secret of the coin, from which the private key :math:`c_s`, blinding :math:`\beta` and nonce :math:`n` are derived as usual in the wallet core
+- :math:`m \in \{1,\ldots,M\}` be the maximum age (according to the reserve)
+ that a wallet can commit to during the withdrawal.
+- :math:`P` be a published constant Edx25519-public-key to which the private
+ key is not known to any client.
+
+For the age commitment, calculate:
+
+1. For age group :math:`a \in \{1,\ldots,m\}`, set
+
+.. math::
+ s_a &:= \text{HDKF}(s, \text{"age-commitment"}, a) \\
+ p_a &:= \text{Edx25519\_generate\_private}(s_a) \\
+ q_a &:= \text{Edx25519\_public\_from\_private}(p_a)
+
+2. For age group :math:`a \in \{m,\ldots,M\}`, set
+
+.. math::
+ f_a &:= \text{HDKF}(s, \text{"age-factor"}, a) \\
+ q_a &:= \text{Edx25519\_derive\_public}(P, f_a).
+
+Then the vector :math:`\vec{q} = \{q_1,\ldots,q_M\}` is then the age commitment
+associated to the coin's private key :math:`c_s`. For the non-disclosed coins,
+the wallet can use the vector :math:`(p_1,\ldots,p_m,\bot,\ldots,\bot)` of
+private keys for the attestation.
+
+Provided with the secret :math:`s`, the exchange can therefore calculate the
+private key :math:`c_s`, the blinding :math:`\beta`, the nonce :math:`n` (if
+needed) and the age commitment :math:`\vec{q}`, along with the coin's public
+key :math:`C_p` and use the value of
+
+.. math::
+
+ \text{TALER\_CoinPubHashP}(C_p, \text{age\_commitment\_hash}(\vec{q}))
+
+during the verification of the original age-withdraw-commitment.
+
+For the withdrawal with age restriction, a sketch of the corresponding database
+schema in the exchange is given here:
+
+.. graphviz::
+
+ digraph deposit_policies {
+ rankdir = LR;
+ splines = true;
+ fontname="monospace"
+ node [
+ fontname="monospace"
+ shape=record
+ ]
+
+ subgraph cluster_commitments {
+ label=<<B>age_withdraw</B>>
+ margin=20
+ commitments [
+ label="age_withdraw_id\l|<hc>h_commitment\l|amount_with_fee_val\l|amount_with_fee_frac\l|noreveal_index\l|max_age\l|<res>reserve_pub\l|reserve_sig\l|<denom>[n] denominations_serials\l|[n] h_blind_evs\l|[n] denom_sigs\l"
+ ]
+ }
+
+ commitments:res->reserves:id [ label="n:1"; fontname="monospace"];
+ commitments:denom -> denominations:id [ label="n:1"; fontname="monospace"] ;
+ }
+
+
Refresh - melting phase
~~~~~~~~~~~~~~~~~~~~~~~
@@ -439,9 +600,9 @@ The object ``ContractTerms`` is extended by an optional field
``minimum_age`` that can be any integer greater than 0. In reality
this value will not be smaller than, say, 8, and not larger than, say, 21.
-.. ts:def:: ContractTerms
+.. ts:def:: DD24ContractTerms
- interface ContractTerms {
+ interface DD24ContractTerms {
...
// If the order requires a minimum age greater than 0, this field is set
@@ -528,19 +689,14 @@ satisfy any minimum age requirement.
Changes in the Wallet
^^^^^^^^^^^^^^^^^^^^^
-TODO.
-
-* choosing age-restriction during withdrawal coins from denominations with
- support for age restriction.
-* Define protocol to pass denominations to child/ward.
-
+A wallet implementation SHOULD support denominations with age restriction. In
+that case it SHOULD allow to select an age group as upper bound during
+withdraw.
Alternatives
============
-TODO.
-
* ID-based systems
* credit/debit card based systems
@@ -548,10 +704,7 @@ TODO.
Drawbacks
=========
-TODO.
-
* age groups, once defined, are set permanently
-* age restricted coins are basically shared between ward and warden.
Also discuss:
@@ -571,9 +724,8 @@ We had some very engaged discussions on the GNU Taler `mailing list <taler@gnu.o
* `Age-restriction is about coins, not currencies <https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`__
+* The published paper: `Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
-The upcoming paper on anonymous age-restriction for GNU Taler from Özgür Kesim
-and Christian Grothoff will be cited here, once it is published.
.. _Edx25519:
diff --git a/design-documents/025-withdraw-from-wallet.rst b/design-documents/025-withdraw-from-wallet.rst
index a00d7a1b..d37fc04b 100644
--- a/design-documents/025-withdraw-from-wallet.rst
+++ b/design-documents/025-withdraw-from-wallet.rst
@@ -1,5 +1,5 @@
-DD25: Withdraw coins manually starting from the wallet
-######################################################
+DD 25: Withdraw coins manually starting from the wallet
+#######################################################
Summary
=======
@@ -29,7 +29,7 @@ This screen the user will be able to select the currency from a list of known
currencies, switch the exchange, go to a page to add an exchange and define an
amount to be withdraw.
-.. image:: ../wallet-start-manual-withdraw.svg
+.. image:: ../images/wallet-start-manual-withdraw.svg
:width: 800
@@ -39,7 +39,7 @@ Success
Here the user will see the account details where it needs to send money to
complete the withdrawal.
-.. image:: ../wallet-confirm-withdraw.svg
+.. image:: ../images/wallet-confirm-withdraw.svg
:width: 800
Transaction history
diff --git a/design-documents/026-refund-fees.rst b/design-documents/026-refund-fees.rst
index b6a31fe0..b2d03066 100644
--- a/design-documents/026-refund-fees.rst
+++ b/design-documents/026-refund-fees.rst
@@ -1,5 +1,9 @@
-DD26: Refunds and Fees
-######################
+DD 26: Refunds and Fees
+#######################
+
+.. note::
+
+ This is implemented (as of 2023-09-15).
Summary
=======
diff --git a/design-documents/027-sandboxing-taler.rst b/design-documents/027-sandboxing-taler.rst
index c671e55d..1a09baf4 100644
--- a/design-documents/027-sandboxing-taler.rst
+++ b/design-documents/027-sandboxing-taler.rst
@@ -1,5 +1,5 @@
-DD27: Sandboxing all the Taler services
-#######################################
+DD 27: Sandboxing all the Taler services
+########################################
.. note::
diff --git a/design-documents/028-deposit-policies.rst b/design-documents/028-deposit-policies.rst
new file mode 100644
index 00000000..1bdf4801
--- /dev/null
+++ b/design-documents/028-deposit-policies.rst
@@ -0,0 +1,207 @@
+DD 28: Deposit Policy Extensions
+################################
+
+.. note::
+
+ This is Work-In-Progress.
+
+Summary
+*******
+
+We will propose here a plugable mechanism in the exchange to support deposits
+with associated policy. An exchange can enable support for such policies via
+configuration.
+
+The inital set of policy extensions that an exchange might provide consists of
+
+Merchant refunds
+ Merchant can grant customers refundable payments. In this case, the
+ amount of the deposit is put into escrow by the exchange for a certain
+ period until which the customer can claim a refund.
+
+Escrowed payments
+ A trustor puts coins into escrow with the exchange. It can be claimed
+ by a beneficiary until a certain deadline, when the claim is signed by
+ both, the beneficiary's and the trustor's keys.
+
+Brandt-Vickrey auctions
+ A bidder puts coins into escrow with the exhange in order to
+ participate in an Brandt-Vickrey auction. The deposit confirmation is
+ proof to the seller for the escrow and contains a hash of the auction
+ meta-data and a deadline. After successfull execution of the auction,
+ the seller provides a valid transcript to the exchange from which the
+ exchange learns which bidder(s) won the auction for which prices. It
+ then transfers the amounts from the winners' coins to the seller. In
+ case of a timeout and for all losing bidders, the coins can be
+ refreshed.
+
+The policies shall be implemented as *extensions* to the exchange (see
+:doc:`006-extensions`).
+
+Motivation
+**********
+
+GNU Taler's initial set of API's (withdraw, deposit, refresh) support most
+payment situations in which customers pay for goods and services within an
+otherwise unconditioned transaction. (A notable exception from this the
+ability to provide refunds, which will be re-factored into a policy extension).
+
+However, in many payments depend on additional conditions to be met. GNU Taler
+already supports payments with age restriction applied, but there are other
+scenarious that we want to support.
+
+Our aim is to provide an API for extensions of GNU Taler that implement
+particular policies and policy-handling for payments (also called *conditioned
+payments*).
+
+
+Background and Requirements
+***************************
+
+TODO
+
+Proposed Solution
+*****************
+
+TODO, explain:
+
+- C-structs for policy extensions (esp. the handlers)
+- Naming conventions for policy extensions
+- Deadlines and -handling
+- Typical choreography of a deposit with policy and its fulfillment
+
+
+API-Endpoints of the Exchange
+=============================
+
+TODO
+
+
+
+Database-schema
+===============
+
+TODO: Description
+
+.. graphviz::
+
+ digraph deposit_policies {
+ rankdir = LR;
+ splines = false;
+ fontname="monospace"
+ node [
+ fontname="monospace"
+ shape=record
+ ]
+
+ subgraph cluster_deposits {
+ label=<<B>deposits</B>>
+ margin=20
+ deposits [
+ label="...|<ref>policy_details_id (null)\l|...|timestamp\l|..."
+ ]
+ }
+
+ subgraph cluster_policy_details {
+ label=<<B>policy_details</B>>
+ margin=20
+ policy_details [
+ label="<id>id\l|<hash>policy_hash_code (unique)\l|deadline\l|commitment (amount)\l|accumulated_total (amount)\l|fee (amount)\l|transferable (amount)\l|fulfillment_state\l|<fid>fulfillment_id (null)\l"
+ ]
+ }
+
+ subgraph cluster_policy_fulfillments {
+ label=<<B>policy_fulfillments</B>>
+ margin=20
+ rank=min;
+ policy_fulfillments [
+ label="<id>id\l|proof\l|timestamp\l|<codes>policy_hash_codes (blob)\l"
+ ]
+ }
+
+ deposits:ref->policy_details:id [ label="n:1"; fontname="monospace" ];
+ policy_details:fid->policy_fulfillments:id [label="n:1"; fontname="monospace" ];
+ }
+
+
+The field ``policy_hash_codes`` in table ``policy_fulfillments`` is a binary
+blob that consists of the concatenation of the sorted
+``policy_details.policy_hash_code`` entries from all policies that are fulfilled by
+this proof.
+
+
+Policy Fulfillment States
+=========================
+
+The fulfillment of a policy can be in one of the following five states:
+
+Ready
+ The policy is funded and ready. The exchange is waiting for a proof of
+ fulfillment to arrive before the deadline.
+
+Insufficient
+ The policy lacks funding, that is ``accumulated_total`` <
+ ``commitment``, but has otherwise been accepted. Funding can be
+ continued by calling ``/deposit`` or ``/batch-deposit`` with more coins
+ and the same policy details.
+
+Success
+ The policy is provably fulfilled. The amounts for payout, fees and
+ refresh are transfered/can be claimed. Note that a policy fulfillment
+ handler can change the values for the amounts for payout, fees and
+ refresh.
+
+Timeout
+ The policy has timed out. The amounts for payout and refresh are
+ transfered/can be claimed.
+
+Failure
+ The policy is in an failure state. Payouts and refreshes are
+ blocked, timeouts are ignored.
+
+
+
+Invariants
+^^^^^^^^^^
+
+The following invariants need to be fulfilled and be checked by the auditor:
+
+- The fulfillment state of a policy is **Insufficient** IF AND ONLY IF the
+ amount in ``policy_details.commitment`` is equal or larger than the amount in
+ ``policy_details.accumulated_total``.
+
+- The sum of amounts in ``policy_details.fee`` and
+ ``policy_details.transferable`` MUST be equal or less than the amount in
+ ``policy_details.accumulated_total``.
+
+- The amount in ``policy_details.accumulated_total`` MUST be equal to the total
+ sum of contributions of the individual coins of the deposits that reference
+ this policy.
+
+- Each hash code encoded in ``policy_fulfillments.policy_hash_codes`` MUST
+ refer to an existing ``policy_details.hash_code`` AND its ``.fulfillment_id``
+ MUST point to the same ``policy_fulfillments.id``.
+
+- Conversely: If a ``policy_details.fulfillment_id`` points to an entry in
+ ``policy_fulfillment``, the ``policy_details.policy_hash_code`` MUST be
+ present in that entry's ``.policy_hash_codes``.
+
+
+
+Alternatives
+============
+
+TODO
+
+Drawbacks
+=========
+
+TODO
+
+
+Discussion / Q&A
+================
+
+TODO
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/028-proof-of-escrow.rst b/design-documents/028-proof-of-escrow.rst
deleted file mode 100644
index 402ddd2c..00000000
--- a/design-documents/028-proof-of-escrow.rst
+++ /dev/null
@@ -1,128 +0,0 @@
-DD28: Proof of escrow in the exchange
-#####################################
-
-.. note::
-
- This design document is currently a draft, it
- does not reflect any implementation decisions yet.
-
-
-Summary
-=======
-
-We propose here an extension to the exchange: An escrow service that can be
-used by other, separate trading services f.e. for online auctions.
-
-
-Motivation
-==========
-
-Certain types of trade, such as auctions or trades with long phases of
-negotiation, require a proof of escrow of money as a guarantee in order to
-participate or perform the trade.
-
-We want to extend GNU Taler to support trades such as anonymous sealed-bid
-auctions. While the auction service will be a separate (from the exchange)
-entity, behaving in parts like a normal merchant in the GNU Taler sense, the
-exchange itself can provide an escrow services.
-
-Background and Requirements
-===========================
-
-An escrow service is a intermediary between two parties and must trusted by
-both. In the GNU Taler payment system, this role is per definition played by
-the exchange for buyers and sellers during purchases. The auditor controls the
-exchange and is also a mediator between buyers and selles.
-
-The role of the exchange can be therefore extended to the specific needs of
-escrow. In contrast to purchase/deposit, for escrow, particular coins are
-locked, but not spent. This prohibits their spending for a specific timespan
-and until a valid order of release is provided.
-
-However, in the context of auctions, we want the parties to be able to verify
-the fairness of the participants. For example, a seller of goods during an
-auction shall only be able to release the money for the winning bidder and not
-for the others. On the other hand, both, sellers and bidders should be able
-provide evidence to the exchange and auditor if the other party wasn't honest,
-f.e. if the winning bidder hasn't released the money.
-
-
-
-Proposed Solution
-=================
-
-We propose a the following endpoints
-
-
-- ``POST /escrows/$ESCROW_ID/register``: Register an escrow account under the
- provided EdDSA public key ``$ESCROW_ID``. The required parameters are:
-
- - a starttime
- - an endttime
- - an interval ``[m, M]`` of minimum and maximum amounts, where ``M`` can also be ``∞``.
-
-- ``GET /escrows/$ESCROW_ID``: Return the terms of the escrow and the current
- list of depositor IDs.
-
-- ``POST /escrows/$ESCROW_ID/deposit/$DEPOSIT_ID``: Deposit a specific amount
- with a particular list of coins. The required parameters are:
-
- - the amount ``a`` to be deposited (must lie in ``[m, M]``)
- - the list of coins to be used for the deposit (the sum of the values must be
- at least ``a``)
- - signatures from each coin over the SHA512 hash of the amount ``a``, the
- ``$DEPOSIT_ID`` and the ``$ESCROW_ID``.
-
- The ``$DEPOSIT_ID`` is the SHA512 hash over all the coins.
-
-
-- ``POST /escrows/$ESCROW_ID/claim/$DEPOSIT_ID``: The owner of the private key for
- ``$ESCROW_ID`` can claim the deposited coins. It has to provide
-
- - the particular amount ``a'`` to be claimed
- - the list of coins to be claimed from the deposit
- - signatures over the SHA512 hash of the amount ``a'``, ``$ESCROW_ID`` and
- the ``$DEPOSIT_ID``, signed by each coin
-
-The following diagram gives an overview of the flow:
-
-.. image:: _svgs/escrow-flow.svg
-
-
-When the ``endtime`` of an escrow has arrived on the exchange, an amount ``a``
-of a deposit of amount ``b ≥ a`` can be claimed by the originator of the escrow
-account. Only *one* successful claim can be made for a particular escrow
-account. After a claim has been made, the remaining coins in the remaining
-deposits are released by the exchange. If no claim is made within a specific
-time interval after ``endtime``, all coins of *all* deposits are released.
-
-The ``GET /escrows/$ESCROWS_ID`` allows to depositors and auditors to confirm
-their deposit. It also allows depositors to compare the list with their
-mentioning at other services, such as the list of bidders in an online auction.
-
-TODOs: Specify
-
-- data structures
-- signatures
-- return values
-- errors
-- terms of contracts
-- default time intervals
-
-Alternatives
-============
-
-TODO
-
-Drawbacks
-=========
-
-TODO
-
-
-Discussion / Q&A
-================
-
-TODO
-
-(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/029-mobile-ui.rst b/design-documents/029-mobile-ui.rst
index ddb5b0ca..fef5ae08 100644
--- a/design-documents/029-mobile-ui.rst
+++ b/design-documents/029-mobile-ui.rst
@@ -1,5 +1,5 @@
-DD29: Mobile P2P UI
-###################
+DD 29: Mobile P2P UI
+####################
Summary
=======
@@ -23,7 +23,7 @@ Requirements
Proposed Solution
=================
-.. image:: ../wallet-mobile-overview.svg
+.. image:: ../images/wallet-mobile-overview.svg
:width: 800
diff --git a/design-documents/030-offline-payments.rst b/design-documents/030-offline-payments.rst
index f2b3027e..d4b4bae5 100644
--- a/design-documents/030-offline-payments.rst
+++ b/design-documents/030-offline-payments.rst
@@ -1,5 +1,5 @@
-DD30: Offline payments
-######################
+DD 30: Offline payments
+#######################
Summary
=======
diff --git a/design-documents/031-invoicing.rst b/design-documents/031-invoicing.rst
index 419c299d..0fcc88fd 100644
--- a/design-documents/031-invoicing.rst
+++ b/design-documents/031-invoicing.rst
@@ -1,12 +1,10 @@
-DD31: Invoicing
-###############
+DD 31: Invoicing
+################
Summary
=======
-This document proposes new endpoints to support invoicing,
-that incidentally also address the long-standing tipping
-reserve expiration problem.
+This document proposes new endpoints to support invoicing.
Motivation
@@ -36,10 +34,7 @@ Requirements
* Reasonable UX and overall design impact.
* Wallets may want to pay for the reserve with coins
- (reserve fresh, not created via bank transfer), while
- tipping merchants likely want to pay from the reserve
- balance itself. So both styles of payment should be
- supported.
+ (reserve fresh, not created via bank transfer).
Unclear in the current proposal are:
@@ -62,12 +57,10 @@ charge the ``account_fee``, bump the number of open purses threshold in the
``reserves`` table and stop auto-closing of the reserve. This will ensure that
the users can withdraw the reserve balance into their wallet even after a
longer time period. This helps if the invoice is paid after a significant
-delay, and also addresses the unwanted tipping reserve closure
-problem. Introduce a way to force an immediate closure of a reserve, allowing
+delay. Introduce a way to force an immediate closure of a reserve, allowing
P2P reserve from invoices to be send to a bank account (this allows a wallet
to be used for convenient invoicing and not strictly require the wallet to
-receive the funds) and also allowing the user to recover funds from a tipping
-reserve after tips are no longer issued.
+receive the funds).
The solution needs three new tables for:
diff --git a/design-documents/032-auctions.rst b/design-documents/032-brandt-vickrey-auctions.rst
index 0f144988..18660289 100644
--- a/design-documents/032-auctions.rst
+++ b/design-documents/032-brandt-vickrey-auctions.rst
@@ -1,5 +1,5 @@
-DD32: Auctions
-##############
+DD 32: Brandt-Vickrey Auctions
+##############################
Summary
=======
@@ -9,6 +9,9 @@ funds held in escrow by a Taler exchange. It is taking major inspiration from
Markus Teich's Master's thesis on "Implementing Privacy Preserving Auction
Protocols".
+The support for these types of auctions will be in the form of an extension for
+a deposit policy.
+
Motivation
==========
@@ -98,10 +101,10 @@ the auction:
as paying any other merchant and thus out of scope for this
design document.
- * An exchange that supports the ``auction`` extension and holds
- the funds for bids in escrow for the duration of the auction.
- Upon completion of the auction, the exchange pays the seller
- from the auction's winner and refunds the other bidders.
+ * An exchange that supports the ``policy_vickrey_auction`` extension and
+ holds the funds for bids in escrow for the duration of the auction. Upon
+ completion of the auction, the exchange pays the seller from the auction's
+ winner and refunds the other bidders.
* An auditor that verifies that the exchange made the payments
correctly.
@@ -140,19 +143,20 @@ bid.
The auction operator then runs the auction protocol with all participants
until conclusion. Once the winner and price have been determined, the auction
-operator POSTs the resulting transcript to a new ``/extensions/auction``
-endpoint of the exchange. Here, the extension-specific logic stores the
-transcript in its database (in a new table) and then simulates the auction
-again (using libbrandt), again determining the winner and price. The
-extension configuration (and thus ``/keys``) may stipendulate some fee(s)
-charged by the exchange to handle the ``/extensions/auction`` request. The
-fees should be covered by the seller. We note that the transcript inherently
-contains the deposit confirmations originally issued by the exchange for the
-auction. So, the exchange can identify all of the coins that were escrowed (it
-should also double-check that the coins were escrowed for the correct
-auction). It then refunds the bids from the loosing bidders, pays the price
-to the seller from the winner (minus auction fee), and partially refunds the
-winner the difference between the escrowed amount and the winning bid.
+operator POSTs the resulting transcript to a new
+``/extensions/policy_brandt_vickrey_auction`` endpoint of the exchange. Here,
+the extension-specific logic stores the transcript in its database (in a new
+table) and then simulates the auction again (using libbrandt), again
+determining the winner and price. The extension configuration (and thus
+``/keys``) may stipendulate some fee(s) charged by the exchange to handle the
+``/extensions/policy_brandt_vickrey_auction`` request. The fees should be
+covered by the seller. We note that the transcript inherently contains the
+deposit confirmations originally issued by the exchange for the auction. So,
+the exchange can identify all of the coins that were escrowed (it should also
+double-check that the coins were escrowed for the correct auction). It then
+refunds the bids from the losing bidders, pays the price to the seller from
+the winner (minus auction fee), and partially refunds the winner the difference
+between the escrowed amount and the winning bid.
.. note::
@@ -174,6 +178,80 @@ transactions.
The auditor of the exchange can again simulate the auction protocol and can
thus confirm that the exchange's ultimate transactions were correct.
+Transcripts
+^^^^^^^^^^^
+
+A transcript of a Brandt-Vickrey auction is the JSON encoding of an object of
+type `BrandtVickreyAuctionTranscript`.
+
+ .. ts:def:: BrandtVickreyAuctionTranscript
+
+ // This structure defines the transcript of an auction of Brandt-Vickrey kind.
+ interface BrandtVickreyAuctionTranscript {
+ // The auction definition.
+ auction: BrandtVickreyAuction;
+
+ // The public keys of the bidders, in Crockford Base32 encoding.
+ bidders: EddsaPublicKey[];
+
+ // Signatures of the auction in Crockford Base32 encoding.
+ // One signature per bidder.
+ signatures: EddsaSignature[];
+
+ // List of policy hash codes that identify policy details associated with
+ // each bidder. Those codes were generated by the policy extension
+ // policy_brandt_vickrey_auction during the deposit of coins for this
+ // auction.
+ policy_hash_codes: HashCode[];
+
+ // The transcript of all messages received by the seller.
+ transcript: BrandtVickreyAuctionMessage[];
+
+ // Optionally, the seller can provide the winners it had calculated.
+ winners?: BrandtVickreyAuctionWinner[];
+
+ // The signature over the hash of this JSON object, without the
+ // key ``sig`` and in normalized form, basically over
+ // H(auction, bidders, signatures, transcripts, winners?)
+ // It is signed by the private key that corresponds to the public key
+ // in `BrandtVickreyAuction`.``pubkey``.
+ // This signature is in Crockford Base32 encoding.
+ sig: EddsaSignature;
+ }
+
+
+ .. ts:def:: BrandtVickreyAuctionMessage
+
+ interface BrandtVickreyAuctionMessage {
+ // The index of the bidder into the
+ // `BrandtVickreyAuctionTranscript`.``bidders`` array.
+ bidder: number;
+
+ // The raw message in Crockford Base32 encoding.
+ msg: string;
+
+ // The signature over the message. The signature is in Crockford Base32
+ // encoding. It must be signed by the private key corresponding to the
+ // bidder's public key in `BrandtVickreyAuctionTranscript`.``bidders``.
+ sig: EddsaSignature;
+ }
+
+
+
+ .. ts:def:: BrandtVickreyAuctionWinner
+
+ interface BrandtVickreyAuctionWinner {
+ // The index of the bidder into the
+ // `BrandtVickreyAuctionTranscript`.bidder array.
+ bidder: number;
+
+ // The index of the winning price into the
+ // `BrandtVickreyAuction`.prices array.
+ price_idx: number;
+
+ // The winning price
+ price: Amount;
+ }
Alternatives
diff --git a/design-documents/033-database.rst b/design-documents/033-database.rst
new file mode 100644
index 00000000..ee37cc32
--- /dev/null
+++ b/design-documents/033-database.rst
@@ -0,0 +1,152 @@
+DD 33: Database Schema and Versioning
+#####################################
+
+Summary
+=======
+
+This document describes how we version database schema in GNU Taler
+and enable migrations.
+
+
+Motivation
+==========
+
+As Taler evolves, it will be necessary to add new tables, change existing
+tables, modify indices and make other changes to the database schema. As
+production systems exist, existing data will need to be migrated to the new
+schema, and we need to do this in a systematic way. While certain major
+changes may require radical or manual interventions, we should have a
+systematic way of dealing with minor or modest modifications to the schema.
+
+
+Requirements
+============
+
+* The approach should be language-independent
+* Schema migration should be reasonably performant
+* Schema migration must be usable, and in particular safe to use for
+ operators without significant risk
+* We need to support key database features we might need to use,
+ such as partitioning or sharding
+
+
+Proposed Solution
+=================
+
+We use the "versioning.sql" system to store the current set of patches that
+have been applied to the database so far in a "_v" schema. This allows us to
+quickly test which version of the database we are on and which migrations may
+still need to be applied.
+
+For each component, all tables are placed into a SCHEMA named after the
+component.
+
+We then use a set of numbered SQL files that create, alter or drop tables and
+indices (like exchange-0001.sql, exchange-0002.sql, ...) to setup the
+database. However, some setups need additional arguments, such as the number
+of partitions. Those setups are then NOT performed explicitly, but by creating
+stored procedures and registering those stored procedures in a general global
+"master" table to be called from the main setup logic with arguments in a
+particular order under certain conditions.
+
+When setting up a database, there is no point in incrementally defining
+ordinary stored procedures that are used at runtime (not the ones to setup the
+tables we talked about above). Thus, all of the stored procedures used by the
+runtime system are placed in a file "procedures.sql" which is loaded
+last. This makes changes to stored procedures particularly easy, as one simply
+edits "procedures.sql". Loading "procedures.sql" also does not change "_v".
+
+A "drop.sql" file is created that DROPs the main SCHEMA of the component and
+additionally unregisters all patches from the "_v" schema. The drop script
+is run during tests to fully reset the database.
+
+Exchange details
+^^^^^^^^^^^^^^^^
+
+The exchange uses "exchange_tables" to create the master
+table mentioned above. In "exchange_tables", entries are
+executed in the order of the "table_serial_id". Each
+entry has a "name", which is the name of the affected table
+(or at least the prefix in the case of partitioned or sharded
+tables). The "version" field stores which "exchange-XXXX.sql"
+file setup the respective table entry, but is for now mostly
+for internal documentation. The "action" defines both the
+condition under which to run a function. Specifically,
+actions can be:
+
+* create --- run on the master table and each shard; used to create or alter the main table
+* constrain --- run only on the partitions/shards, or on master if there are no partitions; used to setup constraints like uniqueness that only apply to the lowest levels of the table
+* master -- run only on the master table; used to setup triggers and other constraints that only apply to the master table
+* foreign -- run only on the master table and only if there are no partition; used to setup foreign key constraints that are not supported on partitioned or sharded tables
+
+The "partitioned" field indicates that this table is partitioned and instructs the functions to create partitions (or shards)
+for this table.
+
+The "by_range" field indicates if the table is partitioned by
+range, which prevents automatic generation of partitions as
+is done if partitioned by hash.
+
+The "finished" field is initially false, but set to TRUE once the respective
+function has been executed.
+
+The main "do_create_tables" function triggers the unfinished actions
+registered in the "exchange_tables" table. It is given arguments to control
+the number of partitions, the use of partitions and (in the future) the use of
+sharding.
+
+The individual actions use helper functions ("create_partitioned_table",
+"comment_partitioned_table" and "comment_partitioned_column") to facilitate
+the creation of tables and associated comments. These functions are used so
+that we can only define the schema or comment once, and have it applied to
+tables with names and creation syntax that changes slightly if we use shards
+or partitions.
+
+Some additional logic will be needed to deal nicely with
+sharding (which is currently not supported), as with
+sharing we will need to detach shards, migrate shards, and
+re-attach shards. So this will require additional stored
+procedures to support operations on shards.
+
+
+Merchant details
+^^^^^^^^^^^^^^^^
+
+The merchant does not (yet) need any type of master table, as we do not
+(yet) use any kind of sharding or partitioning. There are also no
+stored procedures being used by the backend. Hence, it is simply the
+"versioning.sql"-controlled table creation/alteration sequence
+(merchant-0001.sql, etc.) and the "drop.sql" to reset everything.
+
+
+Alternatives
+============
+
+* We might want to consider storing more meta-data
+ in the database, such as the use of sharding, the
+ names of the shard servers, or even just the number
+ of partitions.
+
+* We could require dumping out the old database and
+ loading it in via some explicit importer during each
+ migration; having migration logic in C would enable more
+ powerful migrations, but dumping and reloading the entire
+ database would also be more expensive. It would have the
+ advantage of basically having the old database around in
+ case of migration trouble, so the cost disadvantage might
+ not be so bad (as admins are likely to make a backup anyway).
+ OTOH, doing the migration with the equivalent of a
+ taler-auditor-sync would require quite a bit more code
+ than the simple ALTER/CREATE statements in an SQL file.
+
+
+Drawbacks
+=========
+
+* not exactly trival logic
+* some complexity to implement
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/034-wallet-db-migration.rst b/design-documents/034-wallet-db-migration.rst
new file mode 100644
index 00000000..e8cdb8c5
--- /dev/null
+++ b/design-documents/034-wallet-db-migration.rst
@@ -0,0 +1,78 @@
+DD 34: Considerations for Wallet Database Migrations
+####################################################
+
+Summary
+=======
+
+This design document discusses considerations for wallet database migrations.
+
+Motivation
+==========
+
+The schema of the GNU Taler Wallet database is evolving over time, either
+because new features are added or because bugs are fixed.
+
+Requirements
+============
+
+* Migrations may not result in data loss and must be automatic.
+* Our schema migration must be compatible with how IndexedDB works. This means that we can't
+ do arbitrary schema migrations at any time, but need to increment the IndexedDB database version
+ every time we add/remove/change an object store or index.
+
+Proposed Solution
+=================
+
+The schema of the wallet database is described in code in
+https://git.taler.net/wallet-core.git/tree/packages/taler-wallet-core/src/db.ts#n1959
+(``walletStoresV1``). This schema description is used to initialize and upgrade the
+database automatically.
+
+In IndexedDB terminology, the wallet has two databases:
+
+1. The ``"taler-wallet-meta"`` stores metadata about the current major-version database
+2. The major-version database (currently ``"taler-wallet-main-v9"`` stores the
+ actual data of the wallet.
+
+This indirection allows major database migrations to be safe despite the
+limitations of IndexedDB. The computation that is allowed during an IndexedDB
+migration is very limited. By migrating to a completely new database, we can
+keep around the old database until we're sure that the migration has succeeded
+and, if required, push new code to fix migration errors.
+
+We have three different mechanisms to introduce changes to the database:
+
+1. Major migrations. These migrations introduce a new major-version database and must manually
+ migrate the data from the previons major-version database. This migration should be
+ added in ``db.ts#openTalerDatabase``.
+ Major migrations should be used **very** seldomly. It can make sense to implement them
+ as a backup cycle, i.e. implement a backup export from the old version, upgrade to
+ the latest backup version and then re-import into the new major-version database.
+2. Minor schema migrations: These migrations add or remove object stores or indexes.
+ They are done by adding new elements to the schema descriptions **and** specifying
+ the ``versionAdded`` attribute. This causes an IndexedDB upgrade transaction
+ to be executed.
+3. Fixups. Fixups change data within or between minor schema versions and
+ contain arbitrary code to make changes to object stores. They are usually used
+ when a new mandatory field is added to an existing object store or some data
+ format changes. Fixups are also useful to retroactively fix bugs
+ introduced by previously deployed wallet versions.
+ They must be added to ``db.ts#walletDbFixups``
+
+Alternatives
+============
+
+* Per-object versioning instead of using IndexedDB minor versions
+* Always use the backup mechanism to upgrade the database
+
+ * Would be overkill for minor migrations
+
+Drawbacks
+=========
+
+N/A.
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/035-regional-currencies.rst b/design-documents/035-regional-currencies.rst
new file mode 100644
index 00000000..3d62dcc3
--- /dev/null
+++ b/design-documents/035-regional-currencies.rst
@@ -0,0 +1,196 @@
+DD 35: Regional currencies
+##########################
+
+Summary
+=======
+
+This design document discusses how the GNU Taler wallet can support both
+regional currency deployments and official fiat currencies.
+
+Motivation
+==========
+
+Digital cash in a Taler wallet always requires some kind of trust anchor that
+backs its value, be it either an exchange directly or an auditor that vouches
+for one or more exchanges.
+
+The currency code or symbol (EUR, USD, $, ...) is thus not enough to know what
+a particular wallet balance really means. It also matters what exchange or
+auditor is behind it. Thus the wallet needs some mechanism to allow users to
+distinguish between an official deployment of a currency (say EUR in Europe) or
+deployments of regional currencies. Regional currencies might have coinciding
+currency names for different incompatible deployments (say, MANA to buy Club
+Mate drinks at different hacker events with completely separate and independent
+Taler deployments).
+
+Requirements
+============
+
+* Official deployments for fiat currencies should be supported without clutter
+* Regional currencies should be easy to use as well
+* It must be easy to integrate regional/official currencies with the existing
+ Taler auditor/exchange structure
+* Wallet users should be able to see disagregated balance between global currencies
+ and regional currencies supported by different exchanges even if the currency
+ name is equal.
+* Merchants should be able to accept regional and global currencies based on the
+ supported exchange list.
+
+Proposed Solution
+=================
+
+Users usually do not want to see and verify the auditor/exchange URL for their
+digital cash. The wallet thus needs to support some form of "scope" for
+currencies that indicates the trust anchor for particular amounts and balances.
+
+The scope of a balance/amount gives the user additional information about the
+meaning and trust anchor of a balance/amount. The scope is always local and
+contextual to the wallet. Depending on the configuration, the wallet can show
+different scope information for the exact same coins.
+
+A balance is in exactly one of three scopes:
+
+1. Global. An amount in the global scope is by default displayed without
+ any additional qualification on the currency.
+2. Exchange-scoped. An exchange-scoped amount is always displayed with the
+ prettified base URL of the exchange.
+3. Auditor-scoped. An auditor-scoped amount is always displayed with the
+ prettified base URL of the auditor. When multiple auditors are applicable,
+ either the one with the lexically smallest base URL is chosen, or the
+ one that the user/wallet has configured as the prefered one for the currency.
+
+Whenever the wallet reports an amount, scope information should be
+present in the same message with the following format:
+
+.. code:: TypeScript
+
+ type ScopeInfo =
+ | { kind: "global" }
+ | { kind: "exchange", baseUrl: string }
+ | { kind: "auditor", baseUrl: string };
+
+Prettified base URLs
+^^^^^^^^^^^^^^^^^^^^
+
+The base URLs should be rendered without the ``https://`` and without
+trailing slashes:
+
+* ``https://exchange.demo.taler.net/`` would be rendered as
+ ``exchange.demo.taler.net``.
+
+* ``http://ex1.demo.taler.net/`` would be rendered as
+ ``http://ex1.demo.taler.net``.
+
+* ``https://ex2.demo.taler.net/foo/bar/`` would be rendered as
+ ``ex2.demo.taler.net/foo/bar``.
+
+
+Currency Scope Info in the Wallet
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+For each configured currency code, the wallet should store following information:
+
+* List of global-scope exchanges and currencies for the currency. When this is an empty list,
+ the currency will always be shown with exchange/auditor scope info.
+
+Examples
+^^^^^^^^
+
+The following example shows how a wallet would render balances with
+global-scope EUR (i.e. a user would expect these to be "official" EUR that can
+be used with multiple vendors in Europe), two exchange-scoped MANA balances and
+one auditor-scoped MANA balance.
+
+.. code:: none
+
+ Balances:
+
+ 1.5 EUR
+
+ 3 MANA ([exchange-icon] exchange.leipzig.ccc.de)
+ 3.3 MANA ([exchange-icon] exchange.berlin.ccc.de)
+ 5 MANA ([auditor-icon] auditor.ccc.it]
+
+Scope information in requests for payments
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In wallet-to-merchant payments, the merchant specifies which exchanges and
+auditors the merchant accepts. It is desirable that the wallet renders the
+scope information for a requested amount in a similar way that a balance amount
+would be rendered.
+
+The amount should always be shown in the scope that is compatible with the
+merchant and that the wallet holds the highest amount in.
+
+Let's say a wallet has "auditor.ccc.it" as the global-scope auditor for MANA and holds
+mana audited by this auditor. A merchant accepts MANA from this auditor as well
+as from the exchange "mana.my-hackerspace.it".
+
+A payment request could then be rendered like this:
+
+.. code:: none
+
+ Summary: Club Mate (5x)
+ Amount: MANA:50
+
+
+If a wallet (by a non-Italian hacker) would not have "auditor.ccc.it" as the
+global-scope auditor for MANA, it would show as:
+
+.. code:: none
+
+ Summary: Cart #123 at Foomerchant
+ Amount: MANA:123 ([auditor-icon] auditor.ccc.it)
+
+ Other currencies supported by the merchant:
+ [exchange-icon] mana.my-hackerspace.it
+
+The last part should probably be hidden by default. There might be nicer ways to render
+this, such as some hoverable (?) icon after the amount that shows details about what currencies the merchant
+accepts.
+
+Wallet-core API for scope management
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* ``listGlobalCurrencyExchanges`` lists all ``(currency, exchangeUrl, exchangePub)`` triples
+ where funds are considered to be in global scope (i.e. non-regional).
+* ``listGlobalCurrencyAuditors`` lists all ``(currency, auditorUrl, auditorPub)`` triples
+ where funds are considered to be in global scope (i.e. non-regional).
+* ``addGlobalCurrencyExchange`` and ``removeGlobalCurrencyExchange`` adds/removes a ``(currency, exchangeUrl, exchangePub)``
+* ``addGlobalCurrencyAuditor`` and ``removeGlobalCurrencyAuditor`` adds/removes a ``(currency, auditorUrl, auditorPub)``
+
+
+Implementation Breakdown
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+* we need test currencies in each scope
+* wallet-core needs to add scope information to balances response
+ and various other requests
+* the UI needs to render those
+* wallet-core needs to expose new requests to manage currency information
+* the UI needs to allow the user to manage currency information
+
+
+Alternatives
+============
+
+* Completely distinguish regional currencies (non-three-letter currency code) and official currencies
+ (three letter ISO currency code). This does not help with overlapping regional currency names,
+ we can't expect them to be unique.
+
+Drawbacks
+=========
+
+* API and UI changes are required, but they can be made in an incremental and
+ backwards-compatible manner.
+* Scope information could be attached to the currency code.
+ That's a bad idea, because the scope information is totally local to the wallet.
+
+Discussion / Q&A
+================
+
+* Should we allow users to customize how scopes are displayed (e.g. an alias
+ instead of the full prettified base URL?)
+* Do we still need the auditor/exchange URLs with this proposal?
+* How does this affect the insufficient balance details? Should we also take scopes
+ into account here?
diff --git a/design-documents/036-currency-conversion-service.rst b/design-documents/036-currency-conversion-service.rst
new file mode 100644
index 00000000..92d88499
--- /dev/null
+++ b/design-documents/036-currency-conversion-service.rst
@@ -0,0 +1,97 @@
+DD 36: Currency conversion service
+##################################
+
+Summary
+=======
+
+This document explains the design of the currency conversion
+service. Such service enables customers to spend their fiat
+currency to buy Taler coins in a regional currency, and enables
+merchants to cash-out from the regional currency to fiat.
+
+Motivation
+==========
+
+The conversion service (CCS) is fundamental for a regional
+currency and is missing in the Taler/Libeufin ecosystem.
+
+Definitions
+===========
+
+*Fiat-issuer* is the fiat bank account that belongs to the regional currency
+issuer; typically, such bank account belongs to one association that runs the
+infrastructure. This bank account is hosted at the "fiat bank". *Regio-issuer*
+is the bank account that belongs to the local
+currency issuer but hosted at the bank that generates the regional currency.
+Such bank is also called "circuit bank". *Regio-exchange* is the bank account
+that belongs to the Taler exchange and that is hosted at the circuit bank.
+*Fiat-target* is a bank account hosted in the same currency of fiat-issuer
+and that belongs to a customer who initiated a cash-out operation. *Regio-user*
+is a bank account hosted at the circuit bank that is different from regio-issuer.
+*Fiat-customer* is a bank account hosted in the same currency of fiat-issuer,
+typically owned by customers that want to withdraw Taler coins in the regional
+currency.
+
+Requirements
+============
+
+* CCS must not impact the libeufin-nexus structure.
+* CCS must trigger Taler withdrawls every time a customer buys the
+ regional currency ('cash-in' operation).
+* CCS must offer cash-out operations.
+* CCS should react as soon as possible to cash-in and cash-out operations.
+* CCS must show its state to administrators and offer management tools.
+* CCS must link every fiat-side of a cash-out to its regional currency
+ counterpart. In particular, because every cash-out starts with a
+ payment *P* from regio-user to regio-issuer and ends with another
+ payment *Q* from fiat-issuer to fiat-target, CCS must link P and Q.
+
+Proposed Solution
+=================
+
+The following design assumes that CCS is coded in libeufin-bank and that
+libeufin-bank and libeufin-nexus share the same database with separate
+schemas. The solution relies on SQL triggers to atomically synchronise
+cash-in and cash-out operations between the two schemas.
+
+SQL triggers and conversion operations
+--------------------------------------
+
+Libeufin-bank controls the conversion support and sets up or removes
+conversion SQL triggers when necessary. In order for the SQL triggers to
+perform the conversion operations, the configurable rates/fees are stored
+in the database and the conversion operations are performed using stored
+SQL procedures. The SQL triggers and conversion procedures are stored in
+the libeufin-bank schema.
+
+Cash-out operation
+------------------
+
+Libeufin-bank learns instantly about a cash-out operation, because it's
+*the* service offering such feature. Therefore, as soon as a cash-out
+operation gets TAN-confirmed, libeufin-bank performs a wire transfer from
+regio-user to regio-issuer by specifying the amount without any rates/fees
+applied. Along the same database transaction, a SQL trigger store the
+*instructions* of another payment *P* from fiat-issuer to fiat-target,
+but this time **with** the cash-out rates/fees.
+
+Asynchronously, a libeufin-nexus background task picks P and sends it to
+the fiat bank. Finally, fiat bank conducts P and fiat-target receives the
+wanted amount. The same libeufin-nexus background task should also retry
+previous payments like P that failed to be submitted to fiat bank.
+
+Cash-in operation
+-----------------
+
+A cashin-in operation starts as soon as the customer sends a fiat
+payment from fiat-customer to fiat-issuer.
+
+The libeufin-nexus component is responsible to query the fiat bank
+via EBICS every X seconds. X should match the tightest interval allowed
+by the bank.
+
+When libeufin-nexus registers an incoming payment on fiat-issuer in the
+database, a SQL trigger applies the **current** cash-in rates/fees and
+performs a wire transfer from regio-issuer to regio-exchange. Libeufin-bank
+makes the exchange aware via the Taler Wire Gateway API and from now on,
+the system proceeds like it always did in Taler.
diff --git a/design-documents/037-wallet-transactions-lifecycle.rst b/design-documents/037-wallet-transactions-lifecycle.rst
new file mode 100644
index 00000000..535e4ff8
--- /dev/null
+++ b/design-documents/037-wallet-transactions-lifecycle.rst
@@ -0,0 +1,1049 @@
+DD 37: Wallet Transaction Lifecycle
+###################################
+
+.. contents:: Table of Contents
+ :depth: 2
+
+Summary
+=======
+
+This design doc discusses the lifecycle of transactions in wallet-core.
+
+Motivation
+==========
+
+The transactions in wallet-core all should have an associated state machine. All transactions
+should have some common actions that work uniformly across all transactions.
+
+Requirements
+============
+
+The underlying state machine should make it obvious what interactions
+are possible for the user. The number of possible user interactions
+in any state should be small.
+
+Proposed Solution
+=================
+
+
+Common States
+-------------
+
+The following states apply to multiple different transactions. Only pending
+and aborting have transaction-specific sub-states, denoted by ``state(substate)``.
+
+``pending``: A pending transaction waits for some external event/service.
+The transaction stays pending until its change on the wallet's material balance
+is finished. Any pending state can be suspended and resumed.
+
+There are some other distinctions for pending transactions:
+
+* long-polling vs. exponential backoff: A pending transaction is either waiting
+ on an external service by making a long-polling request or by repeating requests
+ with exponential back-off.
+* ``lastError``: A pending transaction is either clean (i.e. the network interaction
+ is literally active in transmission or the external service successfully
+ communicated that it is not ready yet and this is perfectly normal)
+ or has a ``lastError``, which is a ``TalerErrorDetails``
+ object with details about what happened during the last attempt to proceed
+ with the transaction.
+
+``done``: A transaction that is done does not require any more processing. It also
+never has a ``lastError`` but is considered successful.
+
+``dialog``: A transaction requires input from the user.
+
+``aborting``: Similar to a pending transaction, but instead of taking active steps to
+complete the transaction, the wallet is taking active steps to abort it. The ``lastError``
+indicates errors the wallet experienced while taking active steps to abort the transaction.
+
+``aborted``: Similar to ``done``, but the transaction was successfully aborted
+instead of successfully finished. It will have the information of when (timestamp) it was
+aborted and in which pending sub-state the abort action was initiated. Also, we can
+include more information information relevant to the transaction in ``abortReason``
+
+``suspended``: Similar to a ``aborted`` transaction, but the transaction was could be
+resumed and may then still succeed.
+
+``suspended-aborting``: Network requests or other expensive work
+to abort a transaction is paused.
+
+``failed``: Similar to ``done``, but the transaction could not be completed or
+possibly not even be aborted properly. The user may have lost money. In some
+cases, a report to the auditor would make sense in this state.
+
+``expired``: Similar to ``failed``, but the failure was caused by a timeout.
+
+``deleted``: A ``deleted`` state is always a final state. We only use this
+state for illustrative purposes. In the implementation, the data associated
+with the transaction would be literally deleted.
+
+
+Common Transitions
+------------------
+
+Transitions are actions or other events.
+
+``[action:retry]``: Retrying a transaction *(1.)* stops ongoing long-polling
+requests for the transaction *(2.)* resets the retry timeout *(3.)* re-runs the
+handler to process the transaction. Retries are always possible the following
+states: ``pending(*)`` and ``aborting(*)``.
+
+.. attention::
+
+ Should we show the retry timeout in the UI somewhere? Should we show it in dev mode?
+
+ SEBASJM: Since the wallet will retry anyway, maybe is better if we replace the "retry"
+ button with a "try now" button and a side text "retrying in xxx seconds".
+
+ CG: Instead of a side text, this *might* make a good mouse-over hint for
+ a "retry" (or "try now") button. I would not make this overly visible with
+ side-text as the information is not that important. The text should also be
+ "retrying next at XXX" using an absolute time XXX --- otherwise the UI would
+ be way too busy recomputing/updating all of these strings: Using an absolute time,
+ we only have to redraw anything once a retry actually happened. Given that
+ retries should basically never be > 24h (we can impose a hard cap), the absolute
+ time can just be in the format HH:MM:SS (without day).
+
+``[action:suspend]``: Suspends a pending transaction, stopping any associated
+network activities, but with a chance of trying again at a later time. This
+could be useful if a user needs to save battery power or bandwidth and an
+operation is expected to take longer (such as a backup, recovery or very large
+withdrawal operation).
+
+``[action:resume]``: Suspended transactions may be resumed, placing them back
+into a pending state.
+
+``[action:abort]``: Aborting a transaction either directly stops processing for the
+transaction and puts it in an ``aborted`` state, or starts the necessary steps to
+actively abort the transaction (e.g. to avoid losing money) and puts it in an
+``aborting`` state.
+
+``[action:fail]``: Directly puts an ``aborting`` or ``pending`` transaction into a
+``failed`` state. May result in an ultimate loss of funds (beyond fees) to the
+user and thus requires additional consent.
+
+``[action:delete]``: Deleting a transaction completely deletes the transaction
+from the database. Depending on the type of transaction, some of the other
+data *resulting* from the transaction might still survive deletion. For
+example, deleting a withdrawal transaction does not delete already
+successfully withdrawn coins. Deleting is only safe (no money lost) on initial
+and final states (failed, aborted, done).
+
+Whether aborting, deleting or suspending are possible depends on
+the transaction type, and usually only one of the four choices should be
+offered.
+
+
+.. image:: ../images/transaction-common-states.png
+
+
+Boxed labels indicate an end state in which there is no network activity and
+hence no need to give the user a way to abort or suspend the activity. The
+circle indicates the initial state. Ovals are states with network activity.
+
+Blue arrows are used for user-triggered actions (via UI buttons). Purple
+arrows are used to indicate externally triggered actions. Black arrows
+without labels are used for the normal successful path. Red arrows indicate
+failure paths.
+
+
+Common pending sub-states
+-------------------------
+
+During the pending state the transaction can go through several sub-states before
+reaching a final state. Some of this sub-states are shared between different
+transaction types:
+
+``kyc``: The transaction cannot proceed because the user needs to actively
+finish a KYC process. The wallet should show the user a hint on how to
+start the KYC process.
+
+``aml``: The transaction can't proceed because the user needs to wait for the
+exchange operator to conclude an AML investigation by the staff at the
+exchange. There are two AML substates. In the substate ``pending`` the user
+is not expected to take any action and should just wait for the investigation
+to conclude. In the substate ``frozen`` the staff at the exchange decided that
+the account needed to be frozen. The user should contact the exchange
+provider's customer service department and seek resolution (possibly through
+the courts) to avoid losing the funds for good.
+
+
+Transaction Type: Withdrawal
+----------------------------
+
+* ``pending(bank-register-reserve)``
+
+ Initial state for bank-integrated withdrawals. The wallet submits the reserve public key
+ and selected exchange to the bank (via the bank integration API). Note that if the
+ user aborts at this stage, we do not know if the bank is in the confirmation stage,
+ so we must still *try* to abort the transaction at the bank.
+
+ * ``[processed-success] => pending(bank-confirm-transfer)``
+ * ``[processed-error] => failed``: On permanent errors (like 404 for the withdrawal operation),
+ the wallet gives up.
+ * ``[action:abort] => aborting(bank)``
+
+* ``pending(bank-confirm-transfer)``
+
+ The wallet waits until the bank has confirmed the withdrawal operation;
+ usually the user has to complete a 2FA step to *approve* that the money is
+ wired to the chosen exchange. Note that the user's *approve* action is done
+ in the bank's user interface and not the wallet's user interface. The wallet
+ internally merely *polls* for the success or failure of the approve action.
+ The wallet **may** occasionally (after some initial delay, especially on
+ failures from the bank-poll to return any result) long-poll for the reserve
+ status and, if successful, may then directly jump to
+ ``pending(withdraw-coins)`` if the reserve is filled even if the poll at
+ the bank did not return success or failure.
+
+ * ``[bank-poll-success] => pending(exchange-wait-reserve)``
+ * ``[bank-aborted] => aborted``: Bank denied the operation.
+ * ``[exchange-poll-success] => pending(withdraw-coins)``: Optional
+ short-cut transition. Exchange was faster than the bank.
+ * ``[action:abort] => aborting(bank)``
+
+* ``aborting(bank)``
+
+ The user aborted the withdraw operation in the wallet. The wallet must now
+ try to signal the bank that the wire transfer should no longer be performed.
+ Note that it is possible that the bank registration never succeeded (if the
+ user aborted us during ``pending(bank-register-reserve)``) and in this case
+ we get an ``unknown transaction`` failure here. It is also theoretically
+ possible that the user approved the transaction in the bank while
+ simultaneously aborting in the wallet. In this case, we transition to
+ ``suspended(exchange-wait-reserve)`` (treating the ``abort`` action as a ``suspend``
+ action).
+
+ * ``[processed-success] => aborted``
+ * ``[processed-error(already-confirmed)] => suspended(exchange-wait-reserve)``: We
+ keep a transaction history entry reminding the user about when the already
+ wired funds will be returned.
+ * ``[processed-error(unknown-transaction)] => failed``
+
+* ``suspended(exchange-wait-reserve)``
+
+ State where funds were (presumably) wired to the exchange but the wallet
+ was asked to not proceed with the withdraw, but we still resume.
+
+ In this state, the wallet should show to the user that the money from the
+ withdrawal reserve will be sent back to the originating bank account after
+ ``$closing_delay``. Note that the ``resume`` action should be disabled
+ after ``$closing_delay``.
+
+ * ``[action:delete] => deleted``
+ * ``[action:resume] => pending(exchange-wait-reserve)``
+
+* ``pending(exchange-wait-reserve)``
+
+ Initial state for manual withdrawals. Here, the wallet long-polls the
+ exchange for the reserve status, waiting for the wire transfer to arrive
+ at the exchange.
+
+ * ``[exchange-poll-success] => pending(withdraw-coins)``
+ * ``[action:suspend] => suspended(exchange-wait-reserve)``
+
+* ``pending(withdraw-coins)``
+
+ State where we are finally withdrawing the actual coins. Depending on
+ the AML and KYC thresholds, we may at any time transition into a
+ holding pattern on the AML or KYC checks of the exchange.
+
+ It is possible that the selected denominations expired.
+ In that case, the wallet will re-select denominations.
+
+ * ``[processed-success] => done``
+ * ``[processed-kyc-required] => pending(kyc)``
+ * ``[processed-aml-required] => pending(aml)``
+ * ``[reserve-expired] => expired(reserve)``
+ * ``[action:suspend] => suspended(withdraw-coins)``
+
+* ``pending(kyc)``
+
+ State where the user needs to provide some identity data to pass a KYC
+ check. The wallet only shows the user the link for starting the KYC
+ process and long-polls the exchange in anticipation of the user
+ completing the KYC requirement.
+
+ * ``[poll-success] => pending(withdraw-coins)``
+ * ``[action:suspend] => suspended(kyc)``
+
+* ``suspended(kyc)``
+
+ State where the user needs to provide some identity data to pass a KYC
+ check, but the long-polling was explicitly stopped. The user can
+ choose to resume or delete.
+
+ * ``[action:delete] => deleted``
+ * ``[action:resume] => pending(kyc)``
+
+* ``pending(aml)``
+
+ State where the wallet needs to wait for completion of an AML process by an
+ AML officer of the exchange. The wallet shows that the AML process is
+ blocking progress. The message shown should distinguish between a mere
+ pending AML process and an AML freezing decision in terms of the message
+ shown to the user. If the AML decision is pending at the exchange, he user
+ should be urged to simply wait. If the funds were frozen, the wallet
+ informs the user that their funds were frozen due to an AML decision. The
+ user is urged to contact the exchange operator's AML department out-of-band.
+ In any case, the wallet long-polls for the AML decision to be made or change
+ (possibly at a lower frequeny in case of a freeze).
+
+ * ``[poll-success] => pending(withdraw-coins)``
+ * ``[action:suspend] => suspended(aml)``
+
+* ``suspended(aml)``
+
+ State where the user needs to await some AML decision by the exchange.
+ The long-polling was explicitly stopped. The user can choose to resume or delete.
+
+ * ``[action:delete] => deleted``
+ * ``[action:resume] => pending(aml)``
+
+* ``suspended(withdraw-coins)``
+
+ In this state, the wallet should show how much money arrived into the wallet
+ and the rest of the money will be sent back to the originating bank account
+ after ``$closing_delay``. Note that the ``resume`` action should be
+ disabled after ``$closing_delay``.
+
+ * ``[action:delete] => deleted``
+ * ``[action:resume] => pending(exchange-wait-reserve)``
+
+* ``done``
+
+ The withdrawal operation is complete.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+ Withdrawn coins are preserved, as is reserve information for recoup.
+ So this mostly removes the entry from the visible transaction history.
+ Only once all coins were spent, the withdraw is fully removed.
+
+
+.. image:: ../images/transaction-withdrawal-states.png
+
+
+Transaction Type: Payment to Merchant
+-------------------------------------
+
+* ``pending(claim-proposal)``
+
+ We received a ``pay`` URI. Download (claim) the proposal from the merchant. Can fail if
+ the proposal was already claimed by someone else. If repurchase detection
+ tells us that we already paid for this product, we go immediately to
+ ``failed(repurchase)`` state for this transaction, but with a side-effect of
+ transitioning the UI into a ``pending(repurchase-session-reset)`` on a
+ *different* transaction (which before was in ``done``).
+
+ A ``failed(repurchase)`` transaction will eventually be GCed (=deleted)
+ automatically.
+
+ * ``[error:already-claimed] => failed(already-claimed)`` -- the proposal was
+ already claimed by someone else.
+ * ``[error:invalid-proposal] => failed(invalid-proposal)`` -- the merchant provided a
+ proposal that is invalid (e.g. malformed contract
+ terms or bad signature).
+
+* ``dialog(merchant-order-proposed)``
+
+ Let the user accept (or refuse) the payment.
+
+ * ``[action:pay-accept] => pending(submit-payment)``
+ * ``[action:pay-refuse] => ``aborted(refused)`` -- The user explicitly
+ decided not to proceed (at least not with this wallet).
+ * ``[expired] => failed(expired)`` -- The offer has expired before the user made any
+ decision. Note that we should use this transition at
+ least a few seconds before the offer *actually* expires to avoid
+ encountering an expiration during ``pending(submit-payment)`` in most
+ real-world scenarios. Basically, we should prevent last-second payments to
+ be event attempted client-side.
+
+ The ``failed(expired)`` might be automatically deleted upon GC.
+
+* ``pending(submit-payment)``
+
+ Submit coin-by-coin (or in bulk groups) until payment is complete.
+
+ * ``[action:abort] => aborting(pay-incomplete)`` -- The user explicitly decided to
+ abort the process while the payment was happening. Note that if the
+ payment was already completed (and hence the merchant refuses any
+ refunds), it is theoretically possible that pressing the abort button will
+ nevertheless end up in a ``pending(auto-refund)`` state (and subsequently
+ a ``done`` state) instead!
+ * ``[success] => pending(auto-refund)`` -- Upon receiving confirmation from
+ the merchant that the purchase was completed.
+ * ``[error(insufficient balance)] => aborting(pay-incomplete)`` This transition
+ happens if we detect double-spending and our balance is not sufficient
+ after the double-spending. It is also conceivable (but should be rare)
+ that this transition happens because the offer expired.
+
+* ``pending(auto-refund)``
+
+ The payment succeed. We remain in this state as long as an auto-refund-check
+ is active. If auto refunds are not enabled, we immediately continue to
+ ``done``.
+
+ * ``[no-auto-refund] => done``
+ * ``[timeout] => done`` -- This happens when the auto refund set by the
+ contract expired.
+ * ``[long-poll:refund] => aborting(pay-incomplete)`` -- An auto-refund was detected.
+ * ``[action:abort] => done`` -- The user may explicitly request to abort the
+ auto-refund processing (for example to enable subsequent deletion before
+ the auto-refund delay expires).
+
+* ``aborting(pay-incomplete)``
+
+ The wallet should interact with the merchant to request
+ a refund on the incomplete payment.
+
+ * ``[success] => aborted(pay-incomplete)``
+ * ``[already-paid] => done``
+
+* ``aborted(refunded)``
+
+ The purchase ended with a (partial) refund. The state (and UI) should show
+ the specific provenance of the state, which may include an insufficient
+ balance (due to double-spending being detected during payment), and one or
+ more partial or full refunds.
+
+ * ``[action:delete] => deleted``
+
+* ``done``
+
+ The purchase is completed.
+
+ * ``[action:delete] => deleted``
+ * ``[repurchase] => pending(rebind-session)``: Another offer
+ became pending for this product and we need to update the session so
+ that the user does not have to buy it again.
+ * ``[check-refunds]` => pending(check-refunds)``: New refunds
+ might be available for this purchase.
+
+* ``pending(check-refund)``
+
+ New refunds might be available for this purchase.
+ This state must only be entered *after* the payment has successfully
+ completed. It is not relevant for auto-refunds or refunds for incomplete
+ payments.
+
+ * ``[refunds-checked] => pending(user-new-refund)`` --- New
+ refund(s) are available, user needs to confirm.
+ * ``[refunds-checked] => done`` --- Refunds were checked, but no
+ new refunds are available.
+ * ``[action:stop-refund-query] => done`` ---
+ This action would usually only be offered when the state is pending
+ with errors. It stops the refund query, but the payment of course
+ is left intact.
+
+* ``pending(rebind-session)``
+
+ The wallet should reset the associated session for the already purchased
+ (digital) item.
+
+ * ``[success] => done``
+ * ``[action:abort] => done`` -- User aborted the session reset.
+
+* ``deleted``
+
+ When a payment is deleted, associated refund transactions are always deleted
+ with it.
+
+.. image:: ../images/transaction-payment-states.png
+
+
+Transaction Type: Refund
+------------------------
+
+A refund is a pseudo-transaction that is always associated with a merchant
+payment transaction.
+
+* ``pending(accept)``
+
+ Initial state for a refund.
+
+ * ``[processed-error] => failed``: we received a permanent failure (such as money already wired to the merchant)
+
+* ``failed``
+
+ The refund failed permanently.
+
+.. image:: ../images/transaction-refund-states.png
+
+
+Transaction Type: Refresh
+-------------------------
+
+This is about refreshes that are triggered via coin expiration or as part of
+getting change after making a payment. In the first case, the refresh
+transaction is forever shown as a separate transaction in the history unless
+it did not affect the wallet balance (in which case we hide it). In the second
+case, the refresh transaction is folded into the payment transaction upon
+completion, so that the balance changes are included in the fees of the
+transaction that caused us to obtain change.
+
+If we have to adjust the refund amount (because a coin has fewer funds on it
+than we expect) the transaction only shows the changes due to the refresh, and
+we merely adjust the current balance of the wallet but without giving any
+justification (as we cannot give details we do not have). So this will look
+the same as if the double-spending transaction had been deleted by the user.
+
+* ``pending``
+
+ A refresh operation is pending.
+
+ * ``[processed-success] => done``
+ * ``[action:suspend] => suspended``
+ * ``[failed] => failed``
+
+* ``suspended``
+
+ A refresh operation was suspended by the user.
+
+ * ``[action:resume] => pending``
+
+* ``done``
+
+ The refresh operation completed.
+
+ * ``[action:delete] => deleted``
+
+* ``failed``
+
+ The refresh operation failed. The user lost funds.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+ All memory of the refresh operation is lost, but of course the resulting
+ fresh coins are preserved.
+
+.. image:: ../images/transaction-refresh-states.png
+
+
+Transaction Type: Deposit
+-------------------------
+
+* ``pending(deposit)``
+
+ Initial state for deposit transactions.
+ We deposit the amount coin-by-coin (or in bulk groups) until deposit is completed.
+
+ * ``[action:suspend] => suspended(submit-deposit)``
+ * ``[processed-success] => pending(track)``
+ * ``[processed-failure] => aborting(refund)``
+
+* ``suspended(deposit)``
+
+ The user suspended our ongoing deposit operation.
+
+ * ``[action:resume] => pending(deposit)``
+ * ``[action:abort] => aborting(refund)``
+
+* ``pending(track)``
+
+ All the coins were submitted, waiting to be wired.
+
+ * ``[poll-success] => done``
+ * ``[poll-accepted-kyc] => pending(kyc)``
+ * ``[poll-accepted-aml] => pending(aml)``
+ * ``[action:abort] => aborting(refund)``
+
+* ``pending(kyc)``
+
+ Exchange requires KYC before making the wire transfer.
+
+ * ``[long-poll:kyc] => done``
+ * ``[action:suspend] => suspended(kyc)``
+
+* ``suspended(kyc)``
+
+ The user suspended us while we were waiting for KYC to be finished.
+
+ * ``[action:resume] => pending(kyc)``
+
+* ``pending(aml)``
+
+ Exchange requires AML before making the wire transfer.
+
+ * ``[long-poll:aml] => done``
+ * ``[action:suspend] => suspended(aml)``
+
+* ``suspended(aml)``
+
+ The user suspended us while we were waiting for AML to be finished.
+
+ * ``[action:resume] => pending(aml)``
+
+* ``aborting(refund)``
+
+ Wallet should try to get the deposited amount back from the exchange (by submitting a refund).
+
+ * ``[action:suspend] => suspended(refund)``
+ * ``[processed-success] => aborting(refresh)``
+ * ``[processed-error] => aborting(refresh)``: Even if the refund attempt failed, maybe the deposit failed as well and we can still succeed with a refresh.
+
+* ``suspended(refund)``
+
+ The user suspended us while we were trying to get a refund.
+
+ * ``[action:resume] => aborting(refund)``
+
+* ``aborting(refresh)``
+
+ * ``[action:suspend] => suspended(refresh)``
+ * ``[processed-success] => aborted``
+ * ``[processed-error] => failed``
+
+* ``suspended(refresh)``
+
+ The user suspended us while we were trying to do the refresh.
+
+ * ``[action:resume] => aborting(refresh)``
+
+* ``aborted``
+
+ The operation was aborted, some funds may have been lost (to fees or deposited anyway).
+
+ * ``[action:delete] => deleted``
+
+* ``done``
+
+ The deposit operation completed.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+ All memory of the deposit operation is lost.
+
+.. image:: ../images/transaction-deposit-states.png
+
+
+Transaction Type: Peer Push Debit
+---------------------------------
+
+Peer Push Debit transactions are created when the user wants to transfer money
+to another wallet.
+
+States and transitions:
+
+* ``pending(purse-create)``
+
+ The wallet is creating a purse. Initial state.
+
+ * ``[process-success] => pending(ready)``: The wallet has created the purse.
+ * ``[process-failure] => aborting(refund)``: The purse creation failed.
+ * ``[action:suspend] => suspended(purse-create)``: The user suspended the operation.
+
+* ``suspended(purse-create)``
+
+ * ``[action:resume] => pending(purse-create)``: The user resumed the operation.
+ * ``[action:abort] => aborting(refund)``: The user aborted the operation.
+
+* ``pending(ready)``
+
+ In this state, the user can send / show the ``taler://`` URI or QR code to somebody else.
+
+ * ``[action:abort] => aborting(delete-purse)``: The user aborts the P2P payment. The wallet tries to reclaim money in the purse.
+ * ``[purse-timeout] => aborting(refresh)``: The other party was too slow and the purse has now expired.
+ * ``[poll-success] => done``: The other party has accepted the payment.
+ * ``[poll-error] => aborting(refresh)``: The exchange claims that there is a permanent error regarding the purse. (FIXME(CG): not clear that this is the best transition! Could also go to ``aborting(refund)`` or ``aborting(delete-purse)``; best choice may depend on the specific error returned.)
+
+* ``aborting(delete-purse)``
+
+ The wallet is deleting the purse to prevent the receiver from merging it and to reclaim the funds in it.
+
+ * ``[processed-success] => aborting(refresh)``: The purse was deleted successfully, and refunded coins must be refreshed.
+ * ``[processed-failed(already-merged)] => done``: The other party claimed the funds faster that we were able to abort.
+ * ``[processed-failed(other)] => aborting(refresh)``: The exchange reports a permanent error. We still try to refresh.
+ * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds.
+
+* ``aborting(refund)``
+
+ We abandon the purse that was never fully funded and ask for the deposited coins to be refunded.
+
+ * ``[processed-success] => aborting(refresh)``: After the refund, we still need to refresh the coins.
+ * ``[processed-failure] => aborting(refresh)``: The refund failed, we still try to refresh the coins.
+ * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds.
+
+* ``aborting(refresh)``
+
+ * ``[processed-success] => aborted``: Refresh group finished. Aborting was successful, money was reclaimed.
+ * ``[processed-failed] => failed``: Refresh group failed to complete with a permanent error.
+ * ``[action:fail] => failed``: The user explicitly asked us to give up and accepted the possible loss of funds.
+
+* ``done``
+
+ The transfer was successful.
+
+ * ``[action:delete] => deleted``
+
+* ``aborted``
+
+ The transfer was aborted. Except for fees, the money was recovered.
+
+ * ``[action:delete] => deleted``
+
+* ``failed``
+
+ The transfer failed. Money was lost. Unless on a forced abort, we should probably complain to the auditor.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+ All memory of the push debit operation is lost.
+
+.. image:: ../images/transaction-push-debit-states.png
+
+
+Transaction Type: Peer Push Credit
+----------------------------------
+
+Peer Push Credit transactions are created when the user accepts to be paid via
+a ``taler://pay-push`` URI.
+
+States and transitions:
+
+* ``pending(download)``
+
+ Wallet read the taler:// URI and is downloading the contract details for the user.
+
+ * ``[processed-success] => pending(user)``: Contract can be shown to the user.
+ * ``[action:suspend] => suspended(download)``: User suspended the operation.
+
+* ``suspended(download)``
+
+ The download of the purse meta data was suspended by the user.
+
+ * ``[action:resume] => pending(download)``
+
+* ``pending(user)``
+
+ User needs to decide about accepting the money.
+
+ * ``[action:accept] => pending(merge)``
+ * ``[timeout] => failed``: User took too long to decide.
+
+* ``pending(merge)``
+
+ * ``[processed-success] => pending(withdraw)``: Merging the reserve was successful.
+ * ``[kyc-required] => pending(merge-kyc)``: User must pass KYC checks before the purse can be merged.
+ * ``[timeout] => failed``: The purse expired before we could complete the merge.
+ * ``[failure] => failed``: The merge failed permanently.
+ * FIXME(CG): do we want to allow suspending here?
+
+* ``pending(merge-kyc)``
+
+ We cannot merge the purse until passing a KYC check.
+ The user is shown a hint where to begin the KYC
+ process and the wallet long-polls on the KYC status.
+
+ * ``[poll-success] => pending(withdraw)``
+ * ``[action:suspend] => suspended(kyc)``
+ * ``[timeout] => failed``: The purse expired before we could complete the merge.
+
+* ``suspended(merge-kyc)``
+
+ We cannot merge the purse until passing a KYC check,
+ and that check was suspended by the user.
+
+ * ``[action:resume] => pending(kyc)``
+ * ``[timeout] => failed``: The purse expired before we could complete the merge.
+
+* ``pending(withdraw)``
+
+ The wallet is withdrawing coins from the reserve that was filled by merging
+ the purse.
+
+ * ``[kyc-required] => pending(withdraw-kyc)``
+ * ``[aml-required] => pending(withdraw-aml)``
+ * ``[withdraw-failure] => failed``
+ * ``[withdraw-success] => done``
+ * ``[action:suspend] => suspended(withdraw)``
+
+* ``suspended(withdraw)``
+
+ The user requested the withdraw operation to be suspended.
+
+ * ``[action:resume] => pending(withdraw)``
+
+* ``pending(withdraw-kyc)``
+
+ We cannot withdraw more coins until passing a KYC check.
+ The user is shown a hint where to begin the KYC
+ process and the wallet long-polls on the KYC status.
+
+ * ``[poll-success] => pending(withdraw-coins)``
+ * ``[action:suspend] => suspended(withdraw-kyc)``
+
+* ``suspended(withdraw-kyc)``
+
+ We cannot withdraw from the reserve until passing a KYC check,
+ and that check was suspended by the user.
+
+ * ``[action:resume] => pending(withdraw-kyc)``
+
+* ``pending(withdraw-aml)``
+
+ We cannot withdraw more coins until AML rules are satisfied.
+ The user is shown a hint as to the AML status (pending or frozen).
+
+ * ``[poll-success] => pending(withdraw-coins)``
+ * ``[action:suspend] => suspended(withdraw-aml)``
+
+* ``suspended(withdraw-aml)``
+
+ We cannot withdraw from the reserve until AML rules are satisfied,
+ and the status check was suspended by the user.
+
+ * ``[action:resume] => pending(withdraw-aml)``
+ * ``[action:delete] => deleted``
+
+* ``failed``
+
+ The operation failed. Details are shown to the user. The money from the purse eventually goes to the sender (or some other wallet that merged it).
+
+ * ``[action:delete] => deleted``
+
+* ``done``
+
+ The operation succeeded.
+
+ * ``[action:delete] => deleted``: No money will be lost, the withdrawn coins will be kept
+
+* ``deleted``
+
+ All memory of the push credit operation is lost.
+
+.. image:: ../images/transaction-push-credit-states.png
+
+
+Transaction Type: Peer Pull Credit
+----------------------------------
+
+TODO: Also specify variant where account reserve needs to be created / funded first (Note: post 1.0-feature).
+
+* ``pending(purse-create)``
+
+ The wallet is creating a purse. Initial state.
+
+ * ``[process-success] => pending(ready)``: The wallet has created the purse.
+ * ``[process-failure] => deleted``: The purse creation failed. We only show a transient error.
+ * ``[action:abort] => deleted``: The user aborted the operation.
+
+* ``pending(ready)``
+
+ In this state, the user can send / show the ``taler://`` URI or QR code to
+ somebody else.
+
+ * ``[action:abort] => aborting(delete-purse)``: The user aborts the P2P payment.
+ * ``[purse-timeout] => aborted``: The other party was too slow and the purse
+ has now expired.
+ * ``[poll-success] => pending(withdraw)``: The other party has made the payment.
+ * ``[poll-error] => aborting(delete-purse)``: The exchange claims that there
+ is a permanent error regarding the purse. We should try to delete it.
+
+* ``aborting(delete-purse)``
+
+ We are cleaning up the purse after the operation failed or was aborted by
+ the user.
+
+ * ``[failure:already-merged] => pending(withdraw)``: Too late to abort, the
+ other side already paid the invoice.
+ * ``[process-success] => aborted``: The wallet has deleted the purse.
+ * ``[failure:other] => failed``: The purse deletion failed; we are
+ nevertheless done.
+ * ``[action:fail] => failed``: Money may be lost if it was deposited
+ into the purse in the meantime.
+
+* ``aborted``
+
+ The invoicing process ended without success.
+
+ * ``[action:delete] => deleted``
+
+* ``pending(withdraw)``
+
+ The wallet is withdrawing the money paid for the invoice.
+
+ * ``[processed-success] => done``
+ * ``[failure] => failed``
+ * ``[processed-kyc] => pending(kyc)``
+ * ``[processed-aml] => pending(aml)``
+ * ``[action:suspend] => suspended(withdraw)``
+
+* ``suspended(withdraw)``
+
+ The user suspended a withdraw operation.
+
+ * ``[action:resume] => pending(withdraw)``
+
+* ``pending(kyc)``
+
+ The user must supply KYC information before withdrawing can continue.
+
+ * ``[poll-success] => pending(withdraw)``
+ * ``[action:suspend] => suspended(kyc)``
+
+* ``suspended(kyc)``
+
+ The user suspended waiting for the KYC operation to complete.
+
+ * ``[action:resume] => pending(kyc)``
+
+* ``pending(aml)``
+
+ The user must await a positive exchange AML decision.
+
+ * ``[poll-success] => pending(withdraw)``
+ * ``[action:suspend] => suspended(aml)``
+
+* ``suspended(aml)``
+
+ The user suspended waiting for the AML decision to be successful.
+
+ * ``[action:resume] => pending(aml)``
+
+* ``failed``
+
+ Obtaining the money for the invoce failed. This is likely a case for the
+ auditor.
+
+ * ``[action:delete] => deleted``
+
+* ``done``
+
+ The payment for the invoice was successfully received.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+.. image:: ../images/transaction-pull-credit-states.png
+
+
+Transaction Type: Peer Pull Debit
+---------------------------------
+
+* ``pending(download)``
+
+ We are downloading the information about the invoice. Initial state.
+
+ * ``[action:suspend] => suspended(download)``
+ * ``[success] => pending(user)``
+
+* ``suspended(download)``
+
+ User suspended downloading the information about the invoice.
+
+ * ``[action:resume] => pending(download)``
+ * ``[action:delete] => deleted``
+
+* ``pending(user)``
+
+ We have downloaded information about the pull payment and are waiting for
+ the user to confirm.
+
+ * ``[action:confirm-pay] => pending(submit-payment)``
+ * ``[action:delete] => deleted``
+ * ``[timeout] => aborted``
+
+* ``pending(deposit)``
+
+ The user has confirmed the payment and the wallet tries to deposit
+ into the provided purse.
+
+ * ``[action:suspend] => suspended(deposit)``
+ * ``[processed-success] => done``
+ * ``[failure:timeout] => aborting(refresh)``
+ * ``[processed-success] => done``
+ * ``[failure:other] => aborting(refund)``
+
+* ``suspended(deposit)``
+
+ User suspended depositing into the purse.
+
+ * ``[action:resume] => pending(deposit)``
+ * ``[action:abort] => aborting_refund``
+
+* ``aborting(refund)``
+
+ Aborts the payment, asking for the already deposited coins to be refunded.
+
+ * ``[processed-success] => aborted(refunded)``
+ * ``[processed-failure] => aborting(refresh)``
+ * ``[action:fail] => failed``
+
+* ``aborting(refresh)``
+
+ Refreshes the coins that were previously deposited into the purse to recover their value.
+
+ * ``[processed-success] => aborted``
+ * ``[processed-failed] => failed``
+
+* ``done``
+
+ The invoice was successfully paid.
+
+ * ``[action:delete] => deleted``
+
+* ``deleted``
+
+ All information about the invoice has been deleted.
+
+.. image:: ../images/transaction-pull-debit-states.png
+
+Alternatives
+============
+
+* Each transaction could be treated completely separately; however, uniform
+ terminology for actions (and thus button labels) is likely more helpful for
+ the user experience.
+
+* We could require user re-approval if fees changed when the available
+ denominations change during a *withdraw*. This would require a different
+ state machine on withdraw. We believe the answer can be "no", for two
+ reasons: the wallet MUST pick denominations to withdraw with the "most
+ long-term" withdraw window (i.e. active denominations that have the longest
+ available withdraw durations). So in virtually all normal cases, this will
+ just succeed as a sane exchange will have a reasonable duration overlap, and
+ in the very few cases it's really the user's fault for going offline in the
+ middle of the operation. Plus, even in those few cases, it is highly
+ unlikely that the fee would actually change: again most key rotations can be
+ expected to be there to rotate the key, and not to adjust the withdraw fee.
+ And in the extremely rare case that the user went offline and in the
+ meantime the fees did *increase*, it's again unlikely to matter much to the
+ user. So special-casing this and testing this is probably not worth it.
+
+* We could require user re-approval if due to expired/invalid coins the coin
+ selection (and thus fees) changes during a *deposit*. Again, expired coins
+ should virtually never happen unless a user goes offline for a long time in
+ the middle of a purchase (which would be very strange). If deposit fees
+ *increase* due to a double-spend detection during payment, we might want to
+ have an *optional* dialog ("Balance reduced by X as wallet state was not
+ up-to-date (did you restore from backup?). Consequently, the fees for this
+ transactions increased from Y to Z. [Abort] [Continue] + checkbox: [X] Do
+ not ask again."). Probably at best a post-1.0 feature.
+
+
+Discussion / Q&A
+================
+
+* The diagrams only show what is happening **after** the wallet
+ has created the transaction. It is possible that network requests
+ are happening before that, but they are not considered to be part
+ of the transaction.
+* We have decided against a ``cancel`` state, because it resulted
+ in too much complexity. Instead of doing a direct ``cancel``,
+ the user has to go to the transaction and abort and/or delete
+ it.
+* We might add a ``revive`` action in the future that allows
+ to go from ``aborting`` back to ``pending`` for transactions
+ where this makes sense. We're not doing it right now
+ to simplify things.
diff --git a/design-documents/038-demobanks-protocol-suppliers.rst b/design-documents/038-demobanks-protocol-suppliers.rst
new file mode 100644
index 00000000..ee8fdc09
--- /dev/null
+++ b/design-documents/038-demobanks-protocol-suppliers.rst
@@ -0,0 +1,158 @@
+XX 38: Demobanks protocol suppliers
+###################################
+
+Summary
+=======
+
+This document models the association between financial data
+held in a LibEuFin *demobank* and the interface to let users
+access such financial data.
+
+Motivation
+==========
+
+LibEuFin Sandbox offers multitenency banking by the means of
+'demobanks'. Each demobank offers access to financial data via
+*several* APIs. The objective is to model such APIs so that each
+operation impacts one and only one demobank.
+
+Definitions
+===========
+
+Each API to access financial data at one demobank is offered by
+a **resource** called *protocol supplier*. Therefore protocol
+suppliers MAY be subject to all the CRUD operations
+
+For each request that a protocol supplier serves, the demobank
+being impacted can be found in the following ways:
+
+.. _demobank-mutually-exclusive:
+
+1. In a value that belongs to the request.
+2. In a value that belongs to the protocol supplier's state.
+3. Relying on the default demobank.
+
+Note: the three elements are mutually exclusive, so as to reduce
+ambiguity and simplify the implementation.
+
+Suppliers creation
+==================
+
+Suppliers can be static or dynamic.
+
+Static
+^^^^^^
+
+This supplier never changes its state. Whether this type
+of supplier is associated or not with a particular demobank
+MUST be stated in the documentation.
+
+Examples
+--------
+
+1. A JSON-based protocol that lets users access their bank accounts
+always under the 'default' demobank belongs to this category. It
+is therefore a 'static protocol supplier' with static demobank.
+
+2. A XML-based protocol that lets users access their bank accounts
+in a demobank whose name appear in the URI is as well a 'static protocol
+supplier' with dynamic demobank.
+
+Note: the upcoming (in version 0.9.3) JSON-based supplier that will
+let Nexus reach Sandbox accounts is planned as a 'dynamic protocol
+supplier' with dynamic demobank. That allows Taler demos to only
+speak JSON.
+
+Dynamic
+^^^^^^^
+
+This supplier has a name and its state CAN refer to one
+particular demobank. These suppliers need to be created
+first, in order to be used.
+
+Examples
+--------
+
+1. A JSON-based protocol that lets users access their bank
+accounts under the demobank whose name is held in the supplier
+state belongs to this category. It is therefore a dynamic
+supplier with semi-dynamic demobank.
+
+2. A XML-based protocol that lets user access their bank
+accounts under the demobank whose name is held both in the
+supplier state *and* in the URI is **wrong**. This supplier
+doesn't respect this `mutual exclusivity <demobank-mutually-exclusive_>`_.
+
+3. A XML-based protocol that lets user access their bank accounts
+always under the 'default' demobank belongs to this category. It
+is a dynamic supplier with static demobank.
+
+Supplier reachability
+=====================
+
+Each supplier must be available under its own URI.
+
+
+Current protocol suppliers design
+=================================
+
+Static X-LIBEUFIN-BANK with dynamic demobank
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The ``x-libeufin-bank`` protocol supplier is reachable under
+``/demobanks/{demobankName}/access-api/``. As the path suggests,
+it offers banking operations via the :doc:`Core Bank API </core/api-corebank>`.
+It is static in the sense that it's not possible to assign a name
+to one particular x-libeufin-bank protocol supplier. On the other
+hand, the demobank is dynamic because can be specified along the path
+in the ``demobankName`` placeholder.
+
+Dynamic EBICS supplier with dynamic demobank
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Every protocol supplier of this type is reachable under ``POST /ebicsweb``
+and by specifying its EBICS host name inside the EBICS message.
+
+As of the internal representation, Sandbox keeps a database table called
+``EbicsHostsTable`` that does **not** point at any demobank. Such table
+is the one that provides the bank's EBICS private keys and has **no** business
+implications.
+
+CCT (Payment initiations)
+-------------------------
+
+This handler gets the bank account directly from the IBAN that was
+carried along the pain.001, therefore -- as long as every IBAN is
+unique -- this works with **any** demobank that hosts such IBAN. The
+EBICS subscriber public keys are extracted differently: they come from
+the tuple [userID, partnerID, systemID?] held in the request. Hence as
+long as such tuple is unique for each subscriber (Sandbox checks that),
+even the subscriber public keys are found regardless of the demobank name.
+
+.. note::
+
+ The 'context' object found via the [userID, partnerID, systemID?] tuple
+ has **also** a reference to the bank account. The consistency with the
+ other bank account reference returned by the IBAN is currently NOT checked.
+
+C52 (transactions report)
+-------------------------
+
+This handler gets the reference to the subscriber public keys and bank
+account via the [userID, partnerID, systemID?] tuple. It then uses
+this bank account label to find the transactions that belong to the
+subscriber that made the request.
+
+.. note::
+
+ The current implementation does NOT uses any demobank name along
+ the transactions research: only the bank account label. This can
+ lead to **ambiguity**, in case two different demobanks host respectively
+ one bank account under the same label. This is not possible however
+ in the current version, as Sandbox only admits one ``default`` demobank.
+
+
+Alternatives
+============
+
+Drop support for multitenancy banking. What is the benefit of this anyway?
diff --git a/design-documents/039-taler-browser-integration.rst b/design-documents/039-taler-browser-integration.rst
new file mode 100644
index 00000000..980f3f25
--- /dev/null
+++ b/design-documents/039-taler-browser-integration.rst
@@ -0,0 +1,195 @@
+DD 39: Taler Wallet Browser Integration Considerations
+######################################################
+
+Summary
+=======
+
+This design document discusses considerations for integrating the GNU Taler
+wallet with browsers and highlights difficulties with the implementation of a
+GNU Taler wallet as a cross-browser WebExtension.
+
+Motivation
+==========
+
+GNU Taler is a payment system based on open standards with a free and open
+source reference implementation. The GNU Taler wallet is the main component
+used by end users to manage their electronic cash balance and payments.
+
+Payments with GNU Taler are typically initiated via a QR code or link that
+contains a ``taler://pay/{merchant}/{order_id}`` URI. Navigating to such a
+link should result in a context switch to the wallet, where the payment can can
+be approved/declined, and the user is subsequently redirected to the merchant's
+website again.
+
+Other ``taler://`` URIs (for withdrawals, refunds, etc.) are also commonly
+used, but not explicitly discussed in this document, as very similar
+considerations apply.
+
+There are multiple reference implementations available for multiple
+platforms (command line, Android, iOS, WebExtension).
+
+While native applications can register themselves as a handler for the
+``taler`` URI scheme, the story is different for WebExtensions: There is
+currently no reasonable, cross-platform mechanism that allows a WebExtension to
+register itself as the handler for the ``taler`` URI scheme.
+
+This is unfortunate, as a WebExtension could otherwise easily provide a Taler
+wallet implementation without requiring the user to install a native App,
+providing a better and safer user experience.
+
+The problems with individual browsers are:
+
+* Firefox allows specifying ``protocol_handlers`` in the extension manifest.
+ However, this list only allows schemes with the prefix ``ext+`` and
+ schemes that are included in an allowlist. The ``taler`` URI scheme
+ is not part of this list yet.
+* Chromium / Google Chrome allows extensions to use the
+ ``registerProtocolHandler`` API. However, the same allowlist restrictions
+ apply. Furthermore, the registered protocol scheme is not listed as the
+ extension's required/optional permissions. Instead, a different permission
+ prompt is dynamically shown to the user.
+* Safari currently neither supports ``registerProtocolHandler`` nor the
+ ``protocol_handlers`` mechanism.
+* Opera does not seem to have any support for WebExtension protocol handlers
+ either.
+
+Another issue is that Websites can't easily find out whether a browser
+extension handling the ``taler://`` protocol is installed.
+
+Requirements
+============
+
+* No vendor lock-in: The integration should not require merchant
+ Websites to rely on a particular list of extension IDs but instead
+ any WebExtension to potentially handle ``taler://`` URIs
+ or other mechanisms that Websites can use to interact with Taler
+ wallets.
+* Security: The integration mechanism should require as few
+ permissions as possible.
+* Ergonomic user experience: As few clicks and permission
+ prompts as possible should be shown to the user.
+* Ergonomic developer experience: The code size and
+ effort to trigger a Taler payment on a merchant's Website
+ should be minimized.
+* Forward compatibility: The integration mechanism
+ should work smoothly with future browsers that
+ have native, built-in support for Taler payments.
+
+Proposed Solution
+=================
+
+.. note::
+
+ As of 2023-01-23, we've decided to go ahead with the approach
+ described in this section.
+
+Overview
+^^^^^^^^
+
+The following integration approaches between Websites and the Taler Wallet webextension
+are provided:
+
+1. Directly triggering a ``taler://...`` URI on page load (via a meta tag).
+2. Overriding ``<a href="taler://..." onclick=...>`` tags to trigger the wallet.
+ The onclick handler (which must call preventDefault) can implement behavior
+ that happens only when the webextension is not available.
+3. Future (possibly post-1.0): A ``window.taler`` JavaScript API that is injected
+ into every page that requests it via a meta tag. This is useful for SPAs that
+ want to programmatically trigger the Taler wallet.
+
+
+Usage
+^^^^^
+
+To directly trigger the handling of a ``taler://`` URI on page load, the following meta tag can be used:
+
+.. code::
+
+ <meta name="taler-uri" content="taler://...">
+
+
+To enable additional communication features between a website and the GNU Taler Wallet webextension, the page must
+include the following meta tag:
+
+.. code::
+
+ <meta name="taler-support" content="$features">
+
+where ``$features`` is a comma-separated list of features.
+
+The following features are supported:
+
+* ``uri`` will hijack anchor elements (``<a href="taler://..." onclick=...>``) and replace their onclick handler
+ with a different handler that lets the webexension wallet handle the ``taler://`` URI.
+
+* (future): ``api`` will inject the ``window.taler`` API into the page
+
+
+Caveats and Comments
+^^^^^^^^^^^^^^^^^^^^
+
+* Anchor tag hijacking does not work in all use-cases, for example when a navigation
+ to a ``taler://`` URI is initiated programmatically or by pasting
+ the URI in the browser's address bar.
+
+* The ``window.taler`` API injection may break some websites
+ (https://github.com/brave/browser-laptop/issues/13711).
+
+* All these approaches require excessive permissions, as unfortunately,
+ browsers currently do not provide a safe way for the communication between a
+ WebExtension and the page without excessive permissions. This especially
+ applies if the Website does not know the extension's ID. Hard-coding the
+ extension IDs would violate the "no vendor lock-in requirement".
+
+* A neat feature of the anchor hijacking is that the ``taler://`` URI can be always be copied
+ in the browser (via "copy link address"). Clicking the link always results in either:
+
+ * The native URI handler, if no Taler Wallet webextension is installed and no onclick handler is defined
+ * The execution of the websites onclick handler if no Taler Wallet webextension is installed
+ * Triggering the webextension wallet to handle the ``taler://`` URI.
+
+* Future ``window.taler`` injection should be based on user preferences on
+ sites where the user has explicitly accepted to disclose that is owner of a
+ Taler wallet.
+
+Other Alternatives
+==================
+
+
+* Triggering interactions with the ``taler://`` URI in a ``Taler:`` HTTP
+ header. This approach would allow browsers with native Taler support
+ (or a WebExtension) to handle payment/withdrawal initiations directly,
+ without rendering a page that shows the QR code or link.
+ However, the WebExtension APIs do not allow extensions to
+ read particular headers without excessive permissions. Furthermore,
+ more recent versions of Chrome/Chromium do not allow blocking
+ processing of headers, leading to flickering when the extension
+ redirects based on the presence of the ``Taler:`` header.
+
+* Browser and wallet presence detection. Merchants' Websites could include custom
+ code to detect the browser and/or presence of a Taler WebExtension and show
+ different instructions to guide the user towards processing the payment or to
+ show ``ext+taler`` URIs instead of ``taler`` URIs. This is not a viable
+ solution, as it requires a lot of extra and brittle logic on merchants'
+ Websites.
+
+* Always use ``ext+taler`` URIs. This would help with Firefox.
+ Bad for forward compatibility, as we have already provisionally registered the
+ ``taler`` URI scheme.
+
+* Web Payments API: Using the Web Payments API is not possible, because current
+ browsers do not allow specifying a WebExtension as a handler. Furthermore,
+ the Web Payments API would not support the withdrawal flow
+ (``taler://withdraw`` URIs).
+
+* Browsers could provide anchor elements with a fallback when the protocol isn't supported, such as
+ ``<a href="taler://pay/..." handler-unavailable-href="https://wallet.taler.net/">...</a>``.
+
+
+
+Related Work and References
+===========================
+
+* **[1]** https://github.com/whatwg/html/issues/8596
+* **[2]** https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers
+* **[3]** https://github.com/ipfs/devgrants/blob/master/targeted-grants/protocol-handler-api-for-browser-extensions.md
diff --git a/design-documents/040-distro-packaging.rst b/design-documents/040-distro-packaging.rst
new file mode 100644
index 00000000..13131359
--- /dev/null
+++ b/design-documents/040-distro-packaging.rst
@@ -0,0 +1,139 @@
+DD 40: Distro Packaging
+#######################
+
+.. admonition:: Metadata
+
+ Status
+ proposed
+
+Summary
+=======
+
+This DD discusses considerations for disto packages of GNU Taler components,
+especially with regards to configuration and setup. We focus on Debian
+packages for now.
+
+Motivation
+==========
+
+The current way that configuration files are handled does not work well with
+automated setup tools and furthermore does not easily allow restoring
+configuration files in ``/etc/`` that the admin deleted or manually modified.
+
+The database configuration is currently handled inconsistently. While some
+packages use Debian's dbconfig-common facilities, others don't, even though
+they require a database for operation.
+
+The guidelines in this document are based on pratical experience
+with third parties setting up Taler based on the Debian packages
+and scripting supplied by us (i.e. ``deployment.git/netzbon``).
+
+Requirements
+============
+
+* The distro package should work nicely both for a manual setup
+ process by a sysadmin, as well as for automated installation
+ via helper scripts or other tools.
+* Major differences between different distributions should be minimized, the
+ more code and config templates that can be shared the better.
+
+Proposed Solution
+=================
+
+This section contains the new guidelines that we want to apply to all our
+distro packages, specifically the Debian packages.
+
+General Considerations
+----------------------
+
+* Packages may not enable a systemd service by default.
+
+Config Files: Taler-specific
+----------------------------
+
+The "pristine" version of config files must be installed into
+``/usr/share/taler/etc-original``. These files should not be modified by
+tooling or the user. These files may contain direct placeholders or
+placeholder comments that are replaced (but not in-place, only in ``etc/``!)
+when the package is configured.
+
+During the postinstall step, the files from ``/usr/share/taler/etc-original``
+are copied to ``/etc/`` (using the ``ucf`` tool on Debian) and, if required,
+placeholders are filled in.
+
+When using tooling to set up Taler, the tooling **should not**
+use files from ``/etc/`` as template, but instead from ``/usr/share/taler/etc-original`` or alternatively generate custom configuration files.
+
+Rationale: Debian manages conffiles in ``/etc/`` with special logic.
+In particular, when files are deleted from ``/etc/taler`` and the package
+is reinstalled (even with ``--reinstall``), there is no easy way for
+tooling (or the admin) to restore the unmodified config files.
+The only way to restore it is ``apt install --reinstall libtalerexchange -o Dpkg::Options::="--force-confmiss"``, which might be unsafe as it forces
+overriding of *all* config files of the package.
+
+Config Files: HTTP Server
+-------------------------
+
+The same considerations apply to configuration files of HTTP
+servers (nginx, apache, caddy, ...). Additionally:
+
+* Configuration files *must* either have a well-known name
+ or particular suffix to easily identify them
+
+ * In particular, file names like ``sites-available/exchange.$domain``
+ are unacceptable, as they are very difficult to uninstall
+ or remove when ``$domain`` is changed.
+
+* Configuration files for the HTTP server must not be
+ active by default, i.e. they must be placed in ``sites-available``
+ but not ``sites-enabled``.
+
+Database
+--------
+
+Packages should *not* use ``dbconfig-common``. Reasons are:
+
+* ``dbconfig-common`` is lacking in documentation and very difficult
+ to use for packagers.
+* ``dbconfig-common`` offers too much flexibility and
+ asks too many questions to the administrator, especially when
+ reconfiguring a package. The ``taler-merchant`` package
+ currently breaks when the user chooses anything else than ``ident`` auth.
+* Using ``debconfig-common`` makes the database setup logic difficult to test.
+ That is not a problem with simple packages, but most Taler packages
+ require a non-trivial database setup.
+* Very few packages in Debian (<30) actually use ``dbconfig-common``;
+ even fewer are notable or widely used packages.
+
+Instead, each package should document how to set up
+the database and *optionally* ship an executable named
+``taler-$component-dbconfig`` that:
+
+1. Creates the database and adjusts permissions
+2. Checks if the database is accessible
+3. Runs ``taler-$component-dbinit`` if applicable
+ and unless supressed by the user.
+
+For now, our tooling shall only support PostgreSQL and only set up ``ident``
+authentication or set up ``password`` authentication with a random password for
+components that do not support DB connections via unix domain sockets.
+
+Definition of Done
+==================
+
+* All Taler and Anastasis packages follow the guidelines from this DD
+* Packages installation has been manually tested
+* Automated setup scripts (``deployment.git``) have been adjusted to use the
+ configuration file templates shipped in the package,
+ instead of using their own config templates.
+
+Alternatives
+============
+
+* Do not ship with distro-specific configuration files, instead only ship
+ tooling to generate config files and set up the database.
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/041-wallet-balance-amount-definitions.rst b/design-documents/041-wallet-balance-amount-definitions.rst
new file mode 100644
index 00000000..9943d482
--- /dev/null
+++ b/design-documents/041-wallet-balance-amount-definitions.rst
@@ -0,0 +1,410 @@
+DD 41: Wallet Balance and Amount Definitions
+############################################
+
+Summary
+=======
+
+This design document discusses terminology and concepts used in the wallet
+for balances and amounts.
+
+Motivation
+==========
+
+There are many different types of balances and amounts, and they need
+to have a clear definition.
+
+Furthermore, the user in some situations needs to know/decide whether
+an amount that the user chooses includes fees or not.
+
+
+Proposed Solution
+=================
+
+Amounts
+-------
+
+* "effective amount": An effective amount always represents the direct effect on the
+ wallet's balance of the same currency.
+* "raw amount": The raw amount always refers to the amount with fees applied.
+ The exact interpretation of that depends on the transaction type.
+* "instructed amount": An instructed amount always refers to the amount that
+ a user has explicitly specified as an input. It is not directly a property
+ of transactions, but might be added as metadata to transactions for
+ informational purposes. How the instructed amount is interpreted
+ differs based on the "instructed amount mode" that is specified
+ together with the amount. By default, the instructed amount
+ is assumed to translate to the raw amount of the corresponding transaction.
+* "counter-party effective amount": An amount that **estimates** the effect
+ of the transaction of the balance (either wallet or bank account) of the other
+ party. This is usually a conservative estimate, i.e. when sending money,
+ this is the lower bound for the funds that the other party will obtain
+ *after* fees.
+
+
+Transaction types initialized by the wallet
+-------------------------------------------
+
+MANUAL_WITHDRAW
+ raw amount is the amount that need to be added into the exchange account,
+ this is not the same as the amount leaving the user account since their own
+ account may charge some fee that can be taken into account
+
+ ``coins`` = select-coin-for-operation(credit, mode, instructed_amount)
+
+ if instructed_amount mode = raw
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount - coins.withdrawal_fee
+
+ if instructed_amount mode = effective
+ ``raw_amount`` = instructed_amount + coins.withdrawal_fee
+
+ ``effective_amount`` = instructed_amount
+
+DEPOSIT
+ raw amount is the amount leaving the exchange account without the wire fee,
+ this should be what the user see as an increase in the user bank account unless
+ there are some extra fee not seen from the exchange
+
+ ``coins`` = select-coin-for-operation(debit, mode, instructed_amount)
+
+ if instructed_amount mode = raw
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount + coins.deposit_fee + coins.refresh_fee + wire.transfer_fee
+
+ if instructed_amount mode = effective
+ ``raw_amount`` = instructed_amount - coins.deposit_fee - coins.refresh_fee - wire.transfer_fee
+
+ ``effective_amount`` = instructed_amount
+
+PULL CREDIT (creating an invoice)
+ raw amount is the amount in the exchange that counter-party need will pay (purse_value),
+ this is exactly the same raw amount of the PULL DEBIT operation (paying the invoice)
+ counter-party amount is an aprox amount of the other wallet assuming the same coin selection
+ algorithm
+
+ ``coins`` = select-coin-for-operation(credit, mode, instructed_amount)
+
+ if instructed_amount mode = raw
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount - coins.withdrawal_fee - purse_fee
+
+ if instructed_amount mode = effective
+ ``raw_amount`` = instructed_amount + coins.withdrawal_fee + purse_fee
+
+ ``effective_amount`` = instructed_amount
+
+ if instructed_amount mode = counter-party
+ ``raw_amount`` = instructed_amount - coins.counter-party_deposit_fee
+
+ ``effective_amount`` = instructed_amount - coins.counter-party_deposit_fee - coins.withdrawal_fee - purse_fee
+
+ ``counter-party_raw_amount`` = raw_amount
+
+ ``counter-party_effective_amount`` = raw_amount + coins.counter-party_deposit_fee
+
+ .. note::
+
+ counter-party_effective_amount is an estimation since refresh fee is not included.
+ Refresh fee can't be calculated because depends on the coins available in the wallet
+ of the counter-party
+
+ .. note::
+ coins.counter-party_deposit_fee is the minimum deposit_fee that can be calculated for the
+ given exchange. Counter-party may pay more if it have different preferences doing the coin
+ selection.
+
+
+PUSH DEBIT (creating a transfer)
+ raw amount is the amount in the exchange that counter-party need be able to withdraw (purse_value),
+ this is exactly the same raw amount of the PUSH CREDIT operation (getting the transfer)
+ counter-party amount is an aprox amount of the other wallet assuming the same coin selection
+ algorithm
+
+ ``coins`` = select-coin-for-operation(debit, mode, instructed_amount)
+
+ if instructed_amount mode = raw
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount + coins.deposit_fee + purse_fee
+
+ if instructed_amount mode = effective
+ ``raw_amount`` = instructed_amount - coins.deposit_fee - purse_fee
+
+ ``effective_amount`` = instructed_amount
+
+ if instructed_amount mode = counter-party
+ ``raw_amount`` = instructed_amount + coins.counter-party_withdraw_fee
+
+ ``effective_amount`` = instructed_amount - coins.counter-party_withdraw_fee - coins.withdrawal_fee - purse_fee
+
+ ``counter-party_raw_amount`` = raw_amount
+
+ ``counter-party_effective_amount`` = raw_amount - coins.counter-party_withdraw_fee
+
+ .. note::
+ coins.counter-party_withdraw_fee is the minimum withdraw_fee that can be calculated for the
+ given exchange. Counter-party may pay more if it have different preferences doing the coin
+ selection.
+
+
+Transaction types completed by the wallet
+-------------------------------------------
+
+Next transaction types are not initiated in the wallet so instructed amount is not defined by the wallet user.
+
+We need to calculate effective_amount to check if the wallet is able to perform the operation or the user accept
+the fees.
+
+BANK_WITHDRAW
+ raw amount is that reached the bank account of the exchange,
+ this is not the same as the amount leaving the user account since their own
+ account may charge some fee that can be taken into account
+
+ ``coins`` = select-coin-for-operation(credit, mode, instructed_amount)
+
+ if instructed_amount mode = raw
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount - coins.withdrawal_fee
+
+ if instructed_amount mode = effective
+ ``raw_amount`` = instructed_amount + coins.withdrawal_fee
+
+ ``effective_amount`` = instructed_amount
+
+
+.. note ::
+ how much wire_fee the merchant is willing to pay
+
+ ``merchant_wire_fee`` = min(wire.transfer_fee / contractTerms.amortization_factor, contractTerms.max_wire_fee)
+
+ ``merchant_deposit_fee`` = min(contractTerms.max_fee, contract_wire_fee)
+
+
+PAYMENT
+ raw amount is the amount the merchant should get if is not doing aggregated transaction.
+
+
+ ``instructed_amount`` = contractTerms.amount
+
+ ``coins`` = select-coin-for-operation(debit, mode, raw_amount)
+
+ ``raw_amount`` = instructed_amount - merchant_deposit_fee
+
+ ``effective_amount`` = instructed_amount + coins.deposit_fee + coins.refresh_fee + (wire.transfer_fee - merchant_wire_fee)
+
+ .. note::
+ The current coin-selection algorithm the order_price is neither raw_amount nor effective_amount.
+ We can calculate the raw_amount of the payment as (contractTerms.amount - max_merchant_fee) and then this
+ operation becomes equivalent than a deposit (in terms of fee calculation).
+
+
+PUSH CREDIT (getting the transfer)
+ raw amount is the purse_value in the exchange to be withdrawn
+
+ ``instructed_amount`` = p2pContract.amount
+
+ ``coins`` = select-coin-for-operation(credit, mode, raw_amount)
+
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount - coins.withdrawal_fee
+
+ .. note::
+ In the case that the withdrawal_fee of the coin selection for the push-credit amount
+ is higher than the wire_fee of the exchange, can the wallet ask the exchange to make
+ a wire transfer of the purse instead of proceeding?
+
+PULL DEBIT (paying an invoice)
+ raw amount is the net value of the invoice without fees
+
+ ``instructed_amount`` = p2pContract.amount
+
+ ``coins`` = select-coin-for-operation(debit, mode, raw_amount)
+
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount + coins.deposit_fee + coins.refresh_fee + wire.transfer_fee
+
+REFUND
+ raw amount is the amount that the merchant refunded
+
+ ``instructed_amount`` = refund.amount
+
+ ``raw_amount`` = instructed_amount
+
+ ``effective_amount`` = instructed_amount - refund_fee - refresh_fee
+
+ .. note::
+ There may be the case that the merchant should refund all the value of the purchase
+ and that may include paying for the refund_fee.
+
+ Is there a way that the merchant can initiate a refund of purchase + refund_fee so
+ the wallet will get the same effective_amount?
+
+Coin selection algorithm
+------------------------
+
+Is an internal optimization algorithm that will choose coins given a denomination list or current coin list
+until amount is reached. The coins selected to minimize the fee spent.
+
+``select-coin-for-operation`` will receive 3 parameters:
+
+ * operation: define if the coins are selected from wallet database or from denomination list. Possible values are:
+
+ - credit: reduce withdrawal fee, use exchange denomination list
+ - debit: reduce deposit fee, use database denomination and current coin count
+
+ * amount: how much value the coins need to sum up
+ * mode: the interpretation of the amount parameter
+
+ - net: the amount does not include the operation fee
+ - gross: the amount include the operation fee
+
+If the operation is debit and with the current coins there is no way to reach the amount then
+
+ 1. an optimized withdrawal operation can be suggested (list of denominations)
+ 2. an optimized refresh operation can be suggested (amount gap, coin to be refreshed and list of denominations to withdraw)
+
+.. note::
+
+ ``select-coin-for-operation`` must be predictable (select the same coins for the same parameters) and if
+ the difference between two amounts are the fee for a given operation:
+
+ operation: credit
+
+ withdrawal_fee = amount1 - amount2
+
+ then the wallet should select the same coins using the correct mode
+
+ select-coin(withdraw, raw, amount1) == select-coin(withdraw, effective, amount2)
+
+
+Instructed Amount Modes
+-----------------------
+
+The interpretation and possible choices of the instructed amount mode
+depends on which transaction is initiated.
+
+For withdrawal:
+
+* ``raw-mode`` (default): instructed amount is what is subtracted from the reserve balance (i.e. it's the raw amount)
+* ``effective-mode``: instructed amount is what the user wants to have as material balance in the wallet
+
+FIXME(dold): However, that does not really cover the user case where the merchant charges fees and the user has to pay for that.
+So in theory we could have a mode that withdraws enough to pay for some particular claimed order, but IMHO that's overkill.
+
+For deposits (where there is no contract that already specifies an amount):
+
+* ``max-mode``: The instructed amount is ignored (can be zero in the request), and all coins are spent (note that the calculation should be made available when the user is asked to specify an amount when using a template)
+* ``raw-mode`` (default): The instructed amount is what will be paid to the "merchant" (or the customer's bank account), ignoring wire fees
+* ``effective-mode``: The instructed amount is how the wallet's balance will be affected. Difficult to compute accurately because refresh is involved. Note that the calculation should ideally again be made available when the user is asked to specify an amount when using a template.
+
+
+For peer-push-debit:
+
+* ``raw-mode`` (default): The instructed amount is what will be paid, deposit fees are covered by the sender, withdrawal fees from the reserve by the receiver
+* ``effective-mode``: Instructed amount is the effect on the material balance. Difficult to compute accurately because refresh is involved.
+* ``counter-party-effective-mode``: Instructed amount is the effect on the counterparty's wallet balance. Difficult to compute accurately because coin selection by receiver may not match our expectations.
+
+FIXME(dold): Should we also have a mode where withdrawal fees are covered by the side that does peer-push-debit? However, that assumes the other party has the same withdrawal coin selection algorithm. FIXME(grothoff): isn't this the counterparty-effective-mode you described above, and that would seem to exactly have this issue?
+
+For peer-pull-credit:
+
+* ``raw-mode`` (default): Amount that the other party has to put in the reserve. The payer has to pay any deposit fees on top. The receiver balance is increased by the specified amount minus any withdraw fees.
+* ``effective-mode``: Amount by which the initiator's balance is increased. Difficult to compute as the receiver has to simulate different coin selections and their effect on withdraw fees to arrive at the minimum total amount that must be deposited into the reserve.
+
+
+
+Illustrative Example
+--------------------
+
+To explain the differences between raw, effective and instructed amounts, consider the following scenario: Alice wants to send money
+to Bob via a P2P push payment.
+
+Example 1:
+
+* Alice starts a withdrawal of ``KUDOS:10`` from her bank's web interface into her Taler
+ wallet. The instructed amount is ``KUDOS:10`` and (by default for bank-integrated withdrawals),
+ the mode is ``raw-mode``. After fees, ``KUDOS:9.8`` arrive in her Taler wallet.
+
+Example 3:
+
+* Alice wants to pay for a ``KUDOS:10`` monthly magazine subscription. Her Taler wallet is empty though.
+* She starts withdrawal through her Android wallet app, where she selects ``KUDOS:10`` as the instructed
+ amount with ``mode=effective-mode``. This translates to ``amountEffective=KUDOS:10`` and ``amountRaw=KUDOS:10.10``.
+* Alice is redirected to her banking app where she transfers ``KUDOS:10.10`` to the exchange.
+* Her Taler wallet balance will be ``KUDOS:10.10`` after the withdrawal completes.
+
+Note that on the amount she chooses and the fees / denom structure of the exchange, the ``amountEffective`` might be *higher*
+than the instructed amount.
+
+FIXME(dold): That flow does not work if withdrawal starts in the bank. Maybe there needs to be a mechanism
+where the wallet tells the bank the adjusted amount that needs to be transferred? That would be a new
+feature in the bank integration API.
+
+Example 4:
+
+* Alice has ``KUDOS:10`` in her wallet.
+* Alice wants to initiate a peer-push payment with ``amountInstructed=KUDOS:8``
+ and ``mode=effective-mode``. That means that after the payment, she expects
+ exactly ``KUDOS:2`` to remain in her wallet.
+* Due to the fee configuration, her wallet computes ``amountRaw=KUDOS:7.5`` and ``amountEffective=KUDOS:7.8``.
+ The effective amount in this case does **not** equal the instructed amount, despite the ``mode=effective-mode``.
+ That's because there no amount that can be spend so that the spend amount with resulting refresh
+ fees equal ``KUDOS:8``.
+* Alice confirms the peer-push payment initiation, and exactly ``KUDOS:7.5`` are credited
+ to the purse that her wallet creates.
+* Bob merges the purse into his reserve. Bob's wallet automatically withdraws
+ from the reserve, and his wallet balance increases by ``KUDOS:7.1``, since
+ withdrawal fees are deducted.
+
+Balances
+--------
+
+The following types of balances are defined:
+
+- ``available``: Balance that the wallet believes will certainly be available
+ for spending, modulo any failures of the exchange or double spending issues.
+ This includes available coins *not* allocated to any
+ spending/refresh/... operation. Pending withdrawals are *not* counted
+ towards this balance, because they are not certain to succeed.
+ Pending refreshes *are* counted towards this balance.
+ This balance type is nice to show to the user, because it does not
+ temporarily decrease after payment when we are waiting for refreshes
+
+- ``material``: Balance that the wallet believes it could spend *right now*,
+ without waiting for any operations to complete.
+ This balance type is important when showing "insufficient balance" error messages.
+
+- ``age-acceptable``: Subset of the material balance that can be spent
+ with age restrictions applied.
+
+- ``merchant-acceptable``: Subset of the material balance that can be spent with a particular
+ merchant (restricted via min age, exchange, auditor, wire_method).
+
+- ``merchant-depositable``: Subset of the merchant-acceptable balance that the merchant
+ can accept via their supported wire methods.
+
+Balance Mismatch
+----------------
+
+The wallet uses the following terminology when an operation can't succeed
+because the balance is too low, even though the instructed amount
+
+- "fee-gap-estimate": Additional (material) balance that the wallet estimates it
+ still needs for the operation to succeed.
+
+ - This value is an estimated, because newly withdrawn coins might have different fees.
+ - This value is specified *per exchange*, because each exchange has different fees.
+
+FIXME(dold): Should we specify an upper-bound fee-gap-estimate to simplify it for the UIs?
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/042-synthetic-wallet-errors.rst b/design-documents/042-synthetic-wallet-errors.rst
new file mode 100644
index 00000000..97262002
--- /dev/null
+++ b/design-documents/042-synthetic-wallet-errors.rst
@@ -0,0 +1,57 @@
+DD 42: Wallet Dev Experiments
+#############################
+
+Summary
+=======
+
+This design document defines new ``taler://`` URIs to cause synthetic errors
+or special states in the wallet that can then be rendered by the UI.
+
+Motivation
+==========
+
+UIs need to handle various (error-) states and responses of wallet-core. It's
+not easy to cover all of these states and responses manually. Some of them are
+hard to reach or simulate without an elaborate test setup.
+
+Requirements
+============
+
+The implementation of synthetic errors should be as separate from production
+code as possible, to avoid making the normal code paths unreadable.
+
+
+Proposed Solution
+=================
+
+Special taler:// URIs
+---------------------
+
+* ``taler://dev-experiment/$STATE_ID``
+* ``taler://pay/...?dev-experiment=``
+
+Special http(s):// URIs
+-----------------------
+
+* ``http(s)://*.dev-experiment.taler.net/``
+
+ * URLs for this subdomain are handled specially by the
+ wallet's HTTP layer and return fixed / mocked responses
+ instead of making real requests.
+
+
+List of experiments
+-------------------
+
+
+
+Alternatives
+============
+
+Drawbacks
+=========
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/043-managing-prebuilt-artifacts.rst b/design-documents/043-managing-prebuilt-artifacts.rst
new file mode 100644
index 00000000..3d0debc8
--- /dev/null
+++ b/design-documents/043-managing-prebuilt-artifacts.rst
@@ -0,0 +1,107 @@
+DD 43: Managing Prebuilt Artifacts and Source-Level Dependencies
+################################################################
+
+Summary
+=======
+
+This design document defines how the GNU Taler project manages prebuilt
+artifacts and other source-level dependencies in repositories
+that are part of project.
+
+Motivation
+==========
+
+Some repositories have source-level dependencies on the build results of other
+repositories. While it is possible to build these dependencies from scratch,
+we often want to avoid that extra step in order to make building the software
+easier and faster.
+
+Examples are:
+
+* man-pages built via Sphinx, produced in ``docs.git`` and consumed in ``exchange.git``.
+* SPAs used by the exchange, merchant libeufin.
+* (formerly): The ``taler-wallet-core-embedded.js`` file used
+ by the Android repo.
+
+Another type of source-level dependency is on other **source** files.
+Examples for this are:
+
+* The ``build-common.git`` repository that contains common build-time helpers
+* The ``gana.git`` repository
+
+Requirements
+============
+
+* We are a free software project, users must always be able to
+ easily re-build the prebuilt artifacts for themselves.
+* We want to reduce reliance on third party infrastructure as much
+ as possible.
+* We want to keep our own infrastructure as slim as possible.
+* Builds must be reproducible and remain reproducible in the future.
+* Offline builds should be supported as well as possible.
+* Ideally, prebuilt artifacts can easily be cached / archived by third parties.
+
+Proposed Solution
+=================
+
+Instead of using a full-blown artifact management solution from the start, we
+make use of Git. Since Git is not very good at managing binary artifacts,
+prebuilt files are not managed alongside the source code. Instead, we use
+(orphan-)branches) in a (possibly separate) repository to manage them. This
+allows us to re-use Git authentication and Git commit signing.
+
+Due to a historical accident, prebuilt files are currently stored in the
+``prebuilt`` branch of the ``wallet-core.git`` repository. This might change
+in the future.
+
+To enable forwards-compatibility, we are implementing the following approach:
+The ``taler.net`` HTTP server redirects requests in the form of
+``https://artifacts.taler.net/$component/$version/$file`` to
+``https://git.taler.net/$repo.git/tree/$version/$file?h=$branch``, where
+``$component`` determines ``$repo`` and ``$branch``.
+
+We are also introducing the new rule that a prebuilt artifact must be a single
+archive file (preferable ``.tar.gz.``).
+
+Repositories that produce artifacts *MUST* have phony Makefile helper targets
+to (a) create the prebuilt artifact (b) check out the prebuilt branch as a Git worktree, (c)
+move the artifact to the right place in the prebuilt branch and commit/push.
+
+Repositories that depend on prebuilt components must download them
+in their ``./bootstrap`` script. After downloading, the bootstrap
+script *SHOULD* verify a checksum of the downloaded artifact.
+
+FIXME: Link to an example bootstrap file, once the approach from this document
+is fully implemented.
+
+Alternatives
+============
+
+* The prebuilt branch could be integrated into source repositories that
+ depend on it via a git submodule. This has the following disadvantages:
+
+ 1. Git submodules are difficult to handle for some developers. We've had issues
+ in the past where the prebuilt submodule wasn't integrated properly and would
+ lead to non-reproducible builds
+ 2. Relying purely on the Git commit hash of the prebuilt branch
+ makes it very difficult to know which version of the prebuilt
+ artifact is being used. It also makes it very difficult
+ to reliable find older versions of the artifact.
+ 3. Relying on the commit hash instead of a version and artifact hash
+ couples the artifact to the Git commit. That means that
+ we can never compact the commit history of the prebuilt
+ branch of move artifacts around.
+
+* Set up a full-blown artifact management solution like JFrog Artifactory.
+ That's costly and requires a lot of admin work.
+
+* Never rely on prebuilt files. That slows down the build process
+ and in some cases requires exotic dependencies.
+
+Drawbacks
+=========
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/044-ci-system.rst b/design-documents/044-ci-system.rst
new file mode 100644
index 00000000..e38632c0
--- /dev/null
+++ b/design-documents/044-ci-system.rst
@@ -0,0 +1,130 @@
+DD 44: CI System
+################
+
+Summary
+=======
+
+This documents describes Taler's CI system based on Buildbot.
+
+This document uses `RFC 2119 <https://tools.ietf.org/html/rfc2119>`_
+keywords throughout.
+
+Motivation
+==========
+
+With the current CI system there are an array of issues:
+
+- Central place for all the jobs.
+- The central config is poorly organized.
+- We should prefer to keep as much CI logic in respective project source repos
+ as possible.
+- Jobs should be split up further to allow for more granular control and
+ insight.
+- Job triggers are unclear.
+- The build environments are mutable.
+- Non-trivial and error-prone to keep track of environment state.
+- Hard to get an overview of what repo is causing a failure, at a quick glance.
+- Bad for development workflow on a single project when you are getting
+ false-negatives all the time.
+
+Proposed Solution
+=================
+
+General
+-------
+
+Jobs shall be executed inside of containers.
+
+One build pipeline (aka. "builder") per repo.
+
+Build steps are generated from directory structure within a given repo.
+
+Example directory structure:
+
+::
+
+ contrib
+ └── ci
+ ├── ci.sh
+ ├── Containerfile
+ └── jobs
+ ├── 0-codespell
+ │   ├── config.ini
+ │   ├── dictionary.txt
+ │   └── job.sh
+ ├── 1-build
+ │   ├── build.sh
+ │   └── job.sh
+ └── 2-docs
+ ├── docs.sh
+ └── job.sh
+
+Job directories **MUST** follow this pattern:
+``<repo_root>/contrib/ci/jobs/<n-job_name>/``
+
+``n`` is an integer used for ordering the build steps.
+
+Job directories **MUST** contain a script named ``job.sh`` which **MAY**
+execute other scripts.
+
+Config files may optionally be created, and MUST be named ``config.ini`` and
+placed in the job directory.
+
+Available config options:
+
+::
+
+ [build]
+ HALT_ON_FAILURE = True|False
+ WARN_ON_FAILURE = True|False
+ CONTAINER_BUILD = True|False
+ CONTAINER_NAME = <string>
+ CONTAINER_ARCH = <string>
+
+
+Unless *all* jobs specify a "CONTAINER_NAME" in their custom config a
+container file **MUST** be present at ``<repo_root>/contrib/ci/Containerfile``.
+The container file will be built and used to run all of a repo's jobs
+by default.
+
+All projects SHOULD have a ``build`` step and a ``test`` step, at a minimum.
+
+Running CI Locally
+------------------
+
+Running the CI scripts locally can be useful for development and testing.
+
+Included in each CI directory is a script which simplifies running jobs
+in the same way the CI Worker does, in containers, using ``podman``.
+
+::
+
+ # Usage:
+ ./contrib/ci/ci.sh <job-name>
+
+ # For example, if the CI jobs tree looks like this:
+ ./contrib/ci/jobs
+ ├── 0-codespell/
+ ├── 1-build/
+ ├── 2-test/
+ ├── 3-docs/
+ ├── 4-deb-package/
+ └── 5-deploy-package/
+
+ # Then you can run job '0-codespell' as follows:
+ ./contrib/ci/ci.sh 0-codespell
+
+ # If you are using podman and have "qemu-user-binfmt" installed
+ # then you may attempt to run any job under an alternative CPU
+ # architecture by providing a second argument.
+ # For example:
+ ./contrib/ci/ci.sh 0-codespell arm64
+
+
+Additional Builders
+-------------------
+
+To run some tests there is a need for many or most project's sourcecode to be
+available in the same environment. This will be a separate builder/pipeline
+from the per-repo builders. Triggers for this builder are yet to be
+determined.
diff --git a/design-documents/045-kyc-inheritance.rst b/design-documents/045-kyc-inheritance.rst
new file mode 100644
index 00000000..f33e7ee3
--- /dev/null
+++ b/design-documents/045-kyc-inheritance.rst
@@ -0,0 +1,186 @@
+DD 45: Single-Depth Inheritance of KYC for Reserves
+###################################################
+
+Summary
+=======
+
+This document presents and discusses a mechanism by which a reserve A can
+provide KYC attestation for another reserve B, whenever A's KYC attestation is
+the result of a proper KYC-process and not inherited itself. During the
+transitive attestation process, A can change the birthday for reserve B become
+younger, i.e. choose a date closer to the current date than the original
+birthday.
+
+
+Motivation
+==========
+
+There are two reasons that motivate our proposal:
+
+#. KYC attestation of a reserve is a must for Peer-2-Peer payments. However,
+ a KYC process is usually costly for the exchange and -ultimately- the
+ customer. When a customer has multiple long-term reserves, it should be
+ possible to inherit the KYC attestation from one to another.
+
+#. A parent should be able to provide KYC attestation for the long-term reserve
+ of his or her child and at the same time provide appropriate birthday
+ information. That way, the child can withdraw money from its reserve
+
+ - with appropriate and evolving age-restriction always in place,
+
+ - no further age-commitment interaction with the parent.
+
+With the ability to attest KYC transitively, we can reduce the cost of
+ownership of long-term reserves and enable the principle of subsidiarity,
+according to which parents are responsible for age-restriction settings.
+
+
+Requirements
+============
+
+* none
+
+Proposed Solution
+=================
+
+There are changes in the exchange and in the wallet necessary.
+
+Changes in the Exchange
+^^^^^^^^^^^^^^^^^^^^^^^
+
+A new configuration option in the TALER-configuration defines the maximum
+number of attestations that a (KYC'ed) reserve can provide for other reserves.
+
+.. code:: none
+
+ [kyc-legitimization-inheritance]
+ MAXIMUM_ATTESTATIONS = [number]
+
+
+The database schema needs to be adjusted to incorporate
+
+* a boolean field ``inherited`` in the table ``kyc_attributes`` to indicate the
+ inheritance status
+
+* an integer field ``attestations`` in the table ``reserves`` to count the
+ number of transitive attestations performed by a reserve.
+
+
+On the exchange we propose the following new endpoint:
+
+
+.. http:post:: /kyc-attest/$TARGET_RESERVE_PUB
+
+**Request:**
+
+The request body must be a `KYCAttestationRequest` object.
+
+**Response:**
+
+ :http:statuscode:`200 OK`:
+ Both, the attesting and the target reserves were known to the exchange, and the
+ KYC data of the attesting reserve has been successfully inherited by the
+ target reserve (with optionally adjusted birthday).
+ :http:statuscode:`403 Forbidden`:
+ The attesting reserve is not allowed to transitively attest another reserve.
+ This is because the attesting reserve either
+
+ #. lacks KYC attestation or
+
+ #. its KYC attestation was itself inherited or
+
+ #. has reached the allowed maximum number of transitive attestations.
+
+ The response comes with a standard `ErrorDetail`.
+ :http:statuscode:`404 Not found`:
+ One of the reserve keys belongs to a reserve which is unknown to the exchange.
+ The response comes with a standard `ErrorDetail`, containing the unknown key.
+ :http:statuscode:`409 Conflict`:
+ The birthday in the request is not acceptable, as it points to an earlier
+ point in time (more distant from current time) than the birthday of the
+ attesting reserve.
+
+**Details:**
+
+.. ts:def:: KYCAttestationRequest
+
+ interface KYCAttestationRequest {
+ // An optional birthday for the target reserve. MUST be equal or younger
+ // (more recent to current time) than the birthday of the attesting
+ // reserve.
+ birthday?: FuzzyDateString;
+
+ // The public key of the attesting reserve
+ attester_pub: EddsaPublicKey;
+
+ // Signature of purpose
+ // ``TALER_SIGNATURE_WALLET_RESERVE_TRANSITIVE_KYC`` over
+ // a `TALER_ReserveKYCAttestationPS`.
+ attester_sig: EddsaSignature;
+ }
+
+
+.. ts:def:: FuzzyDateString
+
+ // A date of the form "YYYY-MM-DD","YYYY-MM-00" or "YYYY-00-00".
+ // "YYYY-MM-00" will be evaluated as "YYYY-MM-01" and
+ // "YYYY-00-00" will be evaluated as "YYYY-01-01".
+ type FuzzyDateString = string;
+
+
+.. _TALER_ReserveKYCAttestationPS:
+.. sourcecode:: c
+
+ struct TALER_ReserveKYCAttestationPS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_WALLET_RESERVE_TRANSITIVE_KYC
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_ReservePublicKeyP attester_reserve_pub;
+ struct TALER_ReservePublicKeyP target_reserve_pub;
+ /* If no birthday is set, must be all 0 */
+ char birthday[sizeof("YYYY-MM-DD")];
+ }
+
+
+Changes in the Wallet
+^^^^^^^^^^^^^^^^^^^^^
+
+We need a workflow for the attestation of one wallet through another.
+
+TODO.
+
+
+Definition of Done
+==================
+
+For the exchange, the implementation of the configuration option and the
+endpoint, and corresponding unit tests in ``src/testing`` are necessary.
+
+
+For the wallet: TODO.
+
+Alternatives
+============
+
+* KYC for a reserve can only be provided by a full KYC legitimization process.
+
+Drawbacks
+=========
+
+Other than adding code to the exchange: unknown.
+
+Discussion / Q&A
+================
+
+The proposed solution makes the principle of subsidiarity for age-restrictions
+(i.e parents are responsible for setting the age-restriction) explicit in the
+code.
+
+It also simplifies the KYC process for many situations for customers: Families
+members and partners benefit from it.
+
+However, the proposed solution still allows for other ways to set
+age-restriction in the wallet. For example, parents who do **not** have a
+Taler wallet, are still able to assist their children with the settings of
+age-restriction during the withdraw process.
diff --git a/design-documents/046-mumimo-contracts.rst b/design-documents/046-mumimo-contracts.rst
new file mode 100644
index 00000000..8cb35316
--- /dev/null
+++ b/design-documents/046-mumimo-contracts.rst
@@ -0,0 +1,709 @@
+DD 46: Contract Format v1
+#########################
+
+Summary
+=======
+
+The contract v1 format enables a multitude of advanced interactions between
+merchants and wallets, including donations, subscriptions, coupons, currency
+exchange and more.
+
+Motivation
+==========
+
+The existing v0 contract format is too simplistic to
+support many frequenly requested types of contracts.
+
+Requirements
+============
+
+We want Taler to support various interesting use-cases:
+
+ - Unlinkable, uncopyable subscriptions without accounts (reader can pay with
+ Taler to subscribe to online publication, read unlimited number of
+ articles during a certain period, transfer subscription to other devices,
+ maintain unlinkability / full anonymity amongst all anonymous
+ subscribers).
+
+ - Coupons, discounts and stamps -- like receiving a discount on a product,
+ product basket or subscription -- based on previous purchase(s). Again,
+ with unlinkability and anonymity (modulo there being other users eligible
+ for the discount).
+
+ - Subscription tokens lost (due to loss of device without backup) should
+ be recoverable from any previous backup of the subscription.
+
+ - Currency conversion, that is exchanging one currency for another.
+
+ - Donations, including privacy-preserving tax receipts that prove that the
+ user donated to an entity that is eligible for tax-deductions but without
+ revealing which entity the user donated to. At the same time, the entity
+ issuing the tax receipt must be transparent (to the state) with respect to
+ the amount of tax-deductable donations it has received.
+
+ - Throttled political donations where each individual is only allowed to
+ donate anonymously up to a certain amount per year or election cycle.
+
+ - Unlinkable gifts -- enabling the purchase of digital goods (such as
+ articles, albums, etc.) to be consumed by a third party. For example, a
+ newspaper subscription may include a fixed number of articles that can be
+ gifted to others each week, all while maintaining unlinkability and
+ anonymity between the giver and the recipient.
+
+ - Temporally-constrained, unlinkable event ticketing. Allowing visitors to
+ use Taler to purchase a ticket for an event. This ticket grants entry and
+ exit privileges to the event location during a specified time window, while
+ preserving the anonymity of the ticket holder (within the group of all the
+ ticket holders).
+
+ - Event deposit systems. A deposit mechanism for events where customers
+ receive a token alongside their cup or plate, which they are expected to
+ return. This system validates that the cup or plate was legitimately
+ acquired (i.e., not brought from home or stolen from a stack of dirty items)
+ and incentivizes return after use.
+
+
+Proposed Solution
+=================
+
+Merchants will also blindly sign tokens (not coins) to indicate the
+eligibility of a user for certain special offers. Contracts will be modified
+to allow requiring multiple inputs (to be *provisioned* to the merchant) and
+multiple outputs (to be *yielded* by the merchant). The wallet will then allow
+the user to select between the choices that the user could pay for, or possibly
+make an automatic choice if the correct choice is obvious. One output option is
+blindly signed coins from another exchange, possibly in a different currency.
+Another output option is blindly signed donation receipts from a DONation
+AUthority (DONAU). Subscriptions can be modeled by requiring the wallet to
+provision a token of the same type that is also yielded by the contract. For
+security, payments using subscription tokens (and possibly certain other special
+tokens?) will be limited to a list of domains explicitly defined as trusted by
+the token issuer. When paying for a contract, the wallet must additionally sign
+over the selected sub-contract index and a hash committing it to the blinded
+envelopes (if any). The merchant backend will (probably?) need to be changed
+to truly support multiple currencies (ugh).
+
+.. _contract-terms-v1:
+
+New Contract Terms Format
+-------------------------
+
+The contract terms v1 will have the following structure:
+
+.. ts:def:: ContractTermsV1
+
+ interface ContractTermsV1 {
+
+ // This is version 1, the previous contract terms SHOULD
+ // be indicated using "0", but in v0 specifying the version
+ // is optional.
+ version: "1";
+
+ // Unique, free-form identifier for the proposal.
+ // Must be unique within a merchant instance.
+ // For merchants that do not store proposals in their DB
+ // before the customer paid for them, the ``order_id`` can be used
+ // by the frontend to restore a proposal from the information
+ // encoded in it (such as a short product identifier and timestamp).
+ order_id: string;
+
+ // Price to be paid for the transaction. Could be 0.
+ // The price is in addition to other instruments,
+ // such as rations and tokens.
+ // The exchange will subtract deposit fees from that amount
+ // before transferring it to the merchant.
+ price: Amount;
+
+ // URL where the same contract could be ordered again (if
+ // available). Returned also at the public order endpoint
+ // for people other than the actual buyer (hence public,
+ // in case order IDs are guessable).
+ public_reorder_url?: string;
+
+ // Time when this contract was generated.
+ timestamp: Timestamp;
+
+ // After this deadline, the merchant won't accept payments for the contract.
+ pay_deadline: Timestamp;
+
+ // Transfer deadline for the exchange. Must be in the
+ // deposit permissions of coins used to pay for this order.
+ wire_transfer_deadline: Timestamp;
+
+ // Merchant's public key used to sign this proposal; this information
+ // is typically added by the backend. Note that this can be an ephemeral key.
+ merchant_pub: EddsaPublicKey;
+
+ // Base URL of the (public!) merchant backend API.
+ // Must be an absolute URL that ends with a slash.
+ merchant_base_url: string;
+
+ // More info about the merchant (same as in v0).
+ merchant: Merchant;
+
+ // Human-readable description of the contract.
+ summary: string;
+
+ // Map from IETF BCP 47 language tags to localized summaries.
+ summary_i18n?: { [lang_tag: string]: string };
+
+ // URL that will show that the order was successful after
+ // it has been paid for. Optional. When POSTing to the
+ // merchant, the placeholder "${ORDER_ID}" will be
+ // replaced with the actual order ID (useful if the
+ // order ID is generated server-side and needs to be
+ // in the URL).
+ // Note that this placeholder can only be used once.
+ // Either fulfillment_url or fulfillment_message must be specified.
+ fulfillment_url?: string;
+
+ // Message shown to the customer after paying for the order.
+ // Either fulfillment_url or fulfillment_message must be specified.
+ fulfillment_message?: string;
+
+ // Map from IETF BCP 47 language tags to localized fulfillment
+ // messages.
+ fulfillment_message_i18n?: { [lang_tag: string]: string };
+
+ // List of products that are part of the purchase (see `Product`).
+ products: Product[];
+
+ // After this deadline has passed, no refunds will be accepted.
+ refund_deadline: Timestamp;
+
+ // Specifies for how long the wallet should try to get an
+ // automatic refund for the purchase. If this field is
+ // present, the wallet should wait for a few seconds after
+ // the purchase and then automatically attempt to obtain
+ // a refund. The wallet should probe until "delay"
+ // after the payment was successful (i.e. via long polling
+ // or via explicit requests with exponential back-off).
+ //
+ // In particular, if the wallet is offline
+ // at that time, it MUST repeat the request until it gets
+ // one response from the merchant after the delay has expired.
+ // If the refund is granted, the wallet MUST automatically
+ // recover the payment. This is used in case a merchant
+ // knows that it might be unable to satisfy the contract and
+ // desires for the wallet to attempt to get the refund without any
+ // customer interaction. Note that it is NOT an error if the
+ // merchant does not grant a refund.
+ auto_refund?: RelativeTime;
+
+ // Delivery location for (all!) products (same as in v0).
+ delivery_location?: Location;
+
+ // Time indicating when the order should be delivered.
+ // May be overwritten by individual products.
+ delivery_date?: Timestamp;
+
+ // Nonce generated by the wallet and echoed by the merchant
+ // in this field when the proposal is generated.
+ // Note: required in contract, absent in order!
+ nonce: string;
+
+ // Array of possible specific contracts the wallet/customer
+ // may choose from by selecting the respective index when
+ // signing the deposit confirmation.
+ choices: ContractChoice[];
+
+ // Map from token family slugs to meta data about the
+ // respective token family.
+ token_families: { [token_family_slug: string]: TokenFamily };
+
+ // Extra data that is only interpreted by the merchant frontend.
+ // Useful when the merchant needs to store extra information on a
+ // contract without storing it separately in their database.
+ extra?: any;
+
+ // Maximum total deposit fee accepted by the merchant for this contract.
+ max_fee: Amount;
+
+ // Exchanges that the merchant accepts for this currency.
+ exchanges: Exchange[];
+
+ }
+
+.. ts:def:: ContractChoice
+
+ interface ContractChoice {
+
+ // List of inputs the wallet must provision (all of them) to
+ // satisfy the conditions for the contract.
+ inputs: ContractInput[];
+
+ // List of outputs the merchant promises to yield (all of them)
+ // once the contract is paid.
+ outputs: ContractOutput[];
+
+ }
+
+.. ts:def:: ContractInput
+
+ type ContractInput =
+ | ContractInputRation
+ | ContractInputToken;
+
+.. ts:def:: ContractInputRation
+
+ interface ContractInputRation {
+
+ type: "coin";
+
+ // Price to be paid for the transaction.
+ price: Amount;
+
+ // FIXME-DOLD: do we want to move this into a 'details'
+ // sub-structure as done with tokens below?
+ class: "ration";
+
+ // Base URL of the ration authority.
+ ration_authority_url: string;
+
+ };
+
+.. ts:def:: ContractInputToken
+
+ interface ContractInputToken {
+
+ type: "token";
+
+ // Slug of the token family in the
+ // 'token_families' map on the top-level.
+ token_family_slug: string;
+
+ // Number of tokens of this type required.
+ // Defaults to one if the field is not provided.
+ number?: Integer;
+
+ };
+
+.. ts:def:: ContractOutput
+
+ type ContractOutput =
+ | ContractOutputCoin
+ | ContractOutputTaxReceipt
+ | ContractOutputToken;
+
+.. ts:def:: ContractOutputCoin
+
+ interface ContractOutputCoin {
+
+ type: "coins";
+
+ // Amount of coins that will be yielded.
+ // This excludes any applicable withdraw fees.
+ brutto_yield: Amount;
+
+ // Base URL of the exchange that will issue the
+ // coins.
+ exchange_url: string;
+
+ };
+
+.. ts:def:: ContractOutputTaxReceipt
+
+ interface ContractOutputTaxReceipt {
+
+ type: "tax-receipt";
+
+ // Base URL of the donation authority that will
+ // issue the tax receipt.
+ donau_url: string;
+
+ };
+
+.. ts:def:: ContractOutputToken
+
+ interface ContractOutputToken {
+
+ type: "token";
+
+ // Slug of the token family in the
+ // 'token_families' map on the top-level.
+ token_family_slug: string;
+
+ // Start of the validity period of the token.
+ valid_after: Timestamp;
+
+ // Number of tokens to be yelded.
+ // Defaults to one if the field is not provided.
+ number?: Integer;
+
+ }
+
+.. ts:def:: TokenClass
+
+ type TokenClass =
+ | TokenClassSubscription
+ | TokenClassDiscount
+
+.. ts:def:: TokenClassSubscription
+
+ interface TokenClassSubscription {
+
+ class: "subscription";
+
+ // Array of domain names where this subscription
+ // can be safely used (e.g. the issuer warrants that
+ // these sites will re-issue tokens of this type
+ // if the respective contract says so). May contain
+ // "*" for any domain or subdomain.
+ trusted_domains: string[];
+
+ };
+
+.. ts:def:: TokenClassDiscount
+
+ interface TokenClassDiscount {
+
+ class: "discount";
+
+ // Array of domain names where this discount token
+ // is intended to be used. May contain "*" for any
+ // domain or subdomain. Users should be warned about
+ // sites proposing to consume discount tokens of this
+ // type that are not in this list that the merchant
+ // is accepting a coupon from a competitor and thus
+ // may be attaching different semantics (like get 20%
+ // discount for my competitors 30% discount token).
+ expected_domains: string[];
+
+ };
+
+
+.. ts:def:: TokenFamily
+
+ interface TokenFamily {
+
+ // Human-readable name of the token family.
+ name: string;
+
+ // Human-readable description of the semantics of
+ // this token family (for display).
+ description: string;
+
+ // Map from IETF BCP 47 language tags to localized descriptions.
+ description_i18n?: { [lang_tag: string]: string };
+
+ // Public keys used to validate tokens issued by this token family.
+ keys: TokenSignerPublicKeyGroup;
+
+ // Class-specific information of the token
+ details: TokenClass;
+
+ // Must a wallet understand this token type to
+ // process contracts that consume or yield it?
+ critical: boolean;
+
+ // Number of tokens issued according to ASS authority
+ // FIXME: this is still rather speculative in the design...
+ ass?: Integer;
+
+ // Signature affirming sum of token issuance deposit (?) fees
+ // collected by an exchange according to the ASS authority.
+ // FIXME: this is still rather speculative in the design...
+ ass_cost?: Amount;
+
+ // Signature affirming the ass by the ASS authority.
+ // FIXME: this is still rather speculative in the design...
+ ass_sig?: EddsaSignature;
+
+ };
+
+
+.. ts:def:: TokenSignerPublicKeyGroup
+
+ type TokenSignerPublicKeyGroup =
+ | TokenSignerPublicKeyGroupRsa
+ | TokenSignerPublicKeyGroupCs;
+
+.. ts:def:: TokenSignerPublicKeyGroupRsa
+
+ interface TokenSignerPublicKeyGroupRsa {
+ cipher: "RSA";
+
+ public_keys: {
+ // RSA public key.
+ rsa_pub: RsaPublicKey;
+
+ // Start time of this key's validity period.
+ valid_after?: Timestamp;
+
+ // End time of this key's validity period.
+ valid_before: Timestamp;
+ }[];
+ }
+
+.. ts:def:: TokenSignerPublicKeyGroupCs
+
+ interface TokenSignerPublicKeyGroupCs {
+ cipher: "CS";
+
+ public_keys: {
+ // CS public key.
+ cs_pub: Cs25519Point;
+
+ // Start time of this key's validity period.
+ valid_after?: Timestamp;
+
+ // End time of this key's validity period.
+ valid_before: Timestamp;
+ }[];
+ }
+
+
+Alternative Contracts
+---------------------
+
+The contract terms object may contain any number of alternative contracts that
+the user must choose between. The alternatives can differ by inputs, outputs
+or other details. The wallet must filter the contracts by those that the user
+can actually pay for, and move those that the user could currently not pay for
+to the end of the rendered list. Similarly, the wallet must move up the
+cheaper contracts, so if a contract has a definitively lower price and
+consumes an available discount token, that contract should be moved up in the
+list.
+
+Which specific alternative contract was chosen by the user is indicated in the
+``choice_index`` field of the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`.
+
+
+Output Commitments
+------------------
+
+When a contract has outputs, the wallet must send an array of blinded tokens,
+coins or tax receipts together with the payment request. The order in the
+array must match the order in the outputs field of the contract. For currency
+outputs, one array element must include all of the required planchets for a
+batch withdrawal, but of course not the reserve signature.
+
+ .. note::
+
+ We can probably spec this rather nicely if we first change the
+ batch-withdraw API to only use a single reserve signature.
+
+This array of blinded values is hashed to create the output commitment hash
+(``h_outputs``) in the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`.
+
+
+
+Subscriptions
+-------------
+
+The user buys a subscription (and possibly at the same time an article) using
+currency and the contract yields an additional subscription token as an
+output. Active subscriptions are listed below the currencies in the wallet
+under a new heading. Subscriptions are never auto-renewing, if the user wants
+to extend the subscription they can trivially pay for it with one click.
+
+When a contract consumes and yields exactly one subscription
+token of the same type in a trusted domain, the wallet may automatically
+approve the transaction without asking the user for confirmation (as it is free).
+
+The token expiration for a subscription can be past the "end date" to enable a
+previous subscription to be used to get a discount on renewing the
+subscription. The wallet should show applicable contracts with a lower price
+that only additionally consume subscription tokens after their end date before
+higher-priced alternative offers.
+
+Subscription tokens are "critical" in that a wallet implementation must
+understand them before allowing a user to interact with this class of token.
+Subscription token secrets should be derived from a master secret associated
+with the subscription, so that the private keys are recoverable from backup.
+To obtain the blind signatures, a merchant must offer an endpoint where
+one can submit the public key of the N-1 subscription token and obtain the
+blinded signature over the N-th subscription token. The wallet can then
+effectively recover the subscription from backup using a binary search.
+
+The merchant SPA should allow the administrator to create (maybe update) and
+delete subscriptions. Each subscription is identified by a subscription
+label and includes a validity period.
+
+The merchant backend must then automatically manage (create, use, delete) the
+respective signing keys. When creating an order, the frontend can just refer
+to the subscription label (and possibly a start date) in the inputs or
+outputs. The backend should then automatically substitute this with the
+respective cryptographic fields for the respective time period and
+subscription label.
+
+
+
+
+Discounts
+---------
+
+To offer a discount based on one or more previous purchases, a merchant must
+yield some discount-specific token as an output with the previous purchase,
+and then offer an alternative contract with a lower price that consumes currency
+and the discount token. The wallet should show contracts with a lower price that
+only additionally consume discount tokens
+
+The merchant SPA should allow the administrator to create (maybe update) and
+delete discount tokens. Each discount token is identified by a discount
+label and includes an expiration time or validity duration.
+
+The merchant backend must then automatically manage (create, use, delete) the
+respective signing keys. When creating an order, the frontend can just refer
+to the discount token label in the inputs or outputs. The backend should then
+automatically substitute this with the respective cryptographic fields for the
+respective discount token.
+
+
+Donation Authority
+------------------
+
+A donation authority (DONAU) implements a service that is expected to be run
+by a government authority that determines eligibility for tax deduction. A
+DONAU blindly signs tax receipts using a protocol very close to that of the
+Taler exchange's withdraw protocol, except that the reserves are not filled
+via wire transfers but instead represent accounts of the organizations
+eligible to issue tax deduction receipts. These accounts are bascially
+expected to have only negative balances, but the DONAU may have a
+per-organization negative balance limit to cap tax deduction receipt
+generation to a plausible account. DONAU administators are expected to be
+able to add, update or remove these accounts using a SPA. Tax receipts
+are blindly signed by keys that always have a usage period of one calendar
+year.
+
+A stand-alone app for tax authorities can scan QR codes representing DONAU
+signatures to validate that a given tax payer has donated a certain amount.
+As RSA signatures are typically very large and a single donation may require
+multiple blind signatures, CS blind signatures must also be supported. To
+avoid encoding the public keys, QR codes with tax receipts should reference
+the DONAU, the year and the amount, but not the specific public key. A
+single donation may nevertheless be rendered using multiple QR codes.
+
+Revocations, refresh, deposits, age-restrictions and other exchange features
+are not applicable for a DONAU.
+
+The merchant SPA should allow the administrator to manage DONAU accounts in
+the merchant backend. Each DONAU account includes a base URL and a private
+signing key for signing the requests to the DONAU on behalf of the eligible
+organization.
+
+When creating an order, the frontend must specify a configured DONAU base URL
+in the outputs. The backend should then automatically interact with the DONAU
+when the wallet supplies the payment request with the blinded tax receipts.
+The DONAU interaction must only happen after the exchange confirmed that the
+contract was successfully paid. A partial error must be returned if the
+exchange interaction was successful but the DONAU interaction failed. In this
+case, the fulfillment action should still be triggered, but the wallet should
+display a warning that the donation receipt could not be obtained. The wallet
+should then re-try the payment (in the background with an exponential
+back-off) to possibly obtain the tax receipt at a later time.
+
+
+Tax Receipts
+------------
+
+Tax receipts differ from coins and tokens in that what is blindly signed over
+should be the taxpayer identification number of the tax payer. The format of
+the taxpayer identification number should simply be a string, with the rest
+being defined by the national authority. The DONAU should indicate in its
+``/config`` response what format this string should have, using possibly both
+an Anastasis-style regex and an Anastasis-style function name (to check things
+like checksums that cannot be validated using a regex). Wallets must then
+validate the regex (if given) and if possible should implement the
+Anastasis-style logic.
+
+Wallets should collect tax receipts by year and offer an
+export functionality. The export should generate either
+
+ (a) a JSON file,
+ (b) a PDF (with QR codes), or
+ (c) a series of screens with QR codes.
+
+Wallets may only implement some of the above options due to resource
+constraints.
+
+The documents should encode the taxpayer ID, the amount and the DONAU
+signature (including the year, excluding the exact public key as there should
+only be one possible).
+
+
+Rationing (future work)
+-----------------------
+
+If per-capita rationing must be imposed on certain transactions, a rationing
+authority (RA) must exist that identifies each eligible human and issues that
+human a number of ration coins for the respective rationing period. An RA
+largely functions like a regular exchange, except that eligible humans will
+need to authenticate directly to withdraw rations (instead of transferring
+fiat to an exchange). Merchants selling rationed goods will be (legally)
+required to collect deposit confirmations in proportion to the amount of
+rationed goods sold. A difference to regular exchanges is that RAs do not
+charge any fees. RAs may or may not allow refreshing rations that are about
+to expire for ration coins in the next period.
+
+Once an RA is added to a wallet, it should automatically try to withdraw the
+maximum amount of ration coins it is eligible for. Available rations should be
+shown below the subscriptions by RA (if any).
+
+ ..note::
+
+ RAs are considered an idea for future work and not part of our current timeline.
+
+
+Limited Donations per Capita (future work)
+------------------------------------------
+
+If per-capita limitations must be imposed on anonymous donations (for example
+for donations to political parties), an RA can be used to issue donation
+rations that limit the amount of donations that can be made for the respective
+period.
+
+ ..note::
+
+ RAs are considered an idea for future work and not part of our current timeline.
+
+
+
+Definition of Done
+==================
+
+ - Merchant backend support for multiple currencies
+ - Merchant backend support for consuming and issuing tokens
+ - Merchant SPA support for configuring new tokens of different types
+ - Wallet-core support for various new contract types
+ - Wallet-core filters for feasible contracts and possibly auto-executes subscriptions
+ - Wallet-GUIs (WebEx, Android, iOS) render new contract types
+ - Wallet-GUIs (WebEx, Android, iOS) allow user to select between multiple contracts
+ - Documentation for developers is up-to-date
+ - Token anonymity set size (ASS) authority implemented, documented
+ - Merchants report anonymity set size increases to ASS authority
+ - Wallets process anonymity set size reports from ASS authority
+ - Bachelor thesis written on applications and design
+ - Academic paper written on DONAU (requirements, design, implementation)
+ - DONAU implemented, documented
+ - DONAU receipt validation application implemented
+ - Integration tests exist in wallet-core
+ - Deliverables accepted by EC
+
+While rationing is part of the design, we expect the actual implementation to
+be done much later and thus should not consider it part of the "DONE" part.
+Rationing is complex, especially as a refunded contract should probably also
+refund the ration.
+
+
+Alternatives
+============
+
+The first draft of this DD included the capability of paying with multiple
+currencies for the same contract (for example, USD:1 and EUR:5) plus tokens
+and rations. However, this is very complex, both for wallets (how to display),
+for other merchant APIs (does the refund API have to become multi-currency as
+well?) and there does not seem to be a good business case for it. So for now,
+the price is always only in one currency.
+
+
+Drawbacks
+=========
+
+Significant change, but actually good ratio compared to use-cases covered.
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/047-stefan.rst b/design-documents/047-stefan.rst
new file mode 100644
index 00000000..4cef1865
--- /dev/null
+++ b/design-documents/047-stefan.rst
@@ -0,0 +1,172 @@
+DD 47: STEFAN
+#############
+
+Summary
+=======
+
+The Standardized Template for Effective-Fee Approximation Numbers (STEFAN)
+is a feature to ensure customers see consistent fees for equivalent
+purchases (largely) independent of the specific coins selected. It will also
+make it easier for merchants to configure their systems to pay all reasonable
+fees.
+
+
+Motivation
+==========
+
+Taler has a logarithmic fee structure for good reasons (to compete in
+different market segments with reasonable profit margins). However, the
+logarithmic fee structure inherently implies that the specific coin selection
+made by the wallet can result in very different fees being applicable for the
+same amount at the same merchant merely due to different coins being available
+in the wallet. To minimize support costs, it is important that customers do
+not need to be aware of the concept of coins and are instead shown consistent
+fees for equivalent transactions.
+
+
+Requirements
+============
+
+ - keep the logarithmic nature of the fees (proportionally high fees
+ for tiny amounts, medium fees for medium amounts, low fees for large amounts)
+ - same purchase, same perceived price; prices are predictable for users
+ - enable merchants to easily cover all fees in most cases
+
+
+Proposed Solution
+=================
+
+The proposal is for the exchange to advertise three STEFAN-parameters that
+encode a fee curve of the form ``stefan_abs + stefan_log * log P +
+stefan_lin * P`` where P represents the gross price to be paid.
+Here, the numerical value for P is to be computed by
+dividing the actual gross price by the smallest denomination
+offered by the exchange.
+
+.. note::
+
+ This calculation is already done using floating point (!) as we want the
+ STEFAN-curve to be smooth and not a step-function. This is also needed so
+ that we can invert the computation and calculate gross amounts from net
+ amounts and actually get a nice invertible computation where both
+ directions always match. Note that the computation itself is nevertheless
+ non-trivial involving Newton's method to solve ``f(x)=0`` using a
+ well-estimated starting point for the iteration to avoid divergence issues.
+ Finally, while we do the STEFAN-curve computations using doubles, we should
+ then convert the final amounts back into "human-friendly" numbers rounding
+ towards the nearest value that can be represented using the canonical
+ number of decimal places at the exchange. libtalerexchange (C) has a
+ reference implementation of the gross-net conversion functions (in both
+ directions) and of the final rounding logic. This exception to the general
+ rule of no floating-points for amounts is acceptable as it is not actually
+ done at the protocol level but only in internal computations of the wallet
+ and merchant backend as part of the STEFAN cost estimation logic, which by
+ definition is an estimate and does not need to be exact. (And even if
+ wallet and merchant backend were to (slightly) disagree about the
+ computations due to different floating point implementations, everything
+ would still be fine, and even a significant disagreement would not cause
+ anything but resurface the UI issue the STEFAN-curves addresses.)
+
+The fee curve will be the displayed fee, except in cases where the coin
+selection is exceptionally bad (which should happen in substantially less than
+1% of all cases). The fee curve will also be used as the maximum fee a
+merchant will cover unless the merchant overrides the mechanism.
+
+In the most common case, where the STEFAN-curve fee is at or below what the
+merchant covers, no fees are displayed, except in the exceptionally rare
+case where the actual fees (due to unfortunate coin selection) are above
+both the exchange's STEFAN-curve and the what the merchant covers. In this
+last case the fees shown will be the actual fees minus what the merchant
+covers and here the fees may vary even for equivalent transactions.
+
+In the uncommon case where a merchant does not cover any fees or only covers
+parts of the fee according to the STEFAN-curve, the displayed fee will be the
+value on the STEFAN-curve minus the amount covered by the merchant. If the
+actual fees paid end up being below the approximation by the STEFAN-curve, the
+delta is (by default) *hidden* from the balance of the user to simulate a
+consistent fee.
+
+However, only the total available balance is marked down based on the
+STEFAN-curve value. Thus, the wallet will contains coins with a potentially
+higher balance value than what is shown to the user. This difference is
+reconciled annually by adding a special transaction that increases the wallet
+balance to eliminate the difference without any actual network interaction.
+The entry in the transaction history that boosts the balance links to an
+explanation. We may consider suggesting the user to donate the windfall.
+
+In developer mode, the user should probably be given the choice to see the
+delta, to disable the feature, and/or to force the windfall transaction to
+happen immediately.
+
+
+Computing the curve
+-------------------
+
+Typically, the ``stefan_abs`` value should represent a single wire transfer
+fee. The ``stefan_log`` value should be computed to approximate the deposit
+(and if applicable) refresh and withdraw fees for a coin, to be multiplied by
+the number of coins. In a canonical setup, ``stefan_lin`` would be zero.
+However, if an exchange is configured to use a linear fee structure, then
+``stefan_lin`` would become applicable.
+
+The taler-wallet-cli should have an option to compute the STEFAN-values
+given a denomination fee structure. This computation could probably be done
+either analytically (if the fee structure is systematic) or by simulation.
+
+Modifications to the merchant
+-----------------------------
+
+Instead of having (just) a "default fee", merchants should have an option to
+use the STEFAN-curve when computing the fees they would be willing to cover.
+
+Modifications to the exchange
+-----------------------------
+
+The STEFAN-curve can be configured using three simple configuration values
+in the ``[exchange]`` section. The resulting values should be shared as
+part of the ``/keys`` response, without digital signature.
+
+Modifications to the wallets
+----------------------------
+
+The STEFAN-curves will be useful as an easy approximate way to compare
+exchange fee structures. However, wallets may not want to just trust an
+exchange to honestly report STEFAN-curve values but could possibly use
+a simulation to check that the given STEFAN-curve matches the actual fees.
+
+Wallets will need to keep the hidden STEFAN-balance and add the annual
+internal reconcilliation transaction.
+
+Wallets will need to compute both the STEFAN-fee for display and still
+do their own internal actual coin selection to minimize fees.
+
+
+
+Definition of Done
+==================
+
+ - exchange modified (DONE)
+ - merchant understands STEFAN curve in backend (DONE)
+ - merchant SPA has configuration option to enable use of STEFAN-curves
+ - wallet-core uses STEFAN-curves to compute display fees
+ - wallet-core supports annual reconcilliation transaction
+ - wallet GUIs use STEFAN-curves when comparing exchange fee structures
+
+
+Alternatives
+============
+
+Refresh fees could additionally be waived if the refresh operation yields coins
+of a lower denomination than the original coin. We should check if this allows
+us to define tighter STEFAN-curves.
+
+
+Drawbacks
+=========
+
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/048-wallet-exchange-lifecycle.rst b/design-documents/048-wallet-exchange-lifecycle.rst
new file mode 100644
index 00000000..75ec3afb
--- /dev/null
+++ b/design-documents/048-wallet-exchange-lifecycle.rst
@@ -0,0 +1,144 @@
+DD 48: Wallet Exchange Lifecycle and Management
+###############################################
+
+Summary
+=======
+
+This design document covers the lifecycle and management
+of exchanges in the wallet.
+
+Motivation
+==========
+
+The current wallet implementation lacks requests to manage exchanges.
+It also always fetches the /keys info of all exchanges, because it doens't
+distinguish between used and preset/added exchanges.
+
+Requirements
+============
+
+The following properties of of exchanges managed by the wallet
+are important:
+
+* is the exchange used for something (coins, (account-)reserves, purses, ...)
+* did the user (ever) accept the ToS?
+* does the exchange have newer ToS available?
+* is a current version of /keys (including former /wire) downloaded?
+* were there errors downloading or checking the keys info?
+* is the exchange permanently added or ephemeral?
+
+ * ephemeral exchange records are created when the user
+ checks fees of an exchange but doesn't use it,
+ they would typically be hidden in the UI
+
+
+Proposed Solution
+=================
+
+Exchange Entry Status
+---------------------
+
+The wallet exposes three separate status fields for each exchange entry:
+
+* the entry status
+* the update status
+* the ToS status
+
+
+Entry Status
+~~~~~~~~~~~~
+
+* ``preset``: Exchange record has been added to the exchange (typically as
+ a hardcoded preset in wallet-core). While the exchange can be selected for operations,
+ the wallet doesn't update the status yet, i.e. no /keys queries are done.
+* ``ephemeral``: Exchange has been updated (or update has been attempted) at
+ least once (for example as part of checking the fees for a transaction using
+ the exchange). However, the exchange is not yet used for any resource in the wallet.
+ In this state, the exchange record will be garbage-collected eventually.
+* ``used``: The exchange is known and used, the wallet regularly queries /keys.
+
+Transitions:
+
+* A transition from ``used`` to ``ephemeral`` is not possible,
+ since this would eventually remove preset exchanges and likely confuse
+ the user.
+
+Update Status
+~~~~~~~~~~~~~
+
+* ``initial``: Not updated, no need to update
+* ``initial-update``: Update pending, possibly with error
+* ``suspended``: Exchange was manually disabled, should not be contacted
+ anymore, but record is kept in the wallet. Mostly useful for testing.
+* ``unavailable-update``: The exchange is currently unavailable to be used for withdrawals,
+ but it is possible that the exchange starts working again in the future.
+ The wallet will re-try contacting the exchange. The wallet will still try
+ operations that *spend* coins, but the user might be warned about the bad
+ exchange status.
+
+ Examples:
+
+ * The exchange updated to a new protocol version that is incompatible with the wallet
+ * The exchange advertises a new master public key. This might be a temporary
+ configuration issue or malicious attack.
+ * The exchange only advertises outdated denomination keys, making new withdrawals
+ impossible.
+* ``ready``: Exchange is useable.
+* ``ready-update``: Exchange is useable, but currently being updated.
+
+ToS Status
+~~~~~~~~~~
+
+* ``pending``: The wallet is still trying to download the ToS.
+ Possibly the last download attempt failed, will be reflected in an
+ error details object.
+* ``proposed``: The user needs to accept the current ToS.
+* ``accepted``: The user has accepted the latest version of the ToS.
+
+Management Requests
+-------------------
+
+* ``listExchanges``: List exchanges with their status info.
+* ``addExchange``: Adds an exchange, entry status will be ``ephemeral``
+ until the user actually uses the exchange.
+* ``getExchangeResources``: List resources (number of coins, reserves, ...) associated
+ with the exchange.
+* ``deleteExchange({exchangeUrl: string, purge: boolean})``: Removes an exchange.
+ Unless ``purge: true`` is specified, only an exchange without any associated
+ resources can be deleted.
+* ``getExchangeTos({exchangeUrl: string, acceptLanguage?: string[], acceptFormat?: string[]}) => { language: string, mime: string, content: string, altLanguages: string[] }``
+
+FIXME: Purging should probably delete *all* resources associated with the exchange.
+But does it also remove the associated transactions?
+
+ToS Management
+--------------
+
+The wallet only stores the last accepted ToS ETag and the ETag last offered by
+the exchange. The ToS contents are not stored by the wallet database (and thus
+not included in backups). However, the wallet implementation may cache the
+``/terms`` response on the level of the HTTP client.
+
+In future iterations, the UI might specify the prefered language and content type
+for ``/terms``, so that the wallet can already download the full response (not just ``HEAD``).
+
+
+Definition of Done
+==================
+
+ * states implemented in wallet-core
+ * exchange management specified on a UI level
+ * webex implemented
+ * android wallet implemented
+ * ios wallet implemented
+
+Discussion / Q&A
+================
+
+* Should there be a "permanently failed" update state?
+
+ * dold => I don't think so, as it means that temporary configuration issues on the side of the
+ exchange might *permanently* brick users' wallets.
+ The wallet should always re-try contacting the exchange and of course possibly report
+ information to the auditor.
+
diff --git a/design-documents/049-auth.rst b/design-documents/049-auth.rst
new file mode 100644
index 00000000..d328aabf
--- /dev/null
+++ b/design-documents/049-auth.rst
@@ -0,0 +1,154 @@
+DD 49: Authentication
+#####################
+
+Summary
+=======
+
+This design document specifies a simple authentication framework to be used by multiple Taler
+components that require authentication.
+
+Motivation
+==========
+
+SPAs currently store the username and password in locals storage (or at least
+session storage).
+
+There's also no way to manage auth tokens third parties (e.g.
+auditors).
+
+Requirements
+============
+
+* simple specification
+* simple implementation
+* simple to use
+* must cover two main use cases:
+
+ * SPA login
+ * delegating (possibly restricted) access to a third party using a token
+
+Proposed Solution
+=================
+
+We define a ``token`` endpoint that can be used to obtain access tokens from
+other forms of authentication, typically HTTP Basic auth.
+
+
+.. _dd48-token:
+
+Token Endpoint
+--------------
+
+.. http:post:: /${RESOURCE...}/token
+
+ **Request Body**
+
+ .. ts:def:: TokenRequest
+
+ interface TokenRequest {
+ // Service-defined scope for the token.
+ // Typical scopes would be "readonly" or "readwrite".
+ scope: string;
+
+ // Server may impose its own upper bound
+ // on the token validity duration
+ duration?: RelativeTime;
+
+ // Is the token refreshable into a new token during its
+ // validity?
+ // Refreshable tokens effectively provide indefinite
+ // access if they are refreshed in time.
+ refreshable?: boolean;
+ }
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ The response is a `TokenSuccessResponse`
+
+ **Details:**
+
+ .. ts:def:: TokenSuccessResponse
+
+ interface TokenSuccessResponse {
+ // Expiration determined by the server.
+ // Can be based on the token_duration
+ // from the request, but ultimately the
+ // server decides the expiration.
+ expiration: Timestamp;
+
+ // Opque access token.
+ access_token: string;
+ }
+
+Token Revocation
+-----------------
+
+Clients using session tokens log by forgetting the session token.
+Tokens can be explicitly revoked by making a ``DELETE`` request on
+the token endpoint.
+
+.. http:delete:: /${RESOURCE...}/token
+
+ Invalidate the access token that is being used to make the request.
+
+ **Authentication:** The client must authenticate
+ with a valid access token.
+
+
+
+Definition of Done
+==================
+
+* DONE: spec reviewed
+* DONE: implemented in merchant backend
+* implemented in libeufin-bank
+* DONE: implemented in the bank webui SPA
+* implemented in the merchant backoffice SPA
+
+
+Alternatives
+============
+
+* use something much closer to OAuth2
+
+ * would be unnecessarly generic and complex
+
+Session Tokens / Signatures
+---------------------------
+
+For performance reasons, OAuth 2.0 uses two types of tokens: Short-lived access
+tokens and long-lived refresh tokens. The access tokens can be implemented via
+signatures and the long-lived refresh tokens via server-stored tokens. This
+allows to cheaply validate access tokens, while still allowing longer expiration times
+for refresh tokens.
+
+We could do something similar by introducing login and session tokens. A login
+token is a server-stored token. In addition to being used directly as an
+access token, a login token can also be converted to a short-lived session
+token.
+
+Session access tokens should be implemented as "self-encoded tokens", i.e.
+as tokens signed by the server without requiring server-side token storage.
+Session access tokens should have a rather short maximum expiration.
+
+The signature should be over ``(username, kind, scope, creation_timestamp, expiry)``.
+
+To revoke session tokens, the server must store the timestamp of the last
+revocation and only accept tokens with a ``creation_timestamp`` larger than the
+last revocation timestamp. Individual session tokens cannot be revoked, only
+all issued session tokens can be revoked at once.
+
+However, we decided against doing this because the performance benefits
+are not significant enough for us and having multiple token types would
+lead to unnecessary complexity.
+
+Drawbacks
+=========
+
+* still more complex than simple auth tokens or HTTP basic auth
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/050-libeufin-nexus.rst b/design-documents/050-libeufin-nexus.rst
new file mode 100644
index 00000000..6049bda6
--- /dev/null
+++ b/design-documents/050-libeufin-nexus.rst
@@ -0,0 +1,354 @@
+DD 50: Libeufin-Nexus
+#####################
+
+Summary
+=======
+
+This document proposes a new design for the libeufin Nexus component,
+focusing on the user-interaction and where what state is to be stored.
+
+
+Motivation
+==========
+
+The existing Nexus design is overly complex to configure, develop and
+maintain. It supports EBICS features we do not need, and lacks key features
+(like long-polling) that are absolutely needed.
+
+..
+ long-polling at the TWG is NOT NetzBon-critical, as the TWG is only offered
+ by the Bank.
+
+We also have several implementations with Nexus, Bank and Depolymerization
+subsystems, and it would be good to combine some of them.
+
+Requirements
+============
+
+ * Easy to use, high-level abstraction over EBICS details
+ * Reduce complexity, no multi-account, multi-connection support
+ * No general EBICS client, Taler-specific logic
+ * Support for Taler facade, including bouncing of transactions with malformed subject
+ * Clear separation between configuration and runtime, minimal runtime footprint
+ * No built-in cron-jobs, background tasks runnable via systemd (one-shot and persistent mode)
+ * Configuration style same as other GNUnet/Taler components
+ * No private keys in database, as in other Taler components
+ * Enable future unified implementation with Depolymerization to share database and REST API logic
+
+Proposed Solution
+=================
+
+Split up Nexus into four components:
+
+ * nexus-ebics-setup: register account with EBICS server and perform key generation and exchange
+ * nexus-ebics-fetch: obtain wire transfers and payment status from EBICS server
+ * nexus-ebics-submit: send payment initiation messages to EBICS server
+ * nexus-httpd: serve Taler REST APIs (wire gateway API, revenue API) to Taler clients and implement facade logic
+
+All four components should read a simple INI-style configuration file,
+possibly with component-specific sections.
+
+Configuration file
+------------------
+
+.. code-block:: shell-session
+
+ [nexus-ebics]
+ CURRENCY = EUR
+ HOST_BASE_URL = http://ebics.bank.com/
+ HOST_ID = mybank
+ USER_ID = myuser
+ PARTNER_ID = myorg
+ IBAN = MY-IBAN
+ BIC = MY-BIC
+ NAME = MY NAME
+ BANK_PUBLIC_KEYS_FILE = enc-auth-keys.json
+ CLIENT_PRIVATE_KEYS_FILE = my-private-keys.json
+ BANK_DIALECT = postfinance # EBICS+ISO20022 style used by the bank.
+
+ [nexus-postgres]
+ CONFIG = postgres:///libeufin-nexus
+
+ [nexus-ebics-fetch]
+ FREQUENCY = 30s # used when long-polling is not supported
+ STATEMENT_LOG_DIRECTORY = /tmp/ebics-messages/
+
+ [nexus-ebics-submit]
+ FREQUENCY = 30s # use 0 to always submit immediately (via LISTEN trigger)
+
+ [nexus-httpd]
+ PORT = 8080
+ UNIXPATH =
+ SERVE = tcp | unix
+
+ [nexus-httpd-wire-gateway-facade]
+ ENABLED = YES
+ AUTH_METHOD = token
+ AUTH_TOKEN = "secret-token:foo"
+
+ [nexus-httpd-revenue-facade]
+ ENABLED = YES
+ AUTH_METHOD = token
+ AUTH_TOKEN = "secret-token:foo"
+
+
+File contents: BANK_PUBLIC_KEYS_FILE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+JSON with 3 fields:
+
+ * bank_encryption_public_key (base32)
+ * bank_authentication_public_key (base32)
+ * accepted (boolean)
+
+File contents: CLIENT_PRIVATE_KEYS_FILE
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+JSON with:
+
+ * signature_private_key (base32)
+ * encryption_private_key (base32)
+ * authentication_private_key (base32)
+ * submitted_ini (boolean)
+ * submitted_hia (boolean)
+
+Database schema
+---------------
+
+.. code-block:: shell-session
+
+ CREATE TABLE incoming_transactions
+ (incoming_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,amount taler_amount NOT NULL
+ ,wire_transfer_subject TEXT
+ ,execution_time INT8 NOT NULL
+ ,debit_payto_uri TEXT NOT NULL
+ ,bank_transfer_id TEXT NOT NULL -- EBICS or Depolymerizer (generic)
+ ,bounced BOOL DEFAULT FALSE -- to track if we bounced it
+ );
+
+ CREATE TABLE outgoing_transactions
+ (outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY
+ ,amount taler_amount NOT NULL
+ ,wire_transfer_subject TEXT
+ ,execution_time INT8 NOT NULL
+ ,credit_payto_uri TEXT NOT NULL
+ ,bank_transfer_id TEXT NOT NULL
+ );
+
+ CREATE TABLE initiated_outgoing_transactions
+ (initiated_outgoing_transaction_id INT8 GENERATED BY DEFAULT AS IDENTITY -- used as our ID in PAIN
+ ,amount taler_amount NOT NULL
+ ,wire_transfer_subject TEXT
+ ,execution_time INT8 NOT NULL
+ ,credit_payto_uri TEXT NOT NULL
+ ,out_transaction_id INT8 REFERENCES outgoing_transactions (out_transaction_id)
+ ,submitted BOOL DEFAULT FALSE
+ ,hidden BOOL DEFAULT FALSE -- FIXME: exaplain this.
+ ,client_request_uuid TEXT NOT NULL UNIQUE
+ ,failure_message TEXT -- NOTE: that may mix soon failures (those found at initiation time), or late failures (those found out along a fetch operation)
+ );
+
+ COMMENT ON COLUMN initiated_outgoing_transactions.out_transaction_id
+ IS 'Points to the bank transaction that was found via nexus-fetch. If "submitted" is false or nexus-fetch could not download this initiation, this column is expected to be NULL.'
+
+nexus-ebics-setup
+-----------------
+
+The ebics-setup tool performs the following:
+
+ * Checks if the require configuration options are present and well-formed
+ (like the file names), if not exits with an error message. Given
+ --check-full-config, also sanity-check the configuration options of the
+ other subsystems.
+
+ * Checks if the private keys file exists, if not creates new private keys
+ with flags "not submitted".
+
+ * If any private key flags are set to "not submitted" or a command-line
+ override is given (--force-keys-resubmission), attempt to submit the
+ corresponding client public keys to the bank. If the bank accepts the
+ client public key, update the flags to "submitted".
+
+ * If a public key was submitted or if a command-line override
+ (--generate-registration-pdf) is given, generate a PDF with the public key
+ for the user to print and send to the bank.
+
+ * Checks if the public keys of the bank already exist on disk, if not try to
+ download them with a flag "not accepted". If downloading fails, display a
+ message asking the user to register the private keys (and give override
+ options for re-submission of private keys or re-generation of the
+ registration PDF).
+
+ * If we just downloaded public keys, display the corresponding public keys
+ (or fingerprints) to the user and ask the user to interactively confirm to
+ accept them (or auto-accept via --auto-accept-keys). If the user accepted
+ the public key, update the flag to "accepted".
+
+nexus-ebics-fetch
+-----------------
+
+ * Fetches by default all incoming and outgoing bank transactions and error
+ messages and inserts them into the Postgres database tables (including
+ updating the initiated outgoing transaction table). First, considers the
+ last transactions in the database and fetches statements from that day
+ forward (inclusive). Afterwards, fetches reports and when the day rolls
+ over (before committing any transactions with the next day!) also fetches a
+ final statement of the previous day, thus ensuring we get a statement
+ every day plus intra-day reports.
+
+ .. note::
+
+ (1) "from that day forward (inclusive)" must **not** rely on EBICS returning
+ the unseen messages: that's because they might **already** be downloaded but
+ never made it to the database.
+
+ (2) "and when the day rolls over". When does a day roll over? => A day rolls
+ over when the current time is at least on the day after the transaction with the
+ most recent timestamp that's stored in the database.
+
+ (3) "Afterwards, fetches reports". This must happen **only after** any possible
+ previous statement got downloaded.
+
+ To summarize: at any point in time the database must contain (the content of) any
+ possible statement up to the current time, plus any possible report up to the current
+ time (in case that's not covered by any statement so far).
+
+ * Bounces transactions with mal-formed wire transfer subjects.
+
+ * Optionally logs EBICS messages to disk, one per file, based on
+ configuration. Filenames must include the timestamp of the download. The
+ date must be in the path and the time of day at the beginning of the
+ filename. This will facilitate easy deletion of logs.
+
+ * Optionally only fetches reports (--only-reports) or statements (--only-statements)
+ or only error messages (--only-failures).
+
+ * Optionally terminates after one fetch (--transient) or re-fetches based
+ on the configured frequency.
+
+ * Terminates hard (with error code) if incoming transactions are not in the
+ expected (configured) currency.
+
+
+nexus-ebics-submit
+------------------
+
+ * Generates a payment initiation message for all client-initiated outgoing
+ transactions that have not yet been initiated. If the server accepts the
+ message, sets the initiated flag in the table to true. The EBICS order ID
+ is set to the lowest initiated_outgoing_transaction_id in the transaction
+ set modulo 2^20 encoded in BASE36. The payment information ID is set to
+ the initiated_outgoing_transaction_id of each transaction as a text
+ string. The message identification is set to the lowest
+ initiated_outgoing_transaction_id plus ("-") the highest
+ initiated_outgoing_transaction_id as a text string.
+
+ * Optionally terminates after one fetch (--transient) or re-submits based
+ on the configured frequency.
+
+ * If configured frequency is zero (default), listens to notifications from
+ nexus-httpd for insertions of outgoing payment initiation records.
+
+
+nexus-httpd
+-----------
+
+ * Offers REST APIs as per configuration.
+
+ * Listens to notifications from nexus-ebics-fetch to run facade-logic and
+ wake-up long pollers.
+
+ * Offers a *new* REST API to list failed (initiated outgoing) transactions
+ and allows the user to re-initiate those transactions (by creating new
+ records in the initiated outgoing transactions table). Also allows the
+ user to set the "hidden" flag on failed transactions to not show them
+ anymore.
+
+
+Definition of Done
+==================
+
+ * Code implemented
+ * Testcases migrated (including exchange, merchant, etc.)
+ * Man pages updated
+ * Manual updated
+ * Tested with actual banks (especially error handling and idempotency)
+ * Tested against server-side EBICS mock (to be resurrected)
+ * Tested against various ISO 20022 messages
+
+
+Alternatives
+============
+
+ * Only run Taler on top of Bitcoin.
+
+Drawbacks
+=========
+
+ * Uses EBICS.
+
+Discussion / Q&A
+================
+(This should be filled in with results from discussions on mailing lists / personal communication.)
+
+* From private discussion: bouncing goes inside nexus-fetch as
+ it saves one database event, makes the HTTPd simpler, and lets
+ the bouncing happen even when no HTTPd runs.
+
+* Sign-up PDF is ever only generated if *both* INI & HIA have the "submitted" state.
+
+* What is 'override option for re-submission of private keys?'.
+ --force-keys-submission already re-submits the keys but it does not
+ override them. If the user wants new keys, they can easily remove
+ the keys file on disk. That makes the CLI shorter.
+
+* Implementation sticks to the IBAN found in the configuration, **if** the bank
+ does not show any IBAN related to the EBICS subscriber.
+
+* from nexus-ebics-submit: "if the server accepts the request, sets the
+ initiated flag in the table to true". May there be a case where the
+ server accepted the request, but the client never got any response (some
+ network issue..), and therefore didn't set the submitted flag, ending
+ up in submitting the payment twice? Also: flagging the payment _after_
+ the bank response, may lead double-submission even if the HTTP talk ended
+ well: it suffices to crash after having received a "200 OK" response but
+ before setting the submitted flag to the database.
+
+* the ebics-submit section mentions the EBICS order ID. The following excerpt
+ was found however at page 88 of the EBICS 3 specifications:
+
+ ``OrderID is only present if a file is transmitted to the bank relating to an order with an
+ already existing order number (only allowed for AdminOrderType = HVE or HVS)``
+
+ Nexus does not support HVE or HVS.
+
+* As of private communication, the responsibility of submitting idempotent payments
+ relies on the use of ``request_uid`` (a database column of the initiated payment)
+ as the ``MsgId`` value of the corresponding pain.001 document.
+
+* ``submitted`` column of an initiated payment evolved into the following enum:
+
+ .. code-block:: shell-session
+
+ CREATE TYPE submission_state AS ENUM (
+ 'unsubmitted'
+ ,'transient_failure'
+ ,'permanent_failure'
+ ,'success'
+ ,'never_heard_back'
+ );
+
+ * ``unsubmitted``: default state when a payment is initiated
+ * ``transient_failure``: submission failed but can be retried, for example after a network issue.
+ * ``permanent_failure``: EBICS- or bank-technical error codes were not EBICS_OK (nor any tolerated EBICS code like EBICS_NO_DOWNLOAD_DATA_AVAILABLE), never retry.
+ * ``never_heard_back``: the payment initiation submission has **been** ``success`` but it was never confirmed by any outgoing transaction (from a camt.5x document) or any pain.002 report. It is responsability of a garbage collector to set this state after a particular time period.
+
+* the initiated_outgoing_transactions table takes two more columns:
+ ``last_submission_date``, a timestamp in microseconds, and a
+ ``submission_counter``. Both of them would serve to decide retry
+ policies.
+
+* the ``failure_text`` column at the initiated_outgoing_transactions table
+ should contain a JSON object that contains any useful detail about the problem.
+ That *could* be modeled after the Taler `ErrorDetail <https://docs.taler.net/core/api-common.html#tsref-type-ErrorDetail>`_, where at least the error code and the hint fields are provided.
diff --git a/design-documents/051-fractional-digits.rst b/design-documents/051-fractional-digits.rst
new file mode 100644
index 00000000..4321fd53
--- /dev/null
+++ b/design-documents/051-fractional-digits.rst
@@ -0,0 +1,211 @@
+DD 51: Fractional Digits
+#########################
+
+Summary
+=======
+
+This design document specifies how an amount's fractional digits should be rendered.
+Note that UIs that cannot render amounts as specified (e.g. because the display does
+not support super script digits) may ignore the rendering advice provided by the
+protocol under this DD.
+
+
+Motivation
+==========
+
+Since different currencies have different ways to show/render fractionals, the
+end-user apps should follow these guidelines.
+
+Requirements
+============
+
+There was already a specification for ScopedCurrencyInfo - which got renamed to CurrencySpecification.
+
+We need three core characteristics for fractional digits for each currency:
+
+ e) the number of fractional digits e in [0..8] the user may 'e'nter in a TextInputField
+
+ n) the number of fractional digits n in [0..8] to be rendered as 'n'ormal characters (same font and size as the integer digits). All additional fractional digits will be rendered as SuperScriptDigits as known from gas filling stations. The UI should never round or truncate any amount, but always render all existing digits (except trailing zeroes, see c).
+
+ z) the number of fractional digits z in [0..8] to be rendered as trailing 'z'eroes (including SuperScript digits). E.g. if z = 2 (and n = 2), then render $5 as ``$ 5.00``. If z = 3 (and n = 2), then render $5 as ``$ 5.00⁰`` with two normal trailing zeroes and one superscript trailing zero.
+
+The values e, n, and z are independent from each other. Each could be any value
+from 0 to 8. However, when a user enters an amount, s/he should be able to input
+all normal fractionals. Thus e should never be smaller than n.
+
+Usually, all these three numbers have the same value (e = n = z), which means
+that in case of e.g. '2' (used for €,$,£) the user can enter cent/penny values
+(but not a fraction of those), these cents/pennies are always shown (even if
+they are 0) as two normal digits after the decimal separator, and fractions of
+a cent/penny are rendered as SuperScriptDigits, but appear only if they are not
+trailing zeroes.
+For japanese ¥, all three values could be 0, which means that the user cannot
+enter fractions at all. If there are fractions they would never be rendered as
+normal digits but always as SuperScript, and appear only if they are not
+trailing zeroes.
+
+Additionally, some cryptocurrencies have such huge units, that they are
+commonly rendered in milli-units, such as mBTC (milliBTC, 1/1000 of a BTC),
+Gwei (Giga-WEI), Mwei (Million-WEI), Kwei (Kilo-WEI), or
+Mether/Kether/Gether/Tether and more "logical" units such as Szabo and
+Finney. See ``https://coinguides.org/ethereum-unit-converter-gwei-ether/`` if
+you want a good laugh. Regardless of the self-inflicted insanity here, this
+could also make sense for inflated currencies in some cases. So we probably
+should also have the ability to ship such a conversion map.
+
+
+Proposed Solution
+=================
+
+Protocol considerations
+-----------------------
+
+The exchange, bank and merchant backends would need to be configured (via
+their configuration files) to return the following CurrencySpecification in their
+``/config`` and/or ``/keys`` endpoints. The bank returns this so that the
+bank SPA can render amounts correctly, the exchange informs the wallets about
+the desired way to render the currency, and the merchant backend informs the
+merchant SPA --- independently of any particular exchange being used --- how
+the merchant SPA should render amounts. Hence, the information will need to be
+provisioned by all three services.
+
+ .. code-block:: swift
+
+ public struct CurrencySpecification: Codable, Sendable {
+ // e.g. “Japanese Yen” or "Bitcoin (Mainnet)"
+ let name: String
+ // how many digits the user may enter after the decimal separator
+ let fractional_input_digits: Int
+ // €,$,£: 2; some arabic currencies: 3, ¥: 0
+ let fractional_normal_digits: Int
+ // usually same as fractionalNormalDigits, but e.g. might be 2 for ¥
+ let fractional_trailing_zero_digits: Int
+ // map of powers of 10 to alternative currency names / symbols,
+ // must always have an entry under "0" that defines the base name,
+ // e.g. "0 : €" or "3 : k€". For BTC, would be "0 : BTC, -3 : mBTC".
+ // This way, we can also communicate the currency symbol to be used.
+ let alt_unit_names: [Int : String]
+ }
+
+(Note: decimal_separator, group_separator and is_currency_name_leading were
+removed from this struct since they should always be taken from the user's
+locale.)
+
+
+
+
+For very large (2400000) or very tiny amounts (0.000056) the software would
+then first represent the number compactly without any fraction (so for our
+examples above, 24 * 10^6 and 56 * 10^-6) and then search for the nearest fit
+in the alt_unit_names table. The result might then be 24000 KGELD or 0.056
+mGELD, assuming the map had entries for 3 and -3 respectively. Depending on
+the table, the result could also be 24 MGELD (6 : MGELD), or 5.6 nGELD
+(assuming -6 : nGeld). Fractional rendering rules would still be applied
+to the alternative unit name, alas the "fractional_input_digits" would
+always apply to the unit currency and may need to be adjusted if amounts
+are input using an alternative unit name.
+
+Configuration syntax
+--------------------
+
+Each currency should be specified in its own subsystem-independent
+currency, with the section name prefixed with "currency-". In that
+section. The map could be given directly in JSON. For example:
+
+ .. code-block:: ini
+
+ [currency-euro]
+ ENABLED = YES
+ name = "Euro"
+ code = "EUR"
+ fractional_input_digits = 2
+ fractional_normal_digits = 2
+ fractional_trailing_zero_digits = 2
+ alt_unit_names = {"0":"€"}
+
+ [currency-japanese-yen]
+ ENABLED = YES
+ name = "Japanese Yen"
+ code = "JPY"
+ fractional_input_digits = 2
+ fractional_normal_digits = 0
+ fractional_trailing_zero_digits = 2
+ alt_unit_names = {"0":"¥"}
+
+ [currency-bitcoin-mainnet]
+ ENABLED = NO
+ name = "Bitcoin (Mainnet)"
+ code = "BITCOINBTC"
+ fractional_input_digits = 8
+ fractional_normal_digits = 3
+ fractional_trailing_zero_digits = 0
+ alt_unit_names = {"0":"BTC","-3":"mBTC"}
+
+ [currency-ethereum]
+ ENABLED = NO
+ name = "WAI-ETHER (Ethereum)"
+ code = "EthereumWAI"
+ fractional_input_digits = 0
+ fractional_normal_digits = 0
+ fractional_trailing_zero_digits = 0
+ alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"}
+
+
+Implementation considerations
+-----------------------------
+
+iOS has a built-in currency formatter, which can be configured from a locale.
+It knows how to deal with group-separators and where to apply them (e.g. India
+uses a mixture of thousands and hundreds instead of putting the separator after
+each 3 digits like western currencies).
+Set the formatter's parameter 'maximumFractionDigits' to 8, then it will not
+round the value and thus can be used for the whole amount.
+Set its parameter 'minimumFractionDigits' to 'z' (fractionalTrailingZeroDigits)
+to let it automatically add trailing zeroes.
+Then convert all fractional digits after 'n' (fractionalNormalDigits) to
+SuperScript digits.
+
+(please add information about Android and WebEx here)
+
+
+
+Definition of Done
+==================
+
+(Only applicable to design documents that describe a new feature. While the
+DoD is not satisfied yet, a user-facing feature **must** be behind a feature
+flag or dev-mode flag.)
+
+ * Configuration (INI) format finalized and documented in taler.conf man page [DONE]
+ * Endpoints of libeufin-bank, fakebank, exchange and merchant return the information
+ * SPAs use the information to render amounts
+ * Wallet-core passes rendering information to wallet UIs
+ * Cashier, Android PoS, WebExtension, Android and iOS Wallet render amounts accordingly
+
+
+Alternatives
+============
+
+None, we cannot confuse users by rendering amounts in ways that break cultural
+standards, and we cannot round and have numbers in balances not add up.
+
+
+Drawbacks
+=========
+
+Discussion / Q&A
+================
+
+We probably should NOT have the decimalSeparator in this definition. Instead that
+should be taken from the locale of the user, so they see currency amounts formatted
+like they're used to.
+If we really keep this, then we would also need the groupSeparator to ensure it is
+not identical to the decimalSeparator.
+Better to leave this can of worms to the operating system our app runs on, and render
+according to the user's preferences (locale)...
+
+However, instead of decimalSeparator we could specify the locale this currency belongs to.
+
+
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/052-libeufin-bank-2fa.rst b/design-documents/052-libeufin-bank-2fa.rst
new file mode 100644
index 00000000..3d0900e2
--- /dev/null
+++ b/design-documents/052-libeufin-bank-2fa.rst
@@ -0,0 +1,136 @@
+DD 52: LibEufin Bank Two-factor authentification
+################################################
+
+Summary
+=======
+
+This document proposes designs for supporting 2-FA for more operations in
+libeufin-bank.
+
+Motivation
+==========
+
+Currently, only cashout operations are protected using 2-FA and we want to also
+protects withdrawal, transactions, account reconfiguration and account deletion.
+
+Requirements
+============
+
+* Support future TAN channels (YubiKey, trusted devices, etc) without API-breaking changes
+* Support multiple TAN channels per user
+
+Proposed Solutions
+==================
+
+2 kinds of operations
+^^^^^^^^^^^^^^^^^^^^^
+
+We have two kinds of operations we would like to protect:
+
+* state-machine operations that already have a ``pending`` and ``confirmed`` status and require multiple endpoint calls to complete (cashout and withdrawal).
+* one-shot operations that are currently completed using a single endpoint call (transaction, account reconfiguration and account deletion).
+
+Fine-grained or coarse-grained authentification
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+* Fine-grained authorization is when one challenge is linked to a unique unalterable operation. They are the most secure and have the usability advantage that clients can show users exactly what they are allowing. They are complicated to implement especially for one-shot operations.
+* Coarse-grained authorization is when each challenge allows to perform one or many protected operations of any kind. They are the simplest to implement and might be enough for our needs.
+
+We should also take in consideration how hard it would be to maintain the solution and how hard it would be to protect a new kind of operation in the future.
+
+State machines operations only
+------------------------------
+
+If we transform all operations to become state-machine ones, we can use the same design currently used for cashout operations. All operations are created in a ``pending`` state and need to be confirmed later. The TAN challenge code is sent when the operation is created and checked during confirmation. Operation creation is idempotent and can be used to trigger code retransmission.
+
+The good
+^^^^^^^^
+
++ Fine-grained authorization
+
+The bad
+^^^^^^^
+
+- Requires to store pending operations in the database, requires new tables to store pending state for one-shot ones
+- Requires to add many endpoints to track operations status, list pending operations, confirm operations, etc
+- Requires to mix TAN challenge logic with operation logic, this means asking for TAN channel alongside operation data and returning TAN specific error in all operation creation and confirmation endpoints, therefore TAN logic changes can impact all those endpoints
+- Operation logic rewrite
+- Big backend and database change (new table or column and new API per operation)
+
+
+Centralized 2FA endpoints
+-------------------------
+
+To improve upon the previous design we can separate endpoints to perform TAN challenges from operation ones. When creating operations they return a challenge ID that can be used with TAN-specific endpoints to receive and solve a challenge. Those endpoints will handle the TAN channel choice and TAN-specific errors. Protected endpoints will error when a pending challenge hasn't been solved.
+
+The good
+^^^^^^^^
+
++ Fine-grained authorization
++ Centralized TAN challenge logic and error handling, TAN logic changes only impact TAN-specific endpoints
+
+The bad
+^^^^^^^
+
+- Requires to store pending operations in the database
+- Requires adding many endpoints to track operations status, confirm operations, list pending operations, etc.
+- Operation logic rewrite
+- Big backend and database change (new table or column and new API per operation)
+
+2FA tokens
+----------
+
+To improve upon the previous design, if coarse-grained authorization is enough, we can have a simpler design where a successful challenge produces a one-use 2FA token. Protected endpoints will error when a 2FA token is required and the token is provided through an HTTP header.
+
+We could require a 2FA token when confirming state-machine operations or when performing one-shot ones. Removing the need for new database tables and operation endpoints.
+
+The good
+^^^^^^^^
+
++ Existing database tables stay the same
++ Centralized TAN challenge logic and error handling
++ Most endpoints stay the same except the cashout API
++ Can protect new operations without changing their logic but need to add token consumption logic to the database transaction
++ Small backend and database change per operation (token consumption logic)
+
+The bad
+^^^^^^^
+
+- Using a nonstandard header can be complicated for some clients
+- The pending state of one-shot operations is kept at the client level, this is a simplification for the backend but we do not want to lose this state. This might be easy to do as all oneshot operations are simple ones and the token can be obtained in advance.
+- Coarse-grained authorization, one token for any operation. We could fix this by adding a ``kind`` field and an optional ``operation_id`` (state-machine operation) or ``operation_body`` (one-shot operations) field per challenge but this is ugly.
+
+2FA auth tokens
+---------------
+To improve upon the previous design, we could reuse the existing authentification token logic and create a new scope, ``2fa``, that works as an augmented ``readwrite``. Those auth tokens would be valid for a short amount of time (~3 minute) and would not be refreshable.
+
+The good
+^^^^^^^^
+
++ Existing database tables stay the same
++ Centralized TAN challenge logic and error handling
++ Most endpoints stay the same except the cashout API
++ Can protect new operations without changing their logic
++ Trivial backend and database change per operation (one line of code per operation)
+
+The bad
+^^^^^^^
+
+- Having a short-term token in addition to a long-term refreshable token can be confusing for clients.
+- We still keep the pending state of one-shot operations at the client level.
+- Coarse-grained authorization, one token for any operation for a short amount of time.
+
+Q / A
+=====
+
+* Q: Where do we want to handle TAN challenges logic and error handling, in each operation API or a TAN-specific API?
+
+ * In each operation means fewer API calls and TAN-specific means more API calls but better/cleaner logic separation.
+
+* Q: Where do we want to store pending states for oneshot transactions in the client or the backend database?
+
+ * In the client makes things simpler for the backend but is incompatible with coarse-grained authorization.
+
+* Q: Do we need coarse-grained authorization or fine-grained is enough?
+
+ * Coarse-grained authorization requires that we store pending states for operations even for the ones that are currently oneshot. We could use a different strategy for each kind. \ No newline at end of file
diff --git a/design-documents/053-wallet-ui.rst b/design-documents/053-wallet-ui.rst
new file mode 100644
index 00000000..57f9b1f2
--- /dev/null
+++ b/design-documents/053-wallet-ui.rst
@@ -0,0 +1,1220 @@
+DD 53: Wallet UI Design
+#######################
+
+Summary
+=======
+
+This document proposes designs wallet UI. It defines what Android, iOS and
+WebExtension should follow in order to have a coherent UI between platforms.
+
+Motivation
+==========
+
+We want user to be able to help each others independent of the implementation
+they are using.
+We want user to be able to capitalize the effort of learning how to use one
+wallet and be able to use a different one without the need to learn
+anything new.
+Currently development of different platform specific implementation are independent
+and every developer needs to choose the layout, texts and buttons and navigation.
+
+Requirements
+============
+
+Every screen MUST be defined in a document with the following information:
+
+* **Identifiable UI screens**: every UI should have an unique identifier that will
+ be use for development discussion and bug reports. There should be an option
+ in the application to enable an active rendering of the id.
+
+* **Description**: the reason to be of the screen, should help to define what will
+ be included into, what is going to left for other screens and when and from
+ where should be linked.
+
+The screen MAY also have:
+
+* **Predefined assets**: every implementation should resue the same icons, images,
+ fonts and sounds.
+
+Additionaly the document COULD defined the components of the UI. If one of this
+properties is defined in the spec the implementation must implement it. The specification
+should be minimal to achieve the objective in the description.
+
+* **Info**. Spec of information that the user should have access. The type of info
+ could be a field (id and value) or a banner (information and instructions).
+ The spec will help to reuse the text for i18n across apps and defined
+
+* **Inputs**. Spec of information need to provide in current screen. The type of input,
+ range of values and validation should be defined if necessary.
+
+* **Actions**. Spec of buttons and interactable elements that will have a significant
+ change in the current state. It should also mention navigation when applicable.
+
+* **Layout**. Spec position of elements when needed. The spec should be "soft" in a sense
+ that elements should be easy to find following directions like "close to X" or
+ "at the start/end of the screen".
+
+* **Issues**. Issues we identified that need to be addressed in some implementation(s).
+ This is in particular about inconsistencies and bad design choices made on some
+ platforms.
+
+* **Adoption**. Things that one version does particularly
+ nice that we might want other implementations to adopt.
+
+* **Screenshots**. Should be provided for all wallet implementations and kept
+ up to date, to ensure that they can be used to aid in UI/UX and QA
+ discussions.
+
+Screen should be defined using the most relaxed definition that are good enough to
+be clear for the user. Platform will use this definition and adapt to the differences
+on the platform taking advantange of platform API and screen sizes.
+
+When a screen have multiple uses for the same purpose, a substate section should be
+included with the difference with the main definition.
+
+Part of the screens that are reused shoud also be defined in this document as screen.
+
+Common definition:
+ * navigation between screens should not happen if the user didn't take any action
+
+
+Proposed Solutions
+==================
+
+List of all screens with the defined properties.
+
+.. _cta-balance-list-ref:
+
+balance-list
+------------
+
+This screen shows a currency-scoped list of the balances stored in the wallet,
+and includes information about the total, incoming, and outgoing amounts, as
+well as the currency scope information for each item.
+
+Info
+^^^^
+
+* List of balances in the wallet, where each item contains:
+
+ * Total amount with currency (see :doc:`051-fractional-digits`).
+ * Incoming amount (money pending to arrive).
+ * Outgoing amount (money pending to leave).
+ * Currency scope information (see :doc:`035-regional-currencies`).
+
+* Strings to use:
+
+ * Title is "Balances"
+ * "inbound" and "outbound" are used with
+ pending amounts (shown if applicable below the main
+ amount, green for inbound, red for outbound)
+ * "pending" MAY be used as a badge label to indicate
+ that there are pending activities
+
+Layout
+^^^^^^
+
+* There SHOULD be a menu or button from where the QR code entry / scan
+ functionality is reachable (:ref:`cta-url-entry-ref`)
+* There SHOULD be a menu or button from where the settings screen (:ref:`cta-settings-ref`)
+ is reachable, unless settings are handled differently
+ on the platform.
+* In developer mode, there MAY be a menu or button
+ from where the developer tools (:ref:`cta-devtools-ref`)
+ are reachable. Alternatively, developer tools COULD
+ also only be reachable via settings.
+
+
+Issues
+^^^^^^
+
+* WebEx (minor): Inconsistent to aldready have
+ the "add" and "send" buttons on this page.
+* WebEx (image): Screenshot does not show any
+ pending transactions.
+* Android (text): Uses "Exchange:" which is
+ not user-facing terminology. Should only show the URLs.
+* Android (minor): Screenshot shows only a "pending"
+ badge, which seems redundant given "+10 KUDOS inbound"
+ (too much information?)
+* iOS (major): Way too much detail shown, way too
+ much explanation text, too many operations
+ (send money, request payment, spend test money!!!!);
+ Remove text "Balance:" within each currency, this
+ is all about *Balances* as per the title. Instead,
+ show the balance right of the currency name.
+* iOS (major): overly complex menu with balances and
+ banking; we are NOT a banking app, banking is IMO
+ confusing. Settings icon could be on top left, then
+ it would be more uniform, I would get rid of banking
+ 'tab' entirely!
+* iOS (text): bad icon for "pending outgoing", the
+ "minus" sign does not give me good associations here
+* Android (text): Title should be *Balances* (plural)
+* Android (minor): No "add" and "Send $CURRENCY" buttons
+ on this page.
+
+
+
+Adoption
+^^^^^^^^
+
+* iOS: transaction history visualization with red/green
+ bars after currency is nice.
+
+
+Actions
+^^^^^^^
+
+* **View transactions**. Clicking on a balance item should take you to the
+ transaction list (:ref:`cta-transaction-list-ref`) associated with that balance.
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+========================================================================+
+| WebEx | .. image:: ../screenshots/cta-balance-list-firefox-latest.png |
++-----------+------------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-balance-list-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------------+
+| iOS | .. image:: wallet-screenshots/ios-wallet/11-balances-list-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------------+
+
+
+.. _cta-transaction-list-ref:
+
+transaction-list
+----------------
+
+This screen shows a list of all the transactions associated with a given
+currency scope.
+
+Info
+^^^^
+
+* Total amount and currency (see :doc:`051-fractional-digits`).
+* List of transactions associated with the currency scope, with pending
+ transactions on top, and where each item contains the following:
+
+ * **Title**. It depends on the transaction type. It can be the exchange URL
+ (e.g. exchange.demo.taler.net) for withdraw,
+ the receiver name from the payto-URI for
+ deposits,
+ the human-provided summary of the transaction
+ (for push- and pull- P2P payments),
+ the name of the merchant paid
+ (e.g. Essay Shop) for payments to merchants and
+ refunds
+ * **Status**. It provides complementary information about the transaction
+ for the user, such as the status of the transaction (e.g. “Waiting for
+ confirmation,” “KYC required,” an error message, etc.). (The summary is
+ provided by wallet-core, along with internationalized versions.)
+ * **Timestamp**. The moment when the transaction was created. Ideally, it
+ should be shown with minimal precision, only showing the minutes, hours or
+ days that have elapsed.
+ * **Amount**. The positive or negative impact that the transaction has in
+ the total balance of the currency scope. It should be made clear whether
+ the amount of the transaction is positive or negative, ideally with a sign
+ (+/-) and a color (green/red).
+ * **Pending**. It should be indicated whether the transaction is pending or
+ finished. This can be done with a small badge and with different colors,
+ however, it should be always clear and communicate the message
+ effectively.
+
+Actions
+^^^^^^^
+
+* **Send**. The transaction list should include a button that allows the user
+ to initiate transactions that result in money being sent, such as deposits
+ and peer push payments.
+* **Receive**. The transaction list should also include a button that allows
+ the user to initiate transactions that result in money being received, such
+ as withdrawals and peer pull payments.
+* **View transaction details**. When clicking on a transaction, the user
+ should be taken to its corresponding transaction details depending on the
+ type of the transaction clicked.
+* **Select transaction(s)**. The user should be able to select one or more
+ transactions to perform specific bulk actions, such as deleting. The
+ interaction that triggers this action might differ across platforms. For
+ example, in Android this would be achieved by double pressing a transaction
+ (to activate selection mode) and then clicking other transactions to be
+ selected. On iOS, this could be achieved using an “Edit” button in the
+ toolbar that reveals checkboxes that allow the user to select the desired
+ transactions.
+* **Delete transaction(s)**. This could be achieved in bulk via selection
+ mode, or individually for each transaction via a menu or a button. Either
+ way, performing a deletion should always show a confirmation menu before
+ doing the actual deletion.
+
+Layout
+^^^^^^
+
+* The specific currency for which transactions are shown
+ SHOULD be prominent (title bar, menu bar)
+* The current balance should be on top (ideally always on-top)
+ just below the title and on the right of the "send" and "receive"
+ buttons. The current balance should align with the amounts
+ of the various transactions below. If possible, it should
+ have a label "Balance" near (ideally above) it.
+* There needs to be a way to go "back" to the
+ balance list (:ref:`cta-balance-list-ref`) **if**
+ we have more than a single currency in use. This
+ can be some (optional) "back" button or some
+ "home" button or some "balances" menu entry.
+* There should be a menu or button from where the QR code entry / scan
+ functionality is reachable (:ref:`cta-url-entry-ref`)
+* There COULD be a menu or button from where the settings screen (:ref:`cta-settings-ref`)
+ is reachable. The settings screen MUST be reachable
+ **if** there is no way to get to the balance list screen
+ because we only have a single currency.
+* In developer mode, there MAY be a menu or button
+ from where the developer tools (:ref:`cta-devtools-ref`)
+ are reachable. Alternatively, developer tools COULD
+ also only be reachable via settings.
+
+
+Issues
+^^^^^^
+
+* WebEx (text): Iconography (T), (W) is not as
+ nice as iconography in on Android.
+* WebEx (image): Screenshot does not show any
+ pending transactions.
+* Android (text): Iconography (same icon for push
+ payments and debit and POS) and again same icon
+ for invoice and withdraw) is sub-optimal. Should
+ pick unique icons for each type of operation.
+* Android (text): Button labels should just be
+ "Send" and "Receive" (without "funds").
+* iOS (text): Iconography (+ / -) is also not
+ expressive and redundant with the colors.
+* iOS (text): Sign of the operation (+ / -) should
+ be just before the amount (see Android), not
+ all the way on the left as an icon. Also can
+ probably be more subtle?
+* iOS (text): currency symbol in front of every
+ amount value is highly redundant; would be better
+ to list the currency once in the title (see Android)
+* iOS (minor): lacks search button (see Android)
+* all (text): use the merchant name for the main
+ transaction label on refunds (and payments to
+ merchants)
+* all (text): Use the human-provided *summary* of the
+ P2P payment for both push and pull payments (the
+ direction is clear from +/- on the amount, and
+ it should not matter who initiated!)
+* all (text): "Deposits" should use the receiver name
+ of the payto-URI of the target account (URL-decoded)
+ in the main title, instead of "Deposit"
+* iOS (text): "Withdrawal" shown instead of
+ exchange URL for withdraw
+* iOS (text): "Sent Requested money" shown instead of
+ exchange URL for withdraw
+* iOS (major): The balance is not shown. The
+ "send" and "receive" buttons are missing.
+* iOS (minor): has top/buttom scroll buttons not present
+ in other UIs (likely too much, better to have search!)
+* iOS (critical): the buttons to send/receive funds
+ are not even shown in the screenshot, likely because
+ of balances vs. banking distinction, which should
+ also be removed.
+* All (critical): we do not seem to have
+ screenshots of what happens after picking
+ select funds or receive funds!
+
+
+Adoption
+^^^^^^^^
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+--------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+====================================================================+
+| WebEx | .. image:: ../screenshots/cta-transaction-list-firefox-latest.png |
++-----------+--------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-transaction-list-android-latest.png |
+| | :width: 30% |
++-----------+--------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-transaction-list-ios-latest.png |
+| | :width: 30% |
++-----------+--------------------------------------------------------------------+
+
+
+.. _cta-withdraw-review-ref:
+
+cta-withdraw-review
+-------------------
+
+This screen is used to inform the user that before they can proceed
+with a withdrawal, they must accept the terms of service of the exchange.
+
+
+Info
+^^^^
+
+* service provider to be used showing the URL
+* amount to be withdrawn
+* applicable fees (if any)
+
+* Text:
+
+ * Use "payment service provider" to label the
+ URL, not "exchange"
+ * Use "Net" and "Fees" and "Gross" to label
+ the amount in a section "Details"
+
+Inputs
+^^^^^^
+
+* service provider: allow the selection of different exchange (if applicable)
+
+
+Actions
+^^^^^^^
+
+* **change service provider**: updates fees shown (after returning from exchange selection screen)
+* **review and confirm ToS**: advance to the :ref:`cta-accept-tos-ref` screen
+* **back**: user will be redirected to previous screen
+
+.. attention::
+ User should be able to play with the amount, not possible in the current design
+
+Issues
+^^^^^^
+
+* All(critical): This is an extremely lazy screen,
+ and it probably should be completely revamped.
+ We should allow the user to change the exchange
+ **and** to enter the amount to withdraw
+ **unless** a fixed amount (or exchange) were
+ given when we were triggered.
+ Editing the exchange should go to another screen
+ (for now, with a simple URL entry bar, in the
+ future with exchange comparisson and whatever);
+ the fees should already be shown on this screen
+ for the selected exchange.
+* All(critical): We should probably unify this screen
+ with cta-withdraw-confirm and cta-withdraw,
+ with the only difference
+ being that one variant has "View Terms of Service"
+ while the other has a "Withdraw $AMOUNT" button.
+* iOS(image): no screenshot available
+* All(text): Use "Net" and "Fees" and "Gross" to label
+ the amounts in a section "Details"
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+-----------------------------------------------------------------+
+| Platform | Screenshot |
++===========+=================================================================+
+| WebEx | .. image:: ../screenshots/cta-withdraw-review-chrome-latest.png |
++-----------+-----------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-withdraw-review-android-latest.png|
+| | :width: 30% |
++-----------+-----------------------------------------------------------------+
+
+
+
+
+.. _cta-withdraw-ref:
+
+cta-withdraw
+------------
+
+This screen is used for the confirmation of a manual withdrawal,
+bank-integrated witdrawals and exchange withdrawals. the success of this
+operation will be an increase of the balance in the wallet. fee, restrictions
+and ETA should be clear for the user.
+
+Info
+^^^^
+
+* exchange to be used showing the URL
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+* starting currency: if the exchange has the currency conversion service enabled user should be able to the details based on the wire transfer currency
+* taler URI: show copy button or QR to complete the operation with another device
+
+Inputs
+^^^^^^
+
+* age restriction: allow the selection of the restriction in the age group possible by the exchange
+* service provider: allow the selection of different exchange
+
+Actions
+^^^^^^^
+
+* **confirm operation**: on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **review and confirm ToS**: if the current selected exchange has a version of ToS that the user didn't yet accepted, use the :ref:`cta-accept-tos-ref` screen
+* **cancel**: user will be redirected to ``balance``
+
+.. attention::
+ User should be able to play with the amount, not possible in the current design
+
+Issues
+^^^^^^
+
+* All(screenshots): the flow here diverges completely betwen the different platforms (very bad).
+* Android(major): there really should not be a "check fees" button here, the fees should just be dynamically calculated and shown immediately. Note that the dialog shown here is thus closer to what we might make the future main
+ withdraw review (:ref:`cta-withdraw-review-ref`) dialog.
+
+
+Adoption
+^^^^^^^^
+
+* We should probably keep the "specify the origin
+ of the money" from Firefox as a dialog after
+ "Receive funds" is selected in the
+ transaction list (:ref:`cta-transaction-list-ref`),
+ but without the amount entry. Keep it simple,
+ mostly binary choice: withdraw, invoice, or back.
+* The iOS dialog is reasonably close to the future
+ withdraw review (:ref:`cta-withdraw-review-ref`) dialog,
+ allowing amount entry, shows fees, and final "confirm"
+ button (I guess: "review ToS" should be an alternative
+ button label). Eventually, we'll want an "edit" (pen)
+ icon next to the exchange URL, and
+ if context has fixed the amount or exchange, the
+ respective buttons should be hidden.
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+----------------------------------------------------------------+
+| Platform | Screenshot |
++===========+================================================================+
+| WebEx | .. image:: ../screenshots/cta-withdraw-firefox-latest.png |
++-----------+----------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-withdraw-android-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-withdraw-ios-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------+
+
+
+.. _cta-withdraw-confirm-ref:
+
+cta-withdraw-confirm
+--------------------
+
+This screen is used to ask the the user to confirm the withdrawal operation,
+now that all data has been provided.
+
+
+Info
+^^^^
+
+* service provider to be used showing the URL
+* amount to be withdrawn
+* applicable fees (if any)
+
+Inputs
+^^^^^^
+
+* none?
+
+
+Actions
+^^^^^^^
+
+* **confirm withdraw**: advances to next screen
+* **back**: user will be redirected to previous screen
+
+Issues
+^^^^^^
+
+* All: This dialog should be merged with
+ the future main
+ withdraw review (:ref:`cta-withdraw-review-ref`) dialog
+ where the amount and exchange could be edited (if
+ permitted by trigger event) and the fees are dynamically
+ shown. No point in yet another dialog.
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-withdraw-confirm-firefox-latest.png|
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-withdraw-confirm-android-latest.png|
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+
+
+.. _cta-wire-transfer-ref:
+
+cta-wire-transfer
+-----------------
+
+This screen is used to show the user the information for the wire transfer to
+complete a manual withdrawal operation.
+
+Info
+^^^^
+
+* wire transfer subject to be used (first, most important)
+* target bank account to transfer funds to (e.g. IBAN)
+* total amount to transfer in the wire transfer currency
+* button to copy ``payto://`` URI with the information to clipboard
+
+Actions
+^^^^^^^
+
+* **abort**: aborts the withdrawal operation
+* **menu**: go back to the main balances list (operation continues in background)
+* **automatic**: screen changes to "cta-withdraw-done" upon completion
+
+Issues
+^^^^^^
+
+* All(text): if there is no fee, no point in showing
+ the amount **3** times. Maybe only show the amount
+ on top with the wire transfer details, and then at
+ the bottom show the fee ONCE *if* there is one?
+* Android(text): uses "exchange", which is verboten
+* iOS(text): receiver name is missing, MUST be before IBAN
+* Android(text): wire transfer subject is third, should be first
+* WebEx(screenshot): the screenshot does not show how to select an alternative bank (see: Netzbon). Would be nice to show that.
+* Android(screenshot): the screenshot does not show how to select an alternative bank (see: Netzbon). Would be nice to show that.
+
+
+
+Adoption
+^^^^^^^^
+
+* WebEx/Android(text): Probably best to take the full text of the 3 steps from iOS (but add receiver name in step 2!) to provide very precise instructions.
+* WebEx/Android(text): "Open in banking app" is likely unclear that this requires Payto://-support. Use the long text from iOS everywhere!
+* iOS(minor): the way to switch between different banks is not as clear as it was on WebEx. Proposal: use notebook tabs similar to how it is done on WebEx (IIRC?)
+* Android: transaction status is not shown ("pending" on all other platforms. This is actually *good*, as once we are no longer "pending" the wire transfer details will no longer be shown, so the entire screen will look different. So we can probably get rid of the "This transaction is not complete" and "Status: Pending" on WebEx/iOS as that is *always* the case when we give the user wire transfer instructions here!
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-wire-transfer-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-wire-transfer-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-wire-transfer-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-withdraw-done-ref:
+
+cta-withdraw-done
+-----------------
+
+This screen is used to show the user the information for a completed withdraw
+operation (bank-integrated or manual).
+
+Info
+^^^^
+
+* amount wired (hidden if no fees)
+* fees paid (hidden if no fees)
+* total amount withdrawn into wallet (effective balance change)
+* exchange base URL
+* date
+
+Actions
+^^^^^^^
+
+* **delete**: deletes information about the withdrawal operation
+
+Issues
+^^^^^^
+
+* WebEx(text): not point in showing details if there are no fees.
+* iOS(text): not point in showing details if there are no fees.
+* Android(text): still talks about 'exchange'
+* Android(text): has the amount twice, useless without fees
+* iOS(text): status: Done is unnecessary, if we show this screen, it will always be 'done' (ok, theoretically in the middle of withdrawing, but the wire transfer will be done; but then maybe show 'incoming' but hide the status once really "done").
+* unclear: "Delete" vs. 'Delete from history" --- two
+ styles, two translations, gain?
+
+
+Adoption
+^^^^^^^^
+
+* We probably want to show a "pending" transaction if we
+ are still withdrawing (wire transfer did arrived, coins
+ are still being generated). That's a very brief time,
+ but we probably want to use a minor variation of this
+ dialog in that case. Not sure it needs to be quite
+ as prominent as on iOS though...
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-withdraw-done-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-withdraw-done-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-withdraw-done-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-url-entry-ref:
+
+cta-url-entry
+-------------
+
+This screen allows the user to scan a QR code, scan an NFC tag, or enter a
+taler://-URL. Its implementation may differ significantly between
+platforms. For example, scanning NFC tags may be fully automated, scanning QR
+codes may involve some system applications, and maybe the dialog only allows
+the URL entry *or* the camera but not both at the same time, depending on
+implementation specifics.
+
+Info
+^^^^
+
+* camera with current image to enable user to focus on QR code
+* current URL, with information if it is not well-formed for GNU Taler
+* possibly status information on NFC reader (if available)
+
+Actions
+^^^^^^^
+
+* **open**: if entered manually, open URL as-entered (otherwise open is automatic)
+* **back**: return to previous view
+
+Issues
+^^^^^^
+
+* Android(text) has button label "OK", should probably be "Open" for uniformity.
+* Android(text) has "Enter Taler URI", while WebEx has clearer text "enter taler:// URI".
+* iOS (screenshot): lacks dialog (or screenshot?) entirely, not sure if we need manual URL-entry on mobile though, so could be OK!
+
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-url-entry-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-url-entry-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-payment-ref:
+
+cta-payment
+-----------
+
+This screen is used for the confirmation of a payment to a merchant. the
+success of this operation will be an decrease of the balance in the wallet and
+save a ticket/invoice of the purchase. fee, restrictions and ETA should be
+clear for the user.
+
+Info
+^^^^
+
+* merchant offering the order showing the URL
+* order summary
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+* receipt: order id
+* payment deadline: absolute time before the claimed order expires
+* taler URI: show copy button or QR to complete the operation with another device
+* cant pay desc: if the user has enough balance but unable to use it
+* payment status: if the
+
+Actions
+^^^^^^^
+
+* **confirm operation**: if the payment is possible, on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **get more cash**: if there is not enough balance, it will be redirected to :ref:`cta-withdraw-ref`
+* **cancel**: user will be redirected to ``balance``
+
+Issues
+^^^^^^
+
+* WebEx(Text): "Valid until" is likely confusing, not shown on other platforms. Maybe only show "offer expired" instead of pay button (on all platforms!)?
+* WebEx(Screenshot): would be good to show the dialog with expanded order details as well, maybe even expand by default like on Android (and forgo the button?)
+* WebEx(Screenshot): would be good to show a version of the dialog with fees in the screenshot.
+* iOS(text): the label 'Summary' appears only on iOS, and seems unnecessary. Especially since no long-form is available anywhere.
+* All(screenshot): would be good to have sceenshots of the main variations: with/without product preview images, with/without fees, full details/partial details (if we have such a toggle).
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-payment-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-payment-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-payment-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-payment-paid-ref:
+
+cta-payment-paid
+----------------
+
+This screen is used to show information with details about a historic payment.
+
+Info
+^^^^
+
+* merchant offering the order showing the URL
+* order summary
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+* receipt: order id
+* payment status: if the order was refunded
+* Text to use:
+
+ * **Title**. Name of the merchant paid
+ (e.g. Essay Shop)
+ * **Status**. It provides complementary information about the transaction
+ for the user, such as the status of the transaction (e.g. “Waiting for
+ confirmation,” “KYC required,” an error message, etc.). (The summary is
+ provided by wallet-core, along with internationalized versions.)
+ * **Timestamp**. The moment when the payment was made. Ideally, it
+ should be shown with minimal precision, only showing the minutes, hours or
+ days that have elapsed.
+ * **Amount**. The negative impact that the transaction has in
+ the total balance of the currency scope. It should be made clear that the amount is negative, ideally with a sign (-) and a color (red).
+
+
+Actions
+^^^^^^^
+
+* **delete**: delete information about the transaction
+* **back**: user will be redirected to ``balance``
+
+Issues
+^^^^^^
+
+* Android(text): details say receipt, but WebEx uses the slightly more accurate "Invoice ID". Could also use "Order #"?
+* iOS(major): delete button is missing, instead only has "Done"
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-payment-paid-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-payment-paid-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-payment-paid-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-deposit-ref:
+
+cta-deposit
+------------
+
+This screen is used for the confirmation of a deposit. the success of this
+operation will be an decrease of the balance in the wallet and save a deposit
+ticket for reference. fee, restrictions and ETA should be clear for the user.
+
+Info
+^^^^
+
+* bank account where the money is going to
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+
+Actions
+^^^^^^^
+
+* **confirm operation**: on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **cancel**: user will be redirected to ``balance``
+
+.. attention::
+ User should be able to play with the amount, not possible in the current design
+
+Issues
+^^^^^^
+
+* WebEx(screenshot): need new section in this document for 'ctx-manage-account'.
+* WebEx(minor): should probably separate out target account selection and amount entry, as done on iOS
+* WebEx(text): "amount" is a bad label, "brut amount" might be good, then use "net deposit"
+* WebEx(text): might be good to have "net deposit" amount also editable, not just computed (and then update "brut amount").
+* Android(screenshot): lacks screenshots for dialogs where we enter/edit accounts and amounts
+* Android(minor): if we do it iOS/WebEx style, we can probalby skip the 'pure' confirmation dialog and have the one where the brut/net amounts are entered be the final one in the sequence before the transaction happens
+* all(screenshot): we need a new dialog showing the transaction in-flight/done as reachable via transaction history
+
+
+Adoption
+^^^^^^^^
+
+* iOS: I think it makes sense to split this section up into account selection / management and amount entry & confirmation; we should only have one major screen per section! Please split other UIs similarly and let's introduce a new section here.
+
+
+
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-deposit-firefox-latest.png |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-deposit-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-deposit-1-ios-latest.png |
+| | :width: 30% |
+| | .. image:: ../screenshots/cta-deposit-2-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+
+.. _cta-peer-pull-initiate-ref:
+
+cta-peer-pull-initiate
+----------------------
+
+This screen is used for the confirmation of the creation of a peer pull
+transaction or invoice to request money from another wallet. the success of
+this operation will not change the balance immediately in the wallet and allow
+the user to share a taler URI to the payer. fee, restrictions and ETA should
+be clear for the user.
+
+Info
+^^^^
+
+* exchange to be used showing the URL
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+
+Inputs
+^^^^^^
+
+* **subject**: short description of the transaction
+* **expiration**: absolute time/date after which the invoice is not valid anymore
+* **service provider**: allow the selection of different exchange
+
+Actions
+^^^^^^^
+
+* **confirm operation**: on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **review and confirm ToS**: if the current selected exchange has a version of ToS that the user didn't yet accepted, use the :ref:`cta-accept-tos-ref` screen
+* *cancel*: user will be redirected to ``balance``
+
+.. attention::
+ Is the invoice creation always free of charge or does the exchange have a mechanism
+ to impose a fee to pay on creation?
+
+Issues
+^^^^^^
+
+* Android(text): Webex uses "1 Week" instead of "7 days", let's use "week".
+* iOS(text): 3 min/ 1h are inconsistent; other wallets have 1 day, 7 days, 30 days. We should be consistent.
+
+
+Adoption
+^^^^^^^^
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+----------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+======================================================================+
+| WebEx | .. image:: ../screenshots/cta-peer-pull-initiate-firefox-latest.png |
++-----------+----------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-peer-pull-initiate-android-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-peer-pull-initiate-ios-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------------+
+
+
+.. _cta-peer-pull-confirm-ref:
+
+cta-peer-pull-confirm
+---------------------
+
+This screen is used for the confirmation of the payment of a peer pull
+transaction or invoice to send money from another wallet. the success of this
+operation will be an will decrease the balance in the wallet. fee,
+restrictions and ETA should be clear for the user.
+
+Info
+^^^^
+
+* exchange to be used showing the URL
+* subject: short description of the transaction
+* table of details of the operation: use the :ref:`operation-table-details-ref` screen
+* expiration: absolute time/date after which the invoice is not valid anymore
+
+Actions
+^^^^^^^
+
+* **confirm operation**: if the payment is possible, on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **get more cash**: if there is not enough balance, it will be redirected to :ref:`cta-withdraw-ref`
+* **cancel**: user will be redirected to ``balance``
+
+cta-peer-push-initiate
+----------------------
+
+This screen is used for the confirmation of the creation of a peer push
+transaction or transfer money to another wallet. the success of this
+operation will reduce the balance immediately in the wallet and allow the user
+to share a taler URI to the receiver. fee, restrictions and ETA should be
+clear for the user.
+
+Info
+^^^^
+
+* table of details of the operation: use the ``operation-table-details`` screen
+
+Inputs
+^^^^^^
+
+* **subject**: short description of the transaction
+* **expiration**: absolute time/date after which the transfer is not valid anymore
+
+Actions
+^^^^^^^
+
+* **confirm operation**: on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **cancel**: user will be redirected to ``balance``
+
+Issues
+^^^^^^
+
+* WebEx(minor): forces user to scroll to the bottom. Android nicely shows the accept button always; that seems nicer;
+* WebEx(text): has text about "Digital cash withdrawal" above. Would be more consistent if we also *only* showed the Terms of service.
+* Android(text): uses "Exchange", should say "PSP's Terms of Service"
+* iOS(screenshot): lacks actual ToS, not ideal to compare!
+
+
+Adoption
+^^^^^^^^
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+----------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+======================================================================+
+| WebEx | .. image:: ../screenshots/cta-peer-push-initiate-firefox-latest.png |
++-----------+----------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-peer-push-initiate-android-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-peer-push-initiate-ios-latest.png |
+| | :width: 30% |
++-----------+----------------------------------------------------------------------+
+
+
+.. _cta-peer-push-confirm-ref:
+
+cta-peer-push-confirm
+---------------------
+
+This screen is used for the confirmation of the acceptance of a peer push
+transaction or transfer money to this wallet. the success of this operation
+will be an will decrease the balance in the wallet. fee, restrictions and ETA
+should be clear for the user.
+
+Info
+^^^^
+
+* subject: short description of the payment
+* expiration: absolute time/date after which the invoice is not valid anymore
+* table of details of the operation: use the ``operation-table-details`` screen
+
+Actions
+^^^^^^^
+
+* **confirm operation**: on success will be redirected to the ``transaction-details`` screen where the detail of the current transaction will be displayed
+* **review and confirm ToS**: if the current selected exchange has a version of ToS that the user didn't yet accepted, use the :ref:`cta-accept-tos-ref` screen
+* **cancel**: user will be redirected to ``balance``
+
+Issues
+^^^^^^
+
+* All(screenshot): we have no screenshots!
+
+
+Adoption
+^^^^^^^^
+
+
+Screenshots
+^^^^^^^^^^^
+
+
+.. _operation-table-details-ref:
+
+operation-table-details
+-----------------------
+
+With the table it should be clear how much the operation will cost, the
+initial amount and the final amount with all the items related to the
+operations (like fee)
+
+Labels
+^^^^^^
+
+Initial amount of the operation, and final amount are always shown. Fee should
+be shown as an extra row in the table if it is non-zero. Converted amount
+should be shown as an extra row if initial amount currency is not the same as
+the final amount currency.
+
+Initial amount label by operation:
+
+ * payment -> Price
+ * deposit -> Send
+ * peer-pull-credit -> Invoice
+ * peer-pull-debit -> Invoice
+ * peer-push-debit -> Send
+ * peer-push-credit -> Transfer
+ * withdrawal -> Transfer
+ * refund -> Refund
+
+
+.. _cta-accept-tos-ref:
+
+accept-tos
+----------
+
+This screen can be use everytime that the user is going to interact with an
+exchange. since at any moment wallet may find that ToS changed the user needs
+to be prevented from continue before reading/accepting new rules. If possible,
+this screen should be used inplace of other actions and hidden if not required
+(for example, user already accepted ToS)
+
+Inputs
+^^^^^^
+
+* **format**: allow the selection of a ToS format
+* **languange**: allow the selection of a languange different from system lang
+
+Actions
+^^^^^^^
+
+* **accept tos**: will mark this version as accepted in wallet core and redirect the user to the screen from where it was invoked
+* **save/print tos**: will save the ToS outside of the wallet
+
+Issues
+^^^^^^
+
+
+
+Adoption
+^^^^^^^^
+
+
+Screenshots
+^^^^^^^^^^^
+
++-----------+------------------------------------------------------------------+
+| Platform | Screenshot |
++===========+==================================================================+
+| WebEx | .. image:: ../screenshots/cta-accept-tos-firefox-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| Android | .. image:: ../screenshots/cta-accept-tos-android-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+| iOS | .. image:: ../screenshots/cta-accept-tos-ios-latest.png |
+| | :width: 30% |
++-----------+------------------------------------------------------------------+
+
+.. _cta-settings-ref:
+
+settings
+--------
+
+Info
+^^^^
+
+Layout
+^^^^^^
+
+Actions
+^^^^^^^
+
+Issues
+^^^^^^
+
+Adoption
+^^^^^^^^
+
+Screenshots
+^^^^^^^^^^^
+
+
+.. _cta-devtools-ref:
+
+developer-tools
+---------------
+
+Info
+^^^^
+
+Layout
+^^^^^^
+
+Actions
+^^^^^^^
+
+Issues
+^^^^^^
+
+Adoption
+^^^^^^^^
+
+Screenshots
+^^^^^^^^^^^
+
+
+
+
+
+Q / A
+=====
diff --git a/design-documents/054-dynamic-form.rst b/design-documents/054-dynamic-form.rst
new file mode 100644
index 00000000..d93b3684
--- /dev/null
+++ b/design-documents/054-dynamic-form.rst
@@ -0,0 +1,201 @@
+DD 54: Dynamic Web Form
+#######################
+
+Summary
+=======
+
+This document outlines the approach for implementing a dynamic web form feature.
+
+Motivation
+==========
+
+Currently, creating a new form for a web app involves coding a new
+page with HTML, CSS, and JS. Exchange AML requires multiple forms,
+and different instances may have distinct forms based on jurisdiction.
+
+
+Requirements
+============
+
+A form consist of a layout and a set of fields.
+
+Layout requirements
+-------------------
+
+* **editable by system admin**: System admins should be able to create new forms
+ or edit current one shipped with the source.
+
+* **accesibility**: Forms should meet accessibility level AA.
+
+* **responsive**: Forms should be responsive and function on all devices.
+
+* **metadata**: Generated form information should contain enough data
+ to handle multiple form versions.
+
+Fields requirements
+-------------------
+
+* **validations**: Each field may require custom validation
+
+* **custom data type**: A field may consist of a list, string, number, or a
+ complex composite structure.
+
+
+Proposed Solutions
+==================
+
+Forms are initialized using a flexible structure defined by the
+TypeScript interface FormType<T>. This interface comprises properties
+such as value (current form data), initial (initial form data for resetting),
+readOnly (flag to disable input), onUpdate (callback on form data update),
+and computeFormState (function to derive the form state based on current data).
+
+
+.. code-block:: typescript
+
+ interface FormType<T extends object> {
+ value: Partial<T>;
+ initial?: Partial<T>;
+ readOnly?: boolean;
+ onUpdate?: (v: Partial<T>) => void;
+ computeFormState?: (v: Partial<T>) => FormState<T>;
+ }
+
+
+``T``: is the type of the result object
+``value``: is a reference to the current value of the result
+``initial``: data for resetting
+``readOnly``: when true, fields won't allow input
+``onUpdate``: notification of the result update
+``computeFormState``: compute a new state of the form based on the current value
+
+Form state have the same shape of ``T`` but every field type is ``FieldUIOptions``.
+
+Fields type can be:
+ * strings
+ * numbers
+ * boolean
+ * arrays
+ * object
+
+The field type ``AmountJson`` and ``AbsoluteTime`` are opaque since field is used as a whole.
+
+The form can be instanciated using
+
+.. code-block:: typescript
+
+ import { FormProvider } from "@gnu-taler/web-util/browser";
+
+
+Then the field component can access all the properties by the ``useField(name)`` hook,
+which will return
+
+.. code-block:: typescript
+
+ interface InputFieldHandler<Type> {
+ value: Type;
+ onChange: (s: Type) => void;
+ state: FieldUIOptions;
+ isDirty: boolean;
+ }
+
+
+``value``: the current value of the field
+``onChange``: a function to call anytime the user want to change the value
+``state``: the state of the field (hidden, error, etc..)
+``isDirty``: if the user already tried to change the value
+
+A set of common form field exist in ``@gnu-taler/web-util``:
+
+ * InputAbsoluteTime
+ * InputAmount
+ * InputArray
+ * InputFile
+ * InputText
+ * InputToggle
+
+and should be used inside a ``Form`` context.
+
+.. code-block:: none
+
+ function MyFormComponent():VNode {
+ return <FormProvider>
+ <InputAmount name="amount" />
+ <InputText name="subject" />
+ <button type="submit"> Confirm </button>
+ </FormProvider>
+ }
+
+
+Example
+--------
+
+Consider a form shape represented by the TypeScript type:
+
+.. code-block:: typescript
+
+ type TheFormType = {
+ name: string,
+ age: number,
+ savings: AmountJson,
+ nextBirthday: AbsoluteTime,
+ pets: string[],
+ addres: {
+ street: string,
+ city: string,
+ }
+ }
+
+An example instance of this form could be:
+
+.. code-block:: typescript
+
+ const theFormValue: TheFormType = {
+ name: "Sebastian",
+ age: 15,
+ pets: ["dog","cat"],
+ address: {
+ street: "long",
+ city: "big",
+ }
+ }
+
+
+For such a form, a valid state can be computed using a function like
+``computeFormStateBasedOnFormValues``, returning an object indicating
+the state of each field, including properties such as ``hidden``,
+``disabled``, and ``required``.
+
+
+.. code-block:: javascript
+
+ function computeFormStateBasedOnFormValues(formValues): {
+ //returning fixed state as an example
+ //the return state will be commonly be computed from the values of the form
+ return {
+ age: {
+ hidden: true,
+ },
+ pets: {
+ disabled: true,
+ elements: [{
+ disabled: false,
+ }],
+ },
+ address: {
+ street: {
+ required: true,
+ error: "the street name was not found",
+ },
+ city: {
+ required: true,
+ },
+ },
+ }
+ }
+
+
+
+
+Q / A
+=====
diff --git a/design-documents/055-wallet-problem-report.rst b/design-documents/055-wallet-problem-report.rst
new file mode 100644
index 00000000..2fd7a221
--- /dev/null
+++ b/design-documents/055-wallet-problem-report.rst
@@ -0,0 +1,114 @@
+DD 55: Wallet Problem Reports
+#############################
+
+.. note::
+
+ **Status**: Early work in progress / DD number reservation.
+
+.. warning::
+
+ We concluded that we don't need the problem reports feature right now,
+ as all cases we care about are already covered by something else.
+
+Summary
+=======
+
+This design document specifies global error reports generated/managed by wallet-core
+and rendered by the wallet UIs.
+
+Motivation
+==========
+
+Sometimes the wallet encounters issues that go beyond the scope of single transaction.
+
+Requirements
+============
+
+* problem reports must have a clear lifecycle
+* problem reports must have some type of identification that allows to
+ easily find out if a new problem report needs to be created when an
+ error happens or whether an existing one has been created
+
+Proposed Solution
+=================
+
+Report identification
+---------------------
+
+The report identifier serves multiple purposes:
+
+1. Usage as a reference in wallet-core APIs
+2. De-duplication. The report ID should allow easy identification of an already existing report for a particular problem.
+
+New wallet-core requests
+------------------------
+
+* ``listProblemReports``
+* ``acknowledgeProblemReport``: Mark a problem report as read.
+* ``deleteProblemReport``: Delete the problem report.
+
+New wallet-core notification type
+---------------------------------
+
+* ``problem-report`` to notify clients about status changes or an error report
+ (including creation!)
+
+
+Types of reports
+----------------
+
+(Currently we don't have any good examples where this is actually needed.)
+
+Examples of what should NOT be a report
+---------------------------------------
+
+* money lost due to the exchange stopping to offer a denomination
+
+ * => Should be a transactions item
+
+* money locked behind a (long) pending refresh
+
+ * => Should be a pending transaction
+
+* money lost due to a permanently failing refresh
+
+ * => pending or final transaction item
+
+* money lost due to expired denominations (auto-refresh wasn't done fast enough)
+
+ * => transaction item
+
+* a denomination changed its info (expiration, fees)
+
+ * => exchange entry state
+
+* Important information about the exchange changed (master pub, accounts, keys)
+
+ => exchange entry state
+
+
+Definition of Done
+==================
+
+TBD.
+
+Alternatives
+============
+
+* Report problems with an API specific to each resource (exchange entry, transaction, ...)
+* Have an *alerts* API that returns alerts to the client that the client can show to to the user,
+ but that a user can't interact with.
+
+Drawbacks
+=========
+
+TBD.
+
+Discussion / Q&A
+================
+
+* When is a report amended vs a new report created?
+
+ * example: Exchange stops offering denomination D1. Later, it stops offering D2.
+ Are two reports generated or is the first report changed?
+
diff --git a/design-documents/999-template.rst b/design-documents/999-template.rst
index f620248d..988f163f 100644
--- a/design-documents/999-template.rst
+++ b/design-documents/999-template.rst
@@ -1,5 +1,5 @@
-Template
-########
+DD XY: Template
+###############
Summary
=======
@@ -13,6 +13,13 @@ Requirements
Proposed Solution
=================
+Definition of Done
+==================
+
+(Only applicable to design documents that describe a new feature. While the
+DoD is not satisfied yet, a user-facing feature **must** be behind a feature
+flag or dev-mode flag.)
+
Alternatives
============
diff --git a/design-documents/index.rst b/design-documents/index.rst
index 1ed8ffff..454564d0 100644
--- a/design-documents/index.rst
+++ b/design-documents/index.rst
@@ -6,7 +6,10 @@ The goal of these documents is to discuss facilitate discussion around
new features while keeping track of the evolution of the whole system
and protocol.
+Design documents that start with "XX" are considered deprecated.
+
.. toctree::
+ :maxdepth: 1
:glob:
001-new-browser-integration
@@ -28,7 +31,7 @@ and protocol.
017-backoffice-inventory-management
018-contract-json
019-wallet-backup-merge
- 020-backoffice-tips-management
+ 020-backoffice-rewards-management
021-exchange-key-continuity
022-wallet-auditor-reports
023-taler-kyc
@@ -36,9 +39,32 @@ and protocol.
025-withdraw-from-wallet
026-refund-fees
027-sandboxing-taler.rst
- 028-proof-of-escrow
+ 028-deposit-policies
029-mobile-ui
030-offline-payments
031-invoicing
- 032-auctions
+ 032-brandt-vickrey-auctions
+ 033-database
+ 034-wallet-db-migration
+ 035-regional-currencies
+ 036-currency-conversion-service
+ 037-wallet-transactions-lifecycle.rst
+ 038-demobanks-protocol-suppliers.rst
+ 039-taler-browser-integration.rst
+ 040-distro-packaging.rst
+ 041-wallet-balance-amount-definitions.rst
+ 042-synthetic-wallet-errors.rst
+ 043-managing-prebuilt-artifacts.rst
+ 044-ci-system.rst
+ 045-kyc-inheritance.rst
+ 046-mumimo-contracts.rst
+ 047-stefan.rst
+ 048-wallet-exchange-lifecycle.rst
+ 049-auth.rst
+ 050-libeufin-nexus.rst
+ 051-fractional-digits.rst
+ 052-libeufin-bank-2fa.rst
+ 053-wallet-ui.rst
+ 054-dynamic-form.rst
+ 055-wallet-problem-report.rst
999-template
diff --git a/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-01.png b/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-01.png
new file mode 100644
index 00000000..f6ff6bfc
--- /dev/null
+++ b/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-01.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-latest.png b/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-latest.png
new file mode 120000
index 00000000..69fa1dbb
--- /dev/null
+++ b/design-documents/wallet-screenshots/ios-wallet/10-empty-wallet-latest.png
@@ -0,0 +1 @@
+10-empty-wallet-01.png \ No newline at end of file
diff --git a/design-documents/wallet-screenshots/ios-wallet/11-balances-list-01.png b/design-documents/wallet-screenshots/ios-wallet/11-balances-list-01.png
new file mode 100644
index 00000000..1cd5e287
--- /dev/null
+++ b/design-documents/wallet-screenshots/ios-wallet/11-balances-list-01.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/ios-wallet/11-balances-list-latest.png b/design-documents/wallet-screenshots/ios-wallet/11-balances-list-latest.png
new file mode 120000
index 00000000..93b135ab
--- /dev/null
+++ b/design-documents/wallet-screenshots/ios-wallet/11-balances-list-latest.png
@@ -0,0 +1 @@
+11-balances-list-01.png \ No newline at end of file
diff --git a/design-documents/wallet-screenshots/webex-wallet/READE b/design-documents/wallet-screenshots/webex-wallet/READE
new file mode 100644
index 00000000..597fa22e
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/READE
@@ -0,0 +1,22 @@
+Wallet Web Extension screenshots
+--------------------------------
+
+Screenshoots are organized by common use case using
+the happy path to ilustrate the user flow:
+
+ * withdrawal: starts from an empty wallet and performs
+ a manual withdrawal with demo exchange.
+ * payment: starts from demo shop and pay for an article.
+ also showing how a refund looks.
+ * transfer: performs a p2p push debit and credit.
+ * invoice: performs a p2p pull credit and debit.
+ * deposit: starts from a wallet with some balance
+ and deposit into a bank account new yet
+ known by the wallet.
+ * add-exchange: add the test exchange into the wallet.
+
+In this root folder there are some additionals screen for
+settings, dev tools and others.
+
+Screenshot starting with "store-*" are used in chrome webstore
+and firefox addon store.
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/1-balance.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/1-balance.png
new file mode 100644
index 00000000..1041e2ee
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/1-balance.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/10-testkudos-in-the-list.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/10-testkudos-in-the-list.png
new file mode 100644
index 00000000..12a53026
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/10-testkudos-in-the-list.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/11-select-test-kudos.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/11-select-test-kudos.png
new file mode 100644
index 00000000..43e18c89
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/11-select-test-kudos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/2-click-get-cash.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/2-click-get-cash.png
new file mode 100644
index 00000000..d7d247f0
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/2-click-get-cash.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/3-click-add-exchange.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/3-click-add-exchange.png
new file mode 100644
index 00000000..7a1a48e9
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/3-click-add-exchange.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/4-input-URL.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/4-input-URL.png
new file mode 100644
index 00000000..efdf852a
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/4-input-URL.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/5-click-next.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/5-click-next.png
new file mode 100644
index 00000000..9a2782f2
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/5-click-next.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/6-review-tos.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/6-review-tos.png
new file mode 100644
index 00000000..44a85d6f
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/6-review-tos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/7-accept-tos.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/7-accept-tos.png
new file mode 100644
index 00000000..c73c5256
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/7-accept-tos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/8-confirm.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/8-confirm.png
new file mode 100644
index 00000000..1041e2ee
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/8-confirm.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/add-exchange/9-click-get-cash.png b/design-documents/wallet-screenshots/webex-wallet/add-exchange/9-click-get-cash.png
new file mode 100644
index 00000000..d7d247f0
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/add-exchange/9-click-get-cash.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/1-balance.png b/design-documents/wallet-screenshots/webex-wallet/deposit/1-balance.png
new file mode 100644
index 00000000..7c571c48
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/1-balance.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/10-balance.png b/design-documents/wallet-screenshots/webex-wallet/deposit/10-balance.png
new file mode 100644
index 00000000..4e3a787b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/10-balance.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/2-click-send.png b/design-documents/wallet-screenshots/webex-wallet/deposit/2-click-send.png
new file mode 100644
index 00000000..2cdb5fe0
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/2-click-send.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/3-click-deposit.png b/design-documents/wallet-screenshots/webex-wallet/deposit/3-click-deposit.png
new file mode 100644
index 00000000..afc4a431
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/3-click-deposit.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/4-click-add-account.png b/design-documents/wallet-screenshots/webex-wallet/deposit/4-click-add-account.png
new file mode 100644
index 00000000..c91bc8d9
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/4-click-add-account.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/5-complete-form.png b/design-documents/wallet-screenshots/webex-wallet/deposit/5-complete-form.png
new file mode 100644
index 00000000..77514161
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/5-complete-form.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/6-click-add.png b/design-documents/wallet-screenshots/webex-wallet/deposit/6-click-add.png
new file mode 100644
index 00000000..e78538c7
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/6-click-add.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/7-confirm-deposit.png b/design-documents/wallet-screenshots/webex-wallet/deposit/7-confirm-deposit.png
new file mode 100644
index 00000000..bf2c61bc
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/7-confirm-deposit.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/8-show-transaction.png b/design-documents/wallet-screenshots/webex-wallet/deposit/8-show-transaction.png
new file mode 100644
index 00000000..381ae09e
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/8-show-transaction.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/deposit/9-after-deposit-confirmed.png b/design-documents/wallet-screenshots/webex-wallet/deposit/9-after-deposit-confirmed.png
new file mode 100644
index 00000000..74c2026d
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/deposit/9-after-deposit-confirmed.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/dev-tools.png b/design-documents/wallet-screenshots/webex-wallet/dev-tools.png
new file mode 100644
index 00000000..a817d055
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/dev-tools.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/1-balance.png b/design-documents/wallet-screenshots/webex-wallet/invoice/1-balance.png
new file mode 100644
index 00000000..a6692c4f
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/1-balance.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/10-confirm-transfer.png b/design-documents/wallet-screenshots/webex-wallet/invoice/10-confirm-transfer.png
new file mode 100644
index 00000000..2611c9f3
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/10-confirm-transfer.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/11-show-history.png b/design-documents/wallet-screenshots/webex-wallet/invoice/11-show-history.png
new file mode 100644
index 00000000..52907939
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/11-show-history.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/2-click-add.png b/design-documents/wallet-screenshots/webex-wallet/invoice/2-click-add.png
new file mode 100644
index 00000000..7834436d
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/2-click-add.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/3-input-six.png b/design-documents/wallet-screenshots/webex-wallet/invoice/3-input-six.png
new file mode 100644
index 00000000..0f910210
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/3-input-six.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/4-click-invoice.png b/design-documents/wallet-screenshots/webex-wallet/invoice/4-click-invoice.png
new file mode 100644
index 00000000..0d7c1b31
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/4-click-invoice.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/5-complete-form.png b/design-documents/wallet-screenshots/webex-wallet/invoice/5-complete-form.png
new file mode 100644
index 00000000..625afb3c
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/5-complete-form.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/6-create-invoice.png b/design-documents/wallet-screenshots/webex-wallet/invoice/6-create-invoice.png
new file mode 100644
index 00000000..def698a8
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/6-create-invoice.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/7-copy-qr-open-qr-page.png b/design-documents/wallet-screenshots/webex-wallet/invoice/7-copy-qr-open-qr-page.png
new file mode 100644
index 00000000..fe221eac
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/7-copy-qr-open-qr-page.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/8-paste-URI.png b/design-documents/wallet-screenshots/webex-wallet/invoice/8-paste-URI.png
new file mode 100644
index 00000000..26351cb9
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/8-paste-URI.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/invoice/9-click-open.png b/design-documents/wallet-screenshots/webex-wallet/invoice/9-click-open.png
new file mode 100644
index 00000000..90393d3b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/invoice/9-click-open.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/1-load-shop.png b/design-documents/wallet-screenshots/webex-wallet/payment/1-load-shop.png
new file mode 100644
index 00000000..4aacc47b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/1-load-shop.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/10-click-article-URL.png b/design-documents/wallet-screenshots/webex-wallet/payment/10-click-article-URL.png
new file mode 100644
index 00000000..74488cb6
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/10-click-article-URL.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/2-click-first-article.png b/design-documents/wallet-screenshots/webex-wallet/payment/2-click-first-article.png
new file mode 100644
index 00000000..31635b8a
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/2-click-first-article.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/3-confirm-payment.png b/design-documents/wallet-screenshots/webex-wallet/payment/3-confirm-payment.png
new file mode 100644
index 00000000..016f5514
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/3-confirm-payment.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/4-click-refund.png b/design-documents/wallet-screenshots/webex-wallet/payment/4-click-refund.png
new file mode 100644
index 00000000..4a3ddd39
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/4-click-refund.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/5-click-request-refund.png b/design-documents/wallet-screenshots/webex-wallet/payment/5-click-request-refund.png
new file mode 100644
index 00000000..9b27cd5b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/5-click-request-refund.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/6-accept-refund.png b/design-documents/wallet-screenshots/webex-wallet/payment/6-accept-refund.png
new file mode 100644
index 00000000..a4b1c137
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/6-accept-refund.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/7-click-refund-detail.png b/design-documents/wallet-screenshots/webex-wallet/payment/7-click-refund-detail.png
new file mode 100644
index 00000000..14291836
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/7-click-refund-detail.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/8-show-history.png b/design-documents/wallet-screenshots/webex-wallet/payment/8-show-history.png
new file mode 100644
index 00000000..2758f7f3
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/8-show-history.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/payment/9-click-receipt.png b/design-documents/wallet-screenshots/webex-wallet/payment/9-click-receipt.png
new file mode 100644
index 00000000..a4b1c137
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/payment/9-click-receipt.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/scan-qr-code.png b/design-documents/wallet-screenshots/webex-wallet/scan-qr-code.png
new file mode 100644
index 00000000..ae28e3db
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/scan-qr-code.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/settings-developer-mode.png b/design-documents/wallet-screenshots/webex-wallet/settings-developer-mode.png
new file mode 100644
index 00000000..c93883dc
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/settings-developer-mode.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/settings-normal-mode.png b/design-documents/wallet-screenshots/webex-wallet/settings-normal-mode.png
new file mode 100644
index 00000000..50f70cab
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/settings-normal-mode.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/store-installed.png b/design-documents/wallet-screenshots/webex-wallet/store-installed.png
new file mode 100644
index 00000000..67c481fc
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/store-installed.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/store-payment.png b/design-documents/wallet-screenshots/webex-wallet/store-payment.png
new file mode 100644
index 00000000..3f776696
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/store-payment.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/store-withdraw.png b/design-documents/wallet-screenshots/webex-wallet/store-withdraw.png
new file mode 100644
index 00000000..8409a0b9
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/store-withdraw.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/1-initial-balance.png b/design-documents/wallet-screenshots/webex-wallet/transfer/1-initial-balance.png
new file mode 100644
index 00000000..20e74cf3
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/1-initial-balance.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/10-open-URI.png b/design-documents/wallet-screenshots/webex-wallet/transfer/10-open-URI.png
new file mode 100644
index 00000000..22f21f89
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/10-open-URI.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/11-accept-transfer.png b/design-documents/wallet-screenshots/webex-wallet/transfer/11-accept-transfer.png
new file mode 100644
index 00000000..9ee2083a
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/11-accept-transfer.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/12-show-history.png b/design-documents/wallet-screenshots/webex-wallet/transfer/12-show-history.png
new file mode 100644
index 00000000..d2d12ca8
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/12-show-history.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/2-click-send.png b/design-documents/wallet-screenshots/webex-wallet/transfer/2-click-send.png
new file mode 100644
index 00000000..0e705860
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/2-click-send.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/3-amount-five.png b/design-documents/wallet-screenshots/webex-wallet/transfer/3-amount-five.png
new file mode 100644
index 00000000..ffe4bee7
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/3-amount-five.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/4-send-wallet.png b/design-documents/wallet-screenshots/webex-wallet/transfer/4-send-wallet.png
new file mode 100644
index 00000000..f566b0cb
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/4-send-wallet.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/5-complete-form.png b/design-documents/wallet-screenshots/webex-wallet/transfer/5-complete-form.png
new file mode 100644
index 00000000..836755ca
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/5-complete-form.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/6-confirm-transfer.png b/design-documents/wallet-screenshots/webex-wallet/transfer/6-confirm-transfer.png
new file mode 100644
index 00000000..55de4bb0
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/6-confirm-transfer.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/7-show-history.png b/design-documents/wallet-screenshots/webex-wallet/transfer/7-show-history.png
new file mode 100644
index 00000000..39de7b66
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/7-show-history.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/8-scan-qr.png b/design-documents/wallet-screenshots/webex-wallet/transfer/8-scan-qr.png
new file mode 100644
index 00000000..cacdce06
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/8-scan-qr.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/transfer/9-paste-URI.png b/design-documents/wallet-screenshots/webex-wallet/transfer/9-paste-URI.png
new file mode 100644
index 00000000..32ac0ff4
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/transfer/9-paste-URI.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/10-transaction-completed.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/10-transaction-completed.png
new file mode 100644
index 00000000..2a774334
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/10-transaction-completed.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/11-history-after-withdraw.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/11-history-after-withdraw.png
new file mode 100644
index 00000000..54b05212
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/11-history-after-withdraw.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/2-empty-wallet.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/2-empty-wallet.png
new file mode 100644
index 00000000..79fc787b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/2-empty-wallet.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/3-get-digital-cash.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/3-get-digital-cash.png
new file mode 100644
index 00000000..273d5f5f
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/3-get-digital-cash.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/4-select-kudos.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/4-select-kudos.png
new file mode 100644
index 00000000..741ac32a
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/4-select-kudos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/5-set-amount-five.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/5-set-amount-five.png
new file mode 100644
index 00000000..8454928b
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/5-set-amount-five.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/6-withdraw-from-bank.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/6-withdraw-from-bank.png
new file mode 100644
index 00000000..e0942a0c
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/6-withdraw-from-bank.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/7-review-tos.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/7-review-tos.png
new file mode 100644
index 00000000..505e06d3
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/7-review-tos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/8-accept-tos.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/8-accept-tos.png
new file mode 100644
index 00000000..61d3c0a4
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/8-accept-tos.png
Binary files differ
diff --git a/design-documents/wallet-screenshots/webex-wallet/withdrawal/9-confirm-withdraw.png b/design-documents/wallet-screenshots/webex-wallet/withdrawal/9-confirm-withdraw.png
new file mode 100644
index 00000000..579803cc
--- /dev/null
+++ b/design-documents/wallet-screenshots/webex-wallet/withdrawal/9-confirm-withdraw.png
Binary files differ