summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--conf.py8
-rw-r--r--core/api-bank-access.rst16
-rw-r--r--core/api-bank-integration.rst5
-rw-r--r--core/api-exchange.rst363
-rw-r--r--core/api-merchant.rst135
-rw-r--r--core/api-sync.rst15
-rw-r--r--core/api-wire.rst2
-rw-r--r--core/taler-uri.rst8
-rw-r--r--design-documents/006-extensions.rst148
-rw-r--r--design-documents/015-merchant-backoffice-routing.rst2
-rw-r--r--design-documents/016-backoffice-order-management.rst44
-rw-r--r--design-documents/018-contract-json.rst6
-rw-r--r--design-documents/022-wallet-auditor-reports.rst54
-rw-r--r--design-documents/023-taler-kyc.rst266
-rw-r--r--design-documents/024-age-restriction.rst511
-rw-r--r--design-documents/025-withdraw-from-wallet.rst69
-rw-r--r--design-documents/index.rst4
-rw-r--r--frags/installing-anastasis-gtk.rst31
-rw-r--r--frags/installing-anastasis.rst30
-rw-r--r--frags/installing-debian.rst39
-rw-r--r--frags/installing-trisquel.rst4
-rw-r--r--index.rst7
-rw-r--r--libeufin/api-nexus.rst159
-rw-r--r--libeufin/api-sandbox.rst357
-rw-r--r--libeufin/bank-transport-ebics.rst2
-rw-r--r--libeufin/banking-protocols.rst2
-rw-r--r--libeufin/concepts.rst2
-rw-r--r--libeufin/demo-deployment-gv.rst49
-rw-r--r--libeufin/ebics.rst2
-rw-r--r--libeufin/frontend.rst2
-rw-r--r--libeufin/index.rst1
-rw-r--r--libeufin/iso20022.rst2
-rw-r--r--libeufin/nexus-tutorial.rst160
-rw-r--r--libeufin/sepa.rst2
-rw-r--r--libeufin/transaction-identification.rst2
-rw-r--r--manpages/taler-bank-benchmark.1.rst89
-rw-r--r--manpages/taler-exchange-aggregator.1.rst4
-rw-r--r--manpages/taler-exchange-dbinit.1.rst4
-rw-r--r--manpages/taler-exchange-offline.1.rst19
-rw-r--r--manpages/taler-exchange-wire-gateway-client.1.rst (renamed from manpages/taler-wire-gateway-client.1.rst)12
-rw-r--r--manpages/taler.conf.5.rst82
-rw-r--r--merchant-spec/public-orders-get.ts232
-rw-r--r--taler-auditor-manual.rst4
-rw-r--r--taler-exchange-manual.rst4
-rw-r--r--taler-exchange-setup-guide.rst987
-rw-r--r--taler-mcig.rst34
-rw-r--r--taler-merchant-manual.rst195
-rw-r--r--wallet-confirm-withdraw.svg16
-rw-r--r--wallet-start-manual-withdraw.svg16
49 files changed, 3714 insertions, 493 deletions
diff --git a/conf.py b/conf.py
index 08e9419..f7aa52e 100644
--- a/conf.py
+++ b/conf.py
@@ -89,7 +89,7 @@ copyright = u'2014-2021 Taler Systems SA (GPLv3+ or GFDL 1.3+)'
# The short X.Y version.
version = '0.8'
# The full version, including alpha/beta/rc tags.
-release = '0.8.0pre0'
+release = '0.8.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -280,6 +280,8 @@ man_pages = [
1),
("manpages/taler-auditor-dbinit.1", "taler-auditor-dbinit",
"setup auditor database", "GNU Taler contributors", 1),
+ ("manpages/taler-auditor-sync.1", "taler-auditor-sync",
+ "tool to safely synchronize auditor database", "GNU Taler contributors", 1),
("manpages/taler-auditor-httpd.1", "taler-auditor-httpd",
"HTTP server providing a RESTful API to access a Taler auditor",
"GNU Taler contributors", 1),
@@ -319,8 +321,10 @@ man_pages = [
("manpages/taler-merchant-setup-reserve.1", "taler-merchant-setup-reserve",
"setup reserve for tipping at a Taler merchant backend", "GNU Taler contributors",
1),
- ("manpages/taler-wire-gateway-client.1", "taler-wire-gateway-client",
+ ("manpages/taler-exchange-wire-gateway-client.1", "taler-exchange-wire-gateway-client",
"trigger a transfer at the bank", "GNU Taler contributors", 1),
+ ("manpages/taler-config.1", "taler-config", "Taler configuration inspection and editing",
+ "GNU Taler contributors", 1),
("manpages/taler.conf.5", "taler.conf", "Taler configuration file",
"GNU Taler contributors", 5),
("manpages/taler-exchange-secmod-eddsa.1", "taler-exchange-secmod-eddsa",
diff --git a/core/api-bank-access.rst b/core/api-bank-access.rst
index ddb2ec8..dfd9dc2 100644
--- a/core/api-bank-access.rst
+++ b/core/api-bank-access.rst
@@ -108,8 +108,9 @@ name and account password, at least in the GNU Taler demo bank implementation.
// only non-null if ``selection_done`` is ``true``.
selected_reserve_pub: string | null;
- // Exchange account selected by the exchange,
- // only non-null if ``selection_done`` is ``true``.
+ // Exchange account selected by the wallet, or by the bank
+ // (with the default exchange) in case the wallet did not provide one
+ // through the Integration API.
selected_exchange_account: string | null;
}
@@ -128,11 +129,12 @@ name and account password, at least in the GNU Taler demo bank implementation.
**Response**
- :http:statuscode:`200 OK`: The withdrawal operation has been confirmed. The response is an empty JSON object.
- :http:statuscode:`409 Conflict`: The reserve operation has been aborted previously and can't be confirmed.
-
-
-
+ :http:statuscode:`200 OK`:
+ The withdrawal operation has been confirmed. The response is an empty JSON object.
+ :http:statuscode:`409 Conflict`:
+ The withdrawal has been aborted previously and can't be confirmed.
+ :http:statuscode:`422 Unprocessable Entity` (Not implemented yet):
+ The withdraw operation cannot be confirmed because no exchange and reserve public key selection happened before.
----------------------
Registration (Testing)
diff --git a/core/api-bank-integration.rst b/core/api-bank-integration.rst
index 298e3d7..5f33f45 100644
--- a/core/api-bank-integration.rst
+++ b/core/api-bank-integration.rst
@@ -87,6 +87,9 @@ for the withdrawal operation (the ``wopid``) to interact with the withdrawal ope
.. ts:def:: BankWithdrawalOperationStatus
export class BankWithdrawalOperationStatus {
+ // Indicates whether the withdrawal was aborted.
+ aborted: boolean;
+
// Has the wallet selected parameters for the withdrawal operation
// (exchange and reserve public key) and successfully sent it
// to the bank?
@@ -140,7 +143,7 @@ for the withdrawal operation (the ``wopid``) to interact with the withdrawal ope
// format. NOTE: this field is optional, therefore
// the bank will initiate the withdrawal with the
// default exchange, if not given.
- exchange_wire_details: string;
+ selected_exchange: string;
}
.. ts:def:: BankWithdrawalOperationPostResponse
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
index b93e07a..49f29da 100644
--- a/core/api-exchange.rst
+++ b/core/api-exchange.rst
@@ -1,6 +1,6 @@
..
This file is part of GNU TALER.
- Copyright (C) 2014-2020 Taler Systems SA
+ Copyright (C) 2014-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -15,9 +15,9 @@
@author Christian Grothoff
-=============================
-The Exchange RESTful JSON API
-=============================
+========================
+The Exchange RESTful API
+========================
The API specified here follows the :ref:`general conventions <http-common>`
for all details not specified in the individual requests.
@@ -131,6 +131,12 @@ possibly by using HTTPS.
// not signed (!), can change without notice.
reserve_closing_delay: RelativeTime;
+ // Maximum amount that a wallet is allowed to hold without
+ // having to undergo the KYC process of the issuing
+ // exchange. Optional option, if not given there is no limit.
+ // Currency must match ``currency``.
+ wallet_balance_limit_without_kyc?: Amount;
+
// Denominations offered by this exchange.
denoms: Denom[];
@@ -172,6 +178,10 @@ possibly by using HTTPS.
.. ts:def:: P2PFees
+ .. note::
+
+ This is a draft API that is not yet implemented.
+
interface P2PFees {
// What date (inclusive) does these fees go into effect?
@@ -926,7 +936,11 @@ Management operations authorized by master key
.. http:post:: /management/p2pfees
- Provides fee configuration for purses.
+ Provides fee configuration for purses.
+
+ .. note::
+
+ This is a draft API that is not yet implemented.
**Request:**
@@ -944,7 +958,11 @@ Management operations authorized by master key
.. http:post:: /management/partners
- Enables a partner exchange for wad transfers.
+ Enables a partner exchange for wad transfers.
+
+ .. note::
+
+ This is a draft API that is not yet implemented.
**Request:**
@@ -991,7 +1009,7 @@ This part of the API is for the use by auditors interacting with the exchange.
The auditor signature is invalid.
:http:statuscode:`404 Not found`:
The denomination key for which the auditor is providing a signature is unknown.
- The response will be a `DenominationUnkownMessage`.
+ The response will be a `DenominationUnknownMessage`.
:http:statuscode:`410 Gone`:
This auditor is no longer supported by the exchange.
:http:statuscode:`412 Precondition failed`:
@@ -1050,10 +1068,10 @@ exchange.
.. note::
- Eventually the exchange will need to advertise a policy for how long it will
+ Eventually the exchange will need to advertize a policy for how long it will
keep transaction histories for inactive or even fully drained reserves. We
will therefore need some additional handler similar to ``/keys`` to
- advertise those terms of service.
+ advertize those terms of service.
.. http:post:: /reserves/$RESERVE_PUB/status
@@ -1375,21 +1393,31 @@ exchange.
transaction or before the client can commit the coin signature to disk, the
coin is not lost.
:http:statuscode:`202 Accepted`:
- This reserve has received funds from a purse and must
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
be upgraded to an account (with KYC) before the withdraw can
complete. Note that this response does NOT affirm that the
withdraw will ultimately complete with the requested amount.
The user should be redirected to the provided location to perform
the required KYC checks to open the account before withdrawing.
Afterwards, the request should be repeated.
- The response will be an `AccountKycRedirect` object.
+ The response will be an `KycNeededRedirect` object.
+
+ Implementation note: internally, we need to
+ distinguish between upgrading the reserve to an
+ account (due to P2P payment) and identifying the
+ owner of the origin bank account (due to exceeding
+ the withdraw amount threshold), as we need to create
+ a different payto://-URI for the KYC check depending
+ on the case.
+
:http:statuscode:`403 Forbidden`:
The signature is invalid.
:http:statuscode:`404 Not found`:
The denomination key or the reserve are not known to the exchange. If the
denomination key is unknown, this suggests a bug in the wallet as the
wallet should have used current denomination keys from ``/keys``.
- In this case, the response will be a `DenominationUnkownMessage`.
+ In this case, the response will be a `DenominationUnknownMessage`.
If the reserve is unknown, the wallet should not report a hard error yet, but
instead simply wait for up to a day, as the wire transaction might simply
not yet have completed and might be known to the exchange in the near future.
@@ -1434,7 +1462,7 @@ exchange.
// What kind of operation was requested that now
// failed?
- oper: String;
+ oper: string;
}
@@ -1465,6 +1493,16 @@ exchange.
}
+ .. ts:def:: KycNeededRedirect
+
+ interface KycNeededRedirect {
+ // Payment target that the merchant should
+ // use to check for its KYC status using
+ // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
+ payment_target_uuid: Integer;
+
+ }
+
.. ts:def:: WithdrawError
interface WithdrawError {
@@ -1580,7 +1618,7 @@ denomination.
Either the denomination key is not recognized (expired or invalid),
or the wire type is not recognized.
If the denomination key is unknown, the response will be
- a `DenominationUnkownMessage`.
+ a `DenominationUnknownMessage`.
:http:statuscode:`409 Conflict`:
The deposit operation has either failed because the coin has insufficient
residual value, or because the same public key of the coin has been
@@ -1640,8 +1678,8 @@ denomination.
merchant_pub: EddsaPublicKey;
// Date until which the merchant can issue a refund to the customer via the
- // exchange, possibly zero if refunds are not allowed.
- refund_deadline: Timestamp;
+ // exchange, to be omitted if refunds are not allowed.
+ refund_deadline?: Timestamp;
// Signature over `TALER_DepositRequestPS`, made by the customer with the
// `coin's private key <coin-priv>`.
@@ -1665,6 +1703,15 @@ denomination.
// URL, or if the base URL has changed since the deposit.
transaction_base_url?: string;
+ // Payment target that the merchant should
+ // use to check for its KYC status using
+ // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
+ payment_target_uuid: Integer;
+
+ // True if the KYC check for the merchant has been
+ // satisfied.
+ kyc_ok: boolean;
+
// Timestamp when the deposit was received by the exchange.
exchange_timestamp: Timestamp;
@@ -1730,7 +1777,7 @@ denomination.
// Date until which the merchant can issue a refund to the customer via the
// exchange, possibly zero if refunds are not allowed.
- refund_deadline: Timestamp;
+ refund_deadline?: Timestamp;
// Signature over `TALER_DepositRequestPS`, made by the customer with the
// `coin's private key <coin-priv>`.
@@ -1975,7 +2022,7 @@ denomination.
purse_pub: EddsaPublicKey;
// Signature by the exchange over a
- // `TALER_CoinPurseRefundConfirmationPS`
+ // ``TALER_CoinPurseRefundConfirmationPS``
// of purpose ``TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND``.
exchange_sig: EddsaSignature;
@@ -2022,7 +2069,7 @@ the API during normal operation.
The exchange does not recognize the denomination key as belonging to the exchange,
or it has expired.
If the denomination key is unknown, the response will be
- a `DenominationUnkownMessage`.
+ a `DenominationUnknownMessage`.
:http:statuscode:`409 Conflict`:
The operation is not allowed as the coin has insufficient
residual value, or because the same public key of the coin has been
@@ -2315,14 +2362,14 @@ in using this API.
The denomination key is unknown, or the blinded
coin is not known to have been withdrawn.
If the denomination key is unknown, the response will be
- a `DenominationUnkownMessage`.
+ a `DenominationUnknownMessage`.
:http:statuscode:`409 Conflict`:
The operation is not allowed as the coin has insufficient
residual value, or because the same public key of the coin has been
previously used with a different denomination. Which case it is
can be decided by looking at the error code
(``TALER_EC_EXCHANGE_RECOUP_COIN_BALANCE_ZERO`` or
- ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
The response is a `DepositDoubleSpendError`.
:http:statuscode:`410 Gone`:
The requested denomination key is not yet or no longer valid.
@@ -2490,7 +2537,7 @@ typically also view the balance.)
**Request:**
- :query merchant_sig: EdDSA signature of the merchant made with purpose ``TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION`` over a `TALER_DepositTrackPS`, affirming that it is really the merchant who requires obtaining the wire transfer identifier.
+ :query merchant_sig: EdDSA signature of the merchant made with purpose ``TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION`` over a ``TALER_DepositTrackPS``, affirming that it is really the merchant who requires obtaining the wire transfer identifier.
**Response:**
@@ -2512,6 +2559,7 @@ typically also view the balance.)
.. ts:def:: TrackTransactionResponse
interface TrackTransactionResponse {
+
// Raw wire transfer identifier of the deposit.
wtid: Base32;
@@ -2539,7 +2587,18 @@ typically also view the balance.)
.. ts:def:: TrackTransactionAcceptedResponse
interface TrackTransactionAcceptedResponse {
+
+ // Payment target that the merchant should
+ // use to check for its KYC status using
+ // the ``/kyc-check/$PAYMENT_TARGET_UUID`` endpoint.
+ payment_target_uuid: Integer;
+
+ // True if the KYC check for the merchant has been
+ // satisfied.
+ kyc_ok: boolean;
+
// Time by which the exchange currently thinks the deposit will be executed.
+ // Actual execution may be later if the KYC check is not satisfied by then.
execution_time: Timestamp;
}
@@ -2649,6 +2708,11 @@ Refunds
Wallet-to-wallet transfers
--------------------------
+ .. note::
+
+ This is a draft API that is not yet implemented.
+
+
.. http:GET:: /purses/$PURSE_PUB
Obtain information about a purse. The request header must
@@ -2773,7 +2837,7 @@ Wallet-to-wallet transfers
previously used with a different denomination. Which case it is
can be decided by looking at the error code
(``TALER_EC_EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS`` or
- ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
The fields of the response are the same in both cases.
The request should not be repeated again with this coin.
In this case, the response is a `PurseDepositDoubleSpendError`.
@@ -3147,96 +3211,6 @@ Wallet-to-wallet transfers
}
-.. http:POST:: /reserves/$RESERVE_PUB/kyc
-
- Upgrade a reserve to an *account*. The reserve must
- (from wire transfers or merges of purses) already have a
- sufficient balance to cover the KYC fee. The signature
- affirms that the KYC fee can and should be charged to the reserve.
- The request always updates the payto URI associated with
- the reserve, even if the KYC process fails or is not completed.
-
- **Request:** The request body must be a `AccountSetupRequest` object.
-
- :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
- wait up to ``timeout_ms`` milliseconds for the KYC gateway to
- confirm completion of the KYC process.
-
- **Response:**
-
- :http:statuscode:`200 Ok`:
- The operation succeeded, the exchange confirms that the account
- can now be used.
- The response will be an `AccountKycStatus` object.
- :http:statuscode:`202 Accepted`:
- The user should be redirected to the provided location to perform
- the required KYC checks to open the account. Afterwards, the
- request should be repeated.
- The response will be an `AccountKycRedirect` object.
- :http:statuscode:`504 Gateway Timeout`:
- The exchange did not receive a confirmation from the KYC service
- within the specified time period. Used when long-polling for the
- result.
-
- **Details:**
-
- .. ts:def:: AccountSetupRequest
-
- interface AccountSetupRequest {
-
- // Time of the request to perform the KYC. Determines
- // the KYC fee charged by the exchange. Must be
- // reasonably close to the current time of the exchange.
- kyc_timestamp: Timestamp;
-
- // Bank account to be associated with the account.
- // Can be 'payto://void/' to not associate the
- // account with any bank account. In this case,
- // closing the account will result in the balance
- // being forfeit. If the provided wire method is
- // not supported by the exchange *and* not 'void',
- // this is a ``Bad Request`` (HTTP status 400).
- payto_uri: string;
-
- // EdDSA signature of the reserve affirming the request
- // to create the account, must be of purpose
- // ``TALER_SIGNATURE_ACCOUNT_SETUP_REQUEST``
- // and over `TALER_AccountSetupRequestSignaturePS`.
- reserve_sig: EddsaPublicKey;
-
- }
-
- .. ts:def:: AccountKycStatus
-
- interface AccountKycStatus {
-
- // Current time of the exchange, used as part of
- // what the exchange signs over.
- now: Timestamp;
-
- // EdDSA signature of the exchange affirming the account
- // is KYC'ed, must be of purpose
- // ``TALER_SIGNATURE_ACCOUNT_SETUP_SUCCESS``
- // and over `TALER_AccountSetupRequestSignaturePS`.
- exchange_sig: EddsaSignature;
-
- // public key used to create the signature.
- exchange_pub: EddsaPublicKey;
- }
-
- .. ts:def:: AccountKycRedirect
-
- interface AccountKycRedirect {
-
- // URL that the user should open in a browser to
- // proceed with the KYC process.
- kyc_url: string;
-
- }
-
-
-
-
.. _exchange_wads:
@@ -3244,6 +3218,11 @@ Wallet-to-wallet transfers
Wads
^^^^
+ .. note::
+
+ This is a draft API that is not yet implemented.
+
+
These endpoints are used to manage exchange-to-exchange payments in support of
wallet-to-wallet payments. Only another exchange should access this endpoint.
@@ -3330,3 +3309,161 @@ wallet-to-wallet payments. Only another exchange should access this endpoint.
// Wad fees that was charged to the purse.
wad_fees: Amount;
}
+
+
+------------------
+KYC status updates
+------------------
+
+ .. note::
+
+ This is a draft API that is not yet implemented.
+
+
+.. http:POST:: /kyc-wallet
+
+ Setup KYC identification for a wallet. Returns the KYC UUID.
+
+ **Request:**
+
+ The request body must be a `WalletKycRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ A KYC ID was created.
+ The response will be a `WalletKycUuid` object.
+ :http:statuscode:`204 No Content`:
+ KYC is disabled at this exchange.
+ :http:statuscode:`401 Unauthorized`:
+ The provided signature is invalid.
+
+ **Details:**
+
+ .. ts:def:: WalletKycRequest
+
+ interface WalletKycRequest {
+
+ // 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;
+ }
+
+ .. ts:def:: WalletKycUuid
+
+ interface WalletKycUuid {
+
+ // UUID that the wallet should use when initiating
+ // the KYC check.
+ payment_target_uuid: number;
+
+ }
+
+
+.. http:GET:: /kyc-check/$PAYMENT_TARGET_UUID
+
+ Check or update KYC status of a particular payment target.
+ Returns the current KYC status of the account and, if
+ negative, returns the URL where the KYC process can be
+ initiated using OAuth.
+
+ **Request:**
+
+ :query h_payto=HASH: Specifies the hash of the payto:// URI of the payment
+ target. Weak security check used to authorize the status request.
+ :query timeout_ms=NUMBER: *Optional.* If specified, the exchange will
+ wait up to ``timeout_ms`` milliseconds if the payment target
+ is currently not legitimized. Ignored if the payment target
+ is already legitimized. Note that the legitimization would be
+ triggered by another request to the same endpoint with a valid
+ ``token``.
+
+ **Response:**
+
+ :http:statuscode:`200 Ok`:
+ The KYC operation succeeded, the exchange confirms that the
+ payment target is now authorized to transact.
+ The response will be an `AccountKycStatus` object.
+ :http:statuscode:`202 Accepted`:
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account. Afterwards, the
+ ``/kyc/`` request should be repeated.
+ The response will be an `AccountKycRedirect` object.
+ :http:statuscode:`204 No content`:
+ The exchange is not configured to perform KYC and thus
+ generally all accounts are simply considered legitimate.
+ :http:statuscode:`401 Unauthorized`:
+ The provided hash does not match the payment target.
+ :http:statuscode:`404 Not found`:
+ The payment target is unknown.
+
+ **Details:**
+
+ .. ts:def:: AccountKycStatus
+
+ interface AccountKycStatus {
+
+ // Current time of the exchange, used as part of
+ // what the exchange signs over.
+ now: Timestamp;
+
+ // EdDSA signature of the exchange affirming the account
+ // is KYC'ed, must be of purpose
+ // ``TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS``
+ // and over `TALER_AccountSetupStatusSignaturePS`.
+ exchange_sig: EddsaSignature;
+
+ // public key used to create the signature.
+ exchange_pub: EddsaPublicKey;
+ }
+
+ .. ts:def:: AccountKycRedirect
+
+ interface AccountKycRedirect {
+
+ // URL that the user should open in a browser to
+ // proceed with the KYC process.
+ kyc_url: string;
+
+ }
+
+
+.. http:GET:: /kyc-proof/$PAYMENT_TARGET_UUID
+
+ Update KYC status of a particular payment target. Provides
+ the token to the exchange that allows it to verify that the
+ user has completed the KYC process. The HTTP method must be
+ a GET due to requirements from the OAuth 2.0 protocol.
+
+ **Request:**
+
+ :query code=CODE: OAuth 2.0 code argument.
+ :query state=CODE: OAuth 2.0 state argument.
+
+ ..note::
+
+ Depending on the OAuth variant used, additional
+ query parameters may need to be passed here.
+
+ **Response:**
+
+ :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 OAuth 2.0 authentication 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 OAuth-based
+ legitimization service.
+ :http:statuscode:`504 Gateway Timeout`:
+ The exchange did not receive a reply from the OAuth legitimization
+ service within a reasonable time period.
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
index 8a39278..519a432 100644
--- a/core/api-merchant.rst
+++ b/core/api-merchant.rst
@@ -48,9 +48,9 @@ this base URL are divided into to categories:
* Private endpoints (under ``/private/*``) that are only supposed to be exposed
to the merchant internally, and must not be exposed on the
Internet.
-
-To manage instances, private endpoints only available to the ``default``
-instance must be used.
+* Management endpoints (under ``/management/*``) are also private and dedicated
+ to CRUD operation over instances and reset authentication settings over all
+ instances. Only accessible with the default instance authentication token
Examples:
@@ -62,14 +62,14 @@ Examples:
A private endpoint (default instance):
https://merchant-backend.example.com/private/orders
- A public endpoint (default instance):
- https://merchant-backend.example.com/orders
+ A public endpoint (default instance and order id "ABCD"):
+ https://merchant-backend.example.com/orders/ABCD
A private endpoint ("myinst" instance):
https://merchant-backend.example.com/instances/myinst/private/orders
- A public endpoint ("myinst" instance):
- https://merchant-backend.example.com/instances/myinst/orders
+ A public endpoint ("myinst" instance and order id "ABCD"):
+ https://merchant-backend.example.com/instances/myinst/orders/ABCD
A private endpoint (explicit "default" instance):
https://merchant-backend.example.com/instances/default/private/orders
@@ -78,8 +78,8 @@ Examples:
https://merchant-backend.example.com/instances/default/orders
Endpoints to manage other instances (ONLY for implicit "default" instance):
- https://merchant-backend.example.com/private/instances
- https://merchant-backend.example.com/private/instances/$ID
+ https://merchant-backend.example.com/management/instances
+ https://merchant-backend.example.com/management/instances/$ID
Endpoints to manage own instance:
https://merchant-backend.example.com/private
@@ -855,7 +855,7 @@ instance setup before it can be used to manage inventory or process payments.
Setting up instances
--------------------
-.. http:post:: [/instances/$INSTANCE]/private/instances
+.. http:post:: /management/instances
This request will be used to create a new merchant instance in the backend.
It is only available for the implicit ``default`` instance.
@@ -929,7 +929,7 @@ Setting up instances
}
-.. http:post:: /private/instances/$INSTANCE/auth
+.. http:post:: /management/instances/$INSTANCE/auth
.. http:post:: [/instances/$INSTANCE]/private/auth
Update the authentication settings for an instance. POST operations against
@@ -965,7 +965,7 @@ Setting up instances
}
-.. http:patch:: /private/instances/$INSTANCE
+.. http:patch:: /management/instances/$INSTANCE
.. http:patch:: [/instances/$INSTANCE]/private
Update the configuration of a merchant instance. PATCH operations against
@@ -1034,7 +1034,7 @@ Inspecting instances
--------------------
.. _instances:
-.. http:get:: /private/instances
+.. http:get:: /management/instances
This is used to return the list of all the merchant instances.
It is only available for the implicit ``default`` instance.
@@ -1079,7 +1079,7 @@ Inspecting instances
}
-.. http:get:: /private/instances/$INSTANCE
+.. http:get:: /management/instances/$INSTANCE
.. http:get:: [/instances/$INSTANCE]/private
This is used to query a specific merchant instance. GET operations against
@@ -1165,7 +1165,7 @@ Inspecting instances
Deleting instances
------------------
-.. http:delete:: /private/instances/$INSTANCE
+.. http:delete:: /management/instances/$INSTANCE
.. http:delete:: [/instances/$INSTANCE]/private
This request will be used to delete (permanently disable)
@@ -1199,6 +1199,97 @@ Deleting instances
The latter case only applies if ``purge`` was set.
+KYC status checks
+-----------------
+
+ .. note::
+
+ This is a draft API that is not yet implemented.
+
+
+.. http:GET:: /management/instances/$INSTANCE/kyc
+.. http:GET:: /instances/$INSTANCE/private/kyc
+
+ Check KYC status of a particular payment target.
+ Prompts the exchange to inquire with the bank
+ as to the KYC status of the respective account
+ and returns the result.
+
+ **Request:**
+
+ :query h_wire=H_WIRE: *Optional*. If specified, the KYC check should return the KYC status only for this wire account. Otherwise, for all wire accounts.
+ :query exchange_url=URL: *Optional*. If specified, the KYC check should return the KYC status only for the given exchange. Otherwise, for all exchanges we interacted with.
+ :query timeout_ms=NUMBER: *Optional.* If specified, the merchant will
+ wait up to ``timeout_ms`` milliseconds for the exchanges to confirm completion of the KYC process(es).
+
+ **Response:**
+
+ If different exchanges cause different errors when processing
+ the request, the largest HTTP status code that is applicable
+ is returned.
+
+ :http:statuscode:`202 Accepted`:
+ The user should be redirected to the provided locations to perform
+ the required KYC checks to open an account. Afterwards, the
+ request should be repeated.
+ The response will be an `AccountKycRedirects` object.
+ :http:statuscode:`204 No content`:
+ All KYC operations queried have succeeded.
+ :http:statuscode:`502 Bad gateway`:
+ We failed to obtain a response from an exchange (about the KYC status).
+ The response will be an `AccountKycRedirects` object.
+ :http:statuscode:`504 Gateway Timeout`:
+ The merchant did not receive a confirmation from an exchange
+ within the specified time period. Used when long-polling for the
+ result.
+ The response will be an `AccountKycRedirects` object.
+
+ .. ts:def:: AccountKycRedirects
+
+ interface AccountKycRedirects {
+
+ // Array of pending KYCs.
+ pending_kycs: MerchantAccountKycRedirect[];
+
+ // Array of exchanges with no reply.
+ timeout_kycs: ExchangeKycTimeout[];
+ }
+
+ .. ts:def:: MerchantAccountKycRedirect
+
+ interface MerchantAccountKycRedirect {
+
+ // URL that the user should open in a browser to
+ // proceed with the KYC process (as returned
+ // by the exchange's ``/kyc-check/`` endpoint).
+ kyc_url: string;
+
+ // Base URL of the exchange this is about.
+ exchange_url: string;
+
+ // Our bank wire account this is about.
+ payto_uri: string;
+
+ }
+
+ .. ts:def:: ExchangeKycTimeout
+
+ interface ExchangeKycTimeout {
+
+ // Base URL of the exchange this is about.
+ exchange_url: string;
+
+ // Numeric `error code <error-codes>` indicating errors the exchange
+ // returned, or TALER_EC_INVALID for none.
+ exchange_code: number;
+
+ // HTTP status code returned by the exchange when we asked for
+ // information about the KYC status.
+ // 0 if there was no response at all.
+ exchange_http_status: number;
+
+ }
+
--------------------
Inventory management
--------------------
@@ -1478,7 +1569,7 @@ Reserving inventory
// UUID that identifies the frontend performing the lock
// Must be unique for the lifetime of the lock.
- lock_uuid: String;
+ lock_uuid: string;
// How long does the frontend intend to hold the lock?
duration: RelativeTime;
@@ -1602,7 +1693,7 @@ Creating orders
// be used in case different UUIDs were used for different
// products (i.e. in case the user started with multiple
// shopping sessions that were combined during checkout).
- lock_uuids: String[];
+ lock_uuids: string[];
// Should a token for claiming the order be generated?
// False can make sense if the ORDER_ID is sufficiently
@@ -1813,12 +1904,12 @@ Inspecting orders
// encountered tracking the wire transfer for this purchase (before
// we even got to specific coin issues).
// 0 if there were no issues.
- exchange_ec: number;
+ exchange_code: number;
// HTTP status code returned by the exchange when we asked for
// information to track the wire transfer for this purchase.
// 0 if there were no issues.
- exchange_hc: number;
+ exchange_http_status: number;
// Total amount that was refunded, 0 if refunded is false.
refund_amount: Amount;
@@ -1932,10 +2023,10 @@ Inspecting orders
hint: string;
// Numerical `error code <error-codes>` from the exchange.
- exchange_ec: number;
+ exchange_code: number;
// HTTP status code received from the exchange.
- exchange_hc: number;
+ exchange_http_status: number;
// Public key of the coin for which we got the exchange error.
coin_pub: CoinPublicKey;
@@ -2420,7 +2511,7 @@ Backend: Giving tips
Tips are a way for websites to give small amounts of e-cash to visitors (for
example as a financial reward for providing information or watching
-advertisements). Tips are non-contractual: neither merchant nor consumer
+advertizements). Tips are non-contractual: neither merchant nor consumer
have any contractual information about the other party as a result of the
tip.
diff --git a/core/api-sync.rst b/core/api-sync.rst
index a6d6572..759a4c3 100644
--- a/core/api-sync.rst
+++ b/core/api-sync.rst
@@ -1,6 +1,6 @@
..
This file is part of GNU TALER.
- Copyright (C) 2018, 2019 Taler Systems SA
+ Copyright (C) 2018-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
@@ -107,7 +107,7 @@ keys.
Receiving Terms of Service
--------------------------
-.. http:get:: /terms
+.. http:get:: /config
Obtain the terms of service provided by the storage service.
@@ -118,14 +118,21 @@ Receiving Terms of Service
.. ts:def:: SyncTermsOfServiceResponse
interface SyncTermsOfServiceResponse {
+ // Name of the service
+ name: "sync";
+
// Maximum backup size supported.
storage_limit_in_megabytes: number;
// Fee for an account, per year.
annual_fee: Amount;
- // Protocol version supported by the server,
- // for now always "0.0".
+ // Maximum liability of the provider in case of data loss.
+ liability_limit: Amount;
+
+ // libtool-style representation of the Sync protocol version, see
+ // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
+ // The format is "current:revision:age".
version: string;
}
diff --git a/core/api-wire.rst b/core/api-wire.rst
index c471cbf..63373f6 100644
--- a/core/api-wire.rst
+++ b/core/api-wire.rst
@@ -1,6 +1,6 @@
..
This file is part of GNU TALER.
- Copyright (C) 2019-2020 Taler Systems SA
+ Copyright (C) 2019-2021 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License as published by the Free Software
diff --git a/core/taler-uri.rst b/core/taler-uri.rst
index a4942b8..dd45611 100644
--- a/core/taler-uri.rst
+++ b/core/taler-uri.rst
@@ -33,7 +33,7 @@ Payments are requested with the ``pay`` action. The parameters are a hierarchic
* ``order_id`` is the order ID that the customer is asked to pay for.
* ``session_id`` is the optional session ID under which the payment takes place.
* ``c`` is the optional high-entropy order `ClaimToken`.
-* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for internet connectivity.
+* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for Internet connectivity.
Examples:
@@ -72,7 +72,7 @@ The action ``withdraw`` is used to trigger a bank-integrated withdrawal operatio
* ``bank_prefix_path`` is an optional list of path components that identifies the path prefix of the bank integration API base URL.
* ``withdrawal_uid`` is the unique ID of the withdrawal operation.
* ``ssid`` is the optional WLAN SSID that the bank (typically in an ATM scenario) can offer the wallet
- to connect to for internet connectivity.
+ to connect to for Internet connectivity.
--------------------------
Withdrawing (Confirmation)
@@ -104,7 +104,7 @@ A ``taler://refund`` URI instructs the wallet to download and apply available re
* ``merchant_host`` is the hostname of the merchant.
* ``merchant_prefix_path`` is an optional list of path components that identifies the path prefix of the merchant base URL.
* ``order_id`` is the order ID to check for refunds.
-* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for internet connectivity.
+* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for Internet connectivity.
-------
@@ -123,7 +123,7 @@ a merchant and ask the user to accept/decline it.
* ``tip_id`` uniquely identifies the tip.
* ``insecure`` is an optional query parameter. When "1", the ``merchant_host`` is contacted via HTTP.
When absent or "0", the ``merchant_host`` is contacted via HTTPS.
-* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for internet connectivity.
+* ``ssid`` is the optional WLAN SSID that the merchant can offer the wallet to connect to for Internet connectivity.
----------------
diff --git a/design-documents/006-extensions.rst b/design-documents/006-extensions.rst
new file mode 100644
index 0000000..d701c6d
--- /dev/null
+++ b/design-documents/006-extensions.rst
@@ -0,0 +1,148 @@
+Design Doc 006: Extensions for GNU Taler
+#############################################
+
+Summary
+=======
+
+This design document describes a generic framework for how extensions (i.e.
+optional features) to GNU Taler can be offered and used by the exchange,
+merchants and wallets.
+
+Motivation
+==========
+
+GNU Taler's list of supported features evolves over time. For example, the
+following features are going to be designed and implemented during the course
+of 2021 and 2022:
+
+* Peer-to-peer payments
+* Anonymous age-restriction
+* Escrow service for anonymous auctions
+
+We call a feature an *extension* when it is *optional* for either the
+exchange, wallet or merchant to enable and support it. (However, enabling
+a feature might *require* the other parties to support the feature, too)
+
+For optional features we therefore need a mechanism to express the
+availability, version and configuration of a particular feature, f.e. p2p or
+age-restriction offered by an exchange, and make it verifiable by the other
+participants.
+
+Requirements
+============
+
+TODO. Not sure if we have any requirements - other than particular
+ideas/designs for extensions?
+
+
+Proposed Solution
+=================
+
+Exchange
+^^^^^^^^
+
+The exchange will add two new REQUIRED fields in response to ``/keys``:
+
+#. The (but maybe empty) field ``extensions`` which contains a dictionary of
+ extension-names and their configuration, see below.
+
+#. The field ``extensions_sig`` that contains the EdDSA signature of the SHA256-hash
+ of the normalized JSON-string of the ``extenstions`` object.
+
+
+The necessary changes to ``ExchangeKeysResponse`` are highlighted here:
+
+.. ts:def:: ExchangeKeysResponse
+
+ interface ExchangeKeysResponse {
+ //...
+
+ // Required (but maybe emtpy) field with a dictionary of (name, object)
+ // pairs defining the supported extensions.
+ // The name MUST be unique and SHOULD include version information in Taler's
+ // protocol version ranges notation as suffix, starting with letter 'v',
+ // f.e.: "age_restriction.v1" or "p2p.v1:2:3".
+ extensions: { name: Extension };
+
+ // Signature by the exchange master key of the SHA-512 hash of the
+ // normalized JSON-object of field ``extenstions``.
+ // The signature MUST have purpose ``TALER_SIGNATURE_MASTER_EXTENSIONS``.
+ extensions_sig: EddsaSignature;
+
+ //...
+ }
+
+
+Extension names
+---------------
+
+The names of extensions MUST be unique and SHOULD include a version information
+in Taler's `protocol version ranges notation`_ as suffix starting with letter
+'``v``', f.e.: ``age_restriction.v1`` or ``p2p.v1:2:3``.
+
+.. _protocol version ranges notation: https://docs.taler.net/core/api-common.html#protocol-version-ranges
+
+The full name MUST be registered with GANA_ along with a full description of
+the extension. (TODO: be more specific)
+
+.. _GANA: https://git.gnunet.org/gana.git
+
+
+Extension object
+----------------
+
+The definition of ``Extension`` object itself is mostly up to the particular
+feature. **However**, it MUST contain 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 extension before proceeding, if "false" clients
+can safely skip extensions they do not understand.
+
+.. _X.509: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2
+
+
+.. ts:def:: Extension
+
+ interface Extension {
+ // Same semantics as "critical" for extensions in X.509, see
+ // https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.
+ // If "true", the client must "understand" the extension before proceeding.
+ // If "false", clients can safely skip extensions they do not understand.
+ critical: boolean;
+
+ // Additional fields defined by the feature itself
+ ...
+
+ }
+
+
+**TODO**:
+
+* Add examples for age-restriction and p2p.
+
+Merchant
+^^^^^^^^
+
+TODO:
+
+* Needs to express support for particular extensions, too. F.e. age-restriction.
+
+Alternatives
+============
+
+TODO. None yet.
+
+
+Drawbacks
+=========
+
+* We do not offer (yet) any lifetime cycle of a feature, that is: There are
+ only two states that a feature can be in: "available" or "not-available".
+
+* The existing design for peer-to-peer payments must be adapted to this.
+
+Discussion / Q&A
+================
+
+The initial ideas presented here are based on discussions between Özgür Kesim
+and Christian Grothoff.
+
diff --git a/design-documents/015-merchant-backoffice-routing.rst b/design-documents/015-merchant-backoffice-routing.rst
index 07aee23..f6372dc 100644
--- a/design-documents/015-merchant-backoffice-routing.rst
+++ b/design-documents/015-merchant-backoffice-routing.rst
@@ -55,7 +55,7 @@ There are 2 type of routing: instance and internal
doest not need to care about matching all applications URL
Knowing that the $BACKEND_URL points to a correct merchant backend the SPA will
-check for ``$BACKEND_URL/private/instances``:
+check for ``$BACKEND_URL/management/instances``:
* if Unauthorized ask for credentials
diff --git a/design-documents/016-backoffice-order-management.rst b/design-documents/016-backoffice-order-management.rst
index 583661c..00250cd 100644
--- a/design-documents/016-backoffice-order-management.rst
+++ b/design-documents/016-backoffice-order-management.rst
@@ -38,7 +38,7 @@ Listing orders
4 tabs will be show for a easy access to common filter, click on any of this and
search will reset all filter except date
-* paid (default)
+* paid (default)
* refunded
* not wired
* all (empty filter box)
@@ -78,7 +78,7 @@ this form is divided into 4 sections
* ``payment``: where some default of the payment processing can be changed
* ``extra``: where the merchant can add extra information in JSON format
-
+
Create order: Product section
.............................
@@ -95,7 +95,7 @@ The first part will add/remove product from the current stock.
The second part will add non inventory product. To add a product a :ref:`create
product <backoffice-create-product>` form will be shown. The product in the list
-can be edited or deleted from the list.
+can be edited or deleted from the list.
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
@@ -107,7 +107,7 @@ list a line with a resume of the total price and units will be shown.
Create order: Price section
...........................
-This section has 2 scenarios.
+This section has 2 scenarios.
The fist one is without products being added: the ``order price`` and
``summary`` inputs will be shown.
@@ -115,7 +115,7 @@ The fist one is without products being added: the ``order price`` and
If there is at least one product added, the ``total products price`` as the sum
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``.
+since ``order price`` cannot be less that ``product taxes``.
.. image:: ../backoffice-order-create.price-section.svg
:width: 800
@@ -126,7 +126,7 @@ Create order: Payment section
This section show optional values that can be overwritten by the merchant
* ``refund deadline``: calendar type of input. default from instance
-
+
* ``pay deadline``: calendar type of input. default from instance
* ``auto refund deadline``: calendar type of input. default empty, optional.
@@ -155,12 +155,12 @@ An example of how all section in a page will be shown.
Creation order success
-**********************
+......................
A success message showing the amount, summary and the order id. Additionally the
-taler_pay_uri can be shown to be copied to send to the customer.
+taler_pay_uri can be shown to be copied to send to the customer.
-action buttons that allow the following:
+action buttons that allow the following:
* create another payment: go to the create payment page again
* view details: show details of the payment (see page)
@@ -177,11 +177,11 @@ indicated:
* refunded: red
Header
-^^^^^^
+......
This is a resume of most important information
-* big status with color
+* big status with color
* date
* total
@@ -194,9 +194,9 @@ This is a resume of most important information
* actions: refund (if not refunded), add note, copy order_status_url
Timeline of events
-^^^^^^^^^^^^^^^^^^
+..................
-Event of status changed over time describe vertically.
+Event of status changed over time describe vertically.
Sorted from newest to oldest.
On line per status updated, with datetime and a short description.
@@ -216,7 +216,7 @@ Info taken from:
Error status
-^^^^^^^^^^^^
+............
This section is not going to be shown if there is no error
@@ -227,7 +227,7 @@ This section is not going to be shown if there is no error
and exchange_hc
Payment details
-^^^^^^^^^^^^^^^
+...............
If the order was claimed
@@ -238,9 +238,9 @@ If the order was claimed
* net (deposit_total - refund_amount)
* current status
-
+
Contract Terms
-^^^^^^^^^^^^^^
+..............
collapsed as default. show disabled if unpaid
@@ -271,7 +271,7 @@ collapsed as default. show disabled if unpaid
refund popup
-------------
+............
If there is any refund:
@@ -284,7 +284,7 @@ Warn if there is a pending refund when ``refund_pending`` is true
Ask for:
* amount: default 0, show max amount refundable (order amount - already
- refunded)
+ refunded)
* reason: concatenation of the next values
@@ -320,7 +320,7 @@ Alternatives
pagination
------------
+----------
order list was originally thought with pagination footer
.. image:: ../backoffice-order-list.pagination.svg
@@ -363,11 +363,11 @@ Discussion / Q&A
Is there any other case? Is this taking into account auto_refund?
* Field left out in the order creation:
-
+
* contractTerm.summary_i18n: it makes the UI complex
* contractTerm.order_id: should be created by the backend
* contractTerm.timestamp: defined by backend
* contractTerm.merchant_pub: filled by the backend
* contractTerm.merchant_base_url: filled by the backend
* contractTerm.h_wire: defined by the backend
- * contractTerm.nonce: not used \ No newline at end of file
+ * contractTerm.nonce: not used
diff --git a/design-documents/018-contract-json.rst b/design-documents/018-contract-json.rst
index 2162d6e..d7f7825 100644
--- a/design-documents/018-contract-json.rst
+++ b/design-documents/018-contract-json.rst
@@ -63,7 +63,7 @@ parent object.
.. code-block:: json
{
- "delivery_address": ...,
+ "delivery_address": "...",
"$forgettable": {
"delivery_address": "<salt>"
},
@@ -96,7 +96,7 @@ member of the parent object.
{
...props,
- "delivery_address": ...,
+ "delivery_address": "...",
"$forgettable": {
"delivery_address": "<memb_salt>"
},
@@ -174,7 +174,7 @@ a minimal interoperability test:
Hashing the above contract results in the following Crockford base32 encoded
hash
-``287VXK8T6PXKD05W8Y94QJNEFCMRXBC9S7KNKTWGH2G2J2D7RYKPSHNH1HG9NT1K2HRHGC67W6QM6GEC4BSN1DPNEBCS0AVDT2DBP5G''.
+``287VXK8T6PXKD05W8Y94QJNEFCMRXBC9S7KNKTWGH2G2J2D7RYKPSHNH1HG9NT1K2HRHGC67W6QM6GEC4BSN1DPNEBCS0AVDT2DBP5G``.
Note that typically the salt values must be chosen at random, only for this test we use static salt values.
diff --git a/design-documents/022-wallet-auditor-reports.rst b/design-documents/022-wallet-auditor-reports.rst
new file mode 100644
index 0000000..0df5e42
--- /dev/null
+++ b/design-documents/022-wallet-auditor-reports.rst
@@ -0,0 +1,54 @@
+DD22: Wallet Proofs to Auditor
+##############################
+
+.. note::
+
+ Status (2021-05-25): Writing in progress.
+
+
+Summary
+=======
+
+This design document defines the structure and contents of proofs
+of misbehavior that the wallet sends to auditors.
+
+Motivation
+==========
+
+There are some situations where the wallet learns that some entity did
+something against the protocol specification. When the wallet has
+cryptographic proof for this, this proof should be stored in the database and
+eventually be exportable to auditors, courts, etc.
+
+Requirements
+============
+
+* Users should be able to review all the information that
+ a misbehavior proof would reveal.
+
+Proposed Solution
+=================
+
+Types of Misbehavior
+--------------------
+
+This section collects all types of misbehavior for which the wallet
+can export cryptographic proof.
+
+* ``exchange-denomination-spec-inconsistent``
+
+ An exchange has announced a denomination with the same
+ denomination public key, but different metadata (value, expiration)
+
+* ``exchange-denomination-gone``
+
+ The exchange is not accepting/listing a denomination
+ anymore that it previously listed.
+
+
+Discussion / Q&A
+================
+
+* What about complaints to the auditor that do not contain
+ cryptographic proof? (e.g. "exchange XYZ has not been responding
+ for the last 14 days")
diff --git a/design-documents/023-taler-kyc.rst b/design-documents/023-taler-kyc.rst
new file mode 100644
index 0000000..a2fc3c7
--- /dev/null
+++ b/design-documents/023-taler-kyc.rst
@@ -0,0 +1,266 @@
+DD 023: Taler KYC
+#################
+
+Summary
+=======
+
+This document discusses the Know-your-customer (KYC) processes supported by Taler.
+
+
+Motivation
+==========
+
+To legally operate, Taler has to comply with KYC regulation that requires
+banks to identify parties involved in transactions at certain points.
+
+
+Requirements
+============
+
+Taler needs to run KYC checks in the following circumstances:
+
+* Customer withdraws money over a monthly threshold
+
+ * exchange triggers KYC
+ * key: IBAN (encoded as payto:// URI)
+
+* Wallet receives (via refunds) money resulting in a balance over a threshold
+
+ * this is a client-side restriction
+ * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
+
+* Wallet receives money via P2P payments
+
+ * key: reserve (=KYC account) long term public key per wallet (encoded as payto:// URI)
+
+* Merchant receives money (Q: any money, or above a monthly threshold?)
+
+ * key: IBAN (encoded as payto:// URI)
+
+
+
+Proposed Solution
+=================
+
+Exchange modifications
+^^^^^^^^^^^^^^^^^^^^^^
+
+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. In this table, we additionally store information
+related to the KYC status of the underlying payto://-URI.
+
+The new ``/kyc-check/`` endpoint is based on the ``wire_targets`` serial
+number. 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 pair,
+the ``/kyc-check/`` endpoint returns either the (positive) KYC status or
+redirects the client (202) to the current stage of the KYC process. (The
+endpoint may have to create and store a nonce to be used during
+``/kyc-proof/``, depending on the OAuth variant used.) The redirection is
+offered using an HTTP-redirect for Web-based clients and a JSON body with
+information for triggering a browser-based KYC process using OAuth 2.0.
+
+The OAuth 2.0 process is setup to end at a new ``/kyc-proof/`` endpoint. This
+endpoint then updates the KYC table of the exchange with the legitimization
+status (which is checked using OAuth 2.0). The endpoint also wakes up any
+long-polling ``/kyc-check/`` requests. Naturally, the exchange's OAuth 2.0
+client credentials must be configured apriori with the legitimization service.
+
+When withdrawing, the exchange checks if the KYC status is acceptable. If no
+KYC was done and if either the amount withdrawn over the last X days exceeds
+the threshold or the reserve received received a P2P transfer, then a ``202
+Accepted`` is returned which redirects the consumer to the new ``/kyc-check/``
+handler.
+
+When depositing, the exchange checks the KYC status and if negative, returns an
+additional information field that tells the merchant the ``wire_target_serial``
+number needed to begin the KYC process (this is independent of the amount)
+at the new ``/kyc-check/`` handler.
+
+When tracking deposits, the exchange also adds the ``wire_target_serial`` to
+the reply if the KYC status is negative.
+
+The aggregator is modified to only SELECT deposits where the ``wire_target``
+has the KYC status set to positive (unless KYC is disabled in the exchange
+configuration).
+
+To allow the wallet to do the KYC check if it is about to exceed a set balance
+threshold, we modify the ``/keys`` response to add a optional field
+``wallet_balance_limit_without_kyc`` the wallet is allowed to hold in coins
+from this exchange without KYC. If this field is absent, there is no limit.
+If the field is provided, a correct wallet must create a long-term
+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 must first request the user to complete the KYC
+process.
+
+For that, it 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. 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.
+
+
+ ..note::
+
+ Unrelated: We may want to consider directly deleting prewire records
+ instead of setting them to ``finished`` in ``taler-exchange-transfer``.
+
+
+
+Exchange database schema changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Note that there is may be some slight complication in the migration as the
+h_wire in deposits is salted, while the h_payto in the new wire_targets is
+expected to be unsalted. So converting the existing information to create the
+wire_targets table will be tricky!
+
+We can *either* not support a fully automatic migration, or do an "expensive"
+migration with C logic (so not just SQL statements).
+
+.. sourcecode:: sql
+
+ -- Everything in one big transaction
+ BEGIN;
+ -- Check patch versioning is in place.
+ SELECT _v.register_patch('exchange-TBD', NULL, NULL);
+ --
+ 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
+ ,kyc_ok BOOLEAN NOT NULL DEFAULT (false)
+ ,oauth_username STRING NOT NULL
+ ,PRIMARY KEY (h_wire)
+ );
+ 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.kyc_ok
+ IS 'true if the KYC check was passed successfully';
+ COMMENT ON COLUMN wire_targets.oauth_username
+ IS 'Name of the user that was used for OAuth 2.0-based legitimization';
+ --
+ -- NOTE: logic to fill wire_target missing, so this
+ -- CANNOT work if the database contains any data!
+ --
+ ALTER TABLE wire_out
+ ADD COLUMN wire_target_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
+ DROP COLUMN wire_target;
+ COMMENT ON COLUMN wire_out.wire_target_serial_id
+ IS 'Identifies the target bank account and KYC status';
+ --
+ ALTER TABLE reserves_in
+ ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
+ DROP COLUMN sender_account_details;
+ COMMENT ON COLUMN wire_out.wire_target_serial_id
+ IS 'Identifies the target bank account and KYC status';
+ --
+ ALTER TABLE reserves_close
+ ADD COLUMN wire_source_serial_id INT8 NOT NULL REFERENCES wire_targets (wire_target_serial_id),
+ DROP COLUMN receiver_account;
+ COMMENT ON COLUMN reserves_close.wire_target_serial_id
+ IS 'Identifies the target bank account and KYC status. Note that closing does not depend on KYC.';
+ --
+ ALTER TABLE deposits
+ ADD COLUMN wire_target_serial_id INT8 NOT NULL,
+ ADD COLUMN salt BYTEA NOT NULL CHECK (LENGTH(salt)=64),
+ DROP COLUMN h_wire,
+ DROP COLUMN wire;
+ COMMENT ON COLUMN deposits.wire_target_serial_id
+ IS 'Identifies the target bank account and KYC status';
+ -- Complete transaction
+ --
+ -- FIXME: 512-bit SALT is likely not specified/checked
+ -- anywhere in the code (salt==string), and we probably
+ -- should move to a 128-bit salt anyway!
+ --
+ COMMIT;
+
+
+TODO: Check if we missed miss any tables to migrate!
+
+
+Merchant modifications
+^^^^^^^^^^^^^^^^^^^^^^
+
+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.
+
+A new private endpoint ``/kyc`` is introduced which allows frontends to
+request the ``/kyc`` status of any configured account (including with long
+polling). If the KYC status is negative or the ``kyc_timestamp`` not recent
+(say older than one month), the merchant backend will re-check the KYC status
+at the exchange (and update its cached status). The endpoint then returns
+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
+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.
+
+ ..note::
+
+ Semi-related: The TMH_setup_wire_account() should be changed to use
+ 128-bit salt values (to keep ``deposits`` table small) and checks for salt
+ to be well-formed should be added "everywhere".
+
+
+
+Bank requirements
+^^^^^^^^^^^^^^^^^
+
+The exchange primarily requires an OAuth 2.0 login page where the user
+can either login (and share an access token that grants access to only
+the username) or register to initiate the KYC process.
+
+
+Alternatives
+============
+
+We may not need the oauth_username, but it seems saner to store it to
+provide a link to the legitimization resource server.
+
+We could also store the access token, but that seems slightly more
+dangerous and given the close business relationship is unnecessary.
+
+We may want to store some additional "permission level" obtained from the
+resource server to say for which of the operations (see requirements section)
+the legitimization is sufficient.
+
+
+
+Drawbacks
+=========
+
+
+Discussion / Q&A
+================
+
+(This should be filled in with results from discussions on mailing lists / personal communication.)
diff --git a/design-documents/024-age-restriction.rst b/design-documents/024-age-restriction.rst
new file mode 100644
index 0000000..0d8d3c3
--- /dev/null
+++ b/design-documents/024-age-restriction.rst
@@ -0,0 +1,511 @@
+DD 024: Anonymous Age Restriction Extension for GNU Taler
+#########################################################
+
+Summary
+=======
+
+This document presents and discusses an extension to GNU Taler that provides
+anonymous age-restriction.
+
+Motivation
+==========
+
+Merchants are legally obliged to perform age verification of customers when
+they buy certain goods and services. Current mechanisms for age verification
+are either ID-based or require the usage of credit/debit cards. In all cases
+sensitive private information is disclosed.
+
+We want to offer a better mechanism for age-restriction with GNU Taler that
+
+* ensures anonymity and unlinkability of purchases
+* can be set to particular age groups by parents/wardens at withdrawal
+* is bound to particular coins/tokens
+* can be verified by the merchant at purchase time
+* persists even after refresh
+
+The mechanism is presented as an 'extension' to GNU Taler, that is, as an
+optional feature that can be switched on by the exchange operator.
+
+Requirements
+============
+
+TODO
+
+* legal requirements for merchants must allow for this kind of mechanism
+
+
+Proposed Solution
+=================
+
+We propose an extension to GNU Taler for age-restriction that can be enabled by
+an Exchange¹).
+
+Once enabled, coins with age restrictions can be withdrawn by parents/warden
+who can choose to **commit** the coins to a certain maximum age out of a
+predefined list of age groups.
+
+The minors/wards receive those coins and can now **attest** a required minimum
+age (provided that age is less or equal to the committed age of the coins) to
+merchants, who can **verify** the minimum age.
+
+For the rest values (change) after an transaction, the minor/ward can
+**derive** new age-restricted coins. The exchange can **compare** the equality
+of the age-restriction of the old coin with the new coin (in a zero-knowledge
+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)
+
+¹) 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.
+We might reconsider this design decision at some point.
+
+
+Main ideas and building blocks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The main ideas are simple:
+
+#. The exchange defines and publishes M different *age groups* of increasing order:
+ :math:`0 < a_1 < \ldots < a_M` with :math:`a_i \in \mathbb{N}`.
+
+#. An **unrestricted** *age commitment* is defined as a vector of length M of
+ pairs of EdDSA public and private keys on Curve25519. In other words: one
+ key pair for each age group:
+ :math:`\bigl\langle (p_1, s_1), \ldots, (p_M, s_M) \bigr\rangle`
+
+#. 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 (p_1, s_1), \ldots, (p_m, s_m), \, (p_{m+1}, \perp),
+ \ldots, (p_M, \perp )\bigr\rangle`. 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).
+
+#. 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
+ available in a restricted age commitment. (Unrestricted age commitments can
+ attest for any age group).
+
+#. An age commitment is *bound to a particular coin* by incorporating the
+ SHA512 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.
+
+TODO: Summarize the design based on the five functions ``Commit()``,
+``Attest()``, ``Verify()``, ``Derive()``, ``Compare()``, once the paper from
+Özgür and Christian is published.
+
+
+Changes in the Exchange API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The necessary changes in the exchange involve
+
+* indication of support for age restriction as an extension
+* modification of the refresh protocol (both, commit and reveal phase)
+* modification of the deposit protocol
+
+
+Extension for age restriction
+-----------------------------
+
+The exchange indicates support for age-restriction in response to ``/keys`` by
+registering the extension ``age_restriction.v1`` with a value type
+``ExtensionAgeRestriction``:
+
+.. ts:def:: ExtensionAgeRestriction
+
+ interface ExtensionAgeRestriction {
+ // The field ``critical`` is mandatory for an extension.
+ // Age restriction is not required to be understood by an client, so
+ // ``critical`` will be set to ``false``.
+ critical: false;
+
+ // Age restriction specific fields.
+
+ // Representation of the age groups as comma separated edges: Increasing
+ // from left to right, the values mark the begining of an age group up
+ // to, but not including the next value. The initial age group starts at
+ // 0 and is not listed. Example: "8:10:12:14:16:18:21".
+ // 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_groups: string;
+ }
+
+Registering an extension is defined in
+:doc:`design document 006 ― Extensions <006-extensions>`.
+
+Age restricted denominations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If age-restriction is registered as an extension under the name
+``age_restriction.v1``, 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
+apply for each denomination in the list:
+
+1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
+ is taken over the public key of the denomination **and** the string in
+ ``ExtensionAgeRestriction.age_groups`` from the corresponding extension
+ object (see above).
+
+2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
+ ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``.
+
+And similar to ``.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.
+
+
+.. ts:def:: ExchangeKeysResponse
+
+ interface ExchangeKeysResponse {
+ //...
+
+ // List of denominations that support age-restriction with the age groups
+ // given in age_groups. This is only set **iff** the extension
+ // ``age_restriction.v1`` is registered under ``entensions`` with type
+ // ``ExtensionAgeRestriction``.
+ //
+ // The data structure for each denomination is the same as for the
+ // denominations in ExchangeKeysResponse.denoms. **However**, the
+ // following differences apply for each denomination in the list:
+ //
+ // 1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
+ // is taken over the public key of the denomination __and__ the
+ // string in ``ExtensionAgeRestriction.age_groups`` from the
+ // corresponding extension object.
+ //
+ // 2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
+ // ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``
+ //
+ // 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[];
+
+ //...
+ }
+
+
+SQL changes
+-----------
+
+The exchange has to mark denominations with support for age restriction as such
+in the database. Also, during the melting phase of the refresh operation, the
+exchange will have to persist the SHA512 hash of the age commitment of the
+original coin.
+
+The schema for the exchange is changed as follows:
+
+.. sourcecode:: sql
+
+ -- Everything in one big transaction
+ BEGIN;
+ -- Check patch versioning is in place.
+ SELECT _v.register_patch('exchange-TBD', NULL, NULL);
+
+ -- Support for age restriction is marked per denomination.
+ ALTER TABLE denominations
+ ADD COLUMN age_restricted BOOLEAN NOT NULL DEFAULT (false);
+ COMMENT ON COLUMN denominations.age_restriced
+ IS 'true if this denomination can be used for age restriction';
+
+ -- During the melting phase of the refresh, the wallet has to present the
+ -- hash value of the age commitment (only for denominations with support
+ -- for age restriction).
+ ALTER TABLE refresh_commitments
+ ADD COLUMN age_commitment_h BYTEA CHECK (LENGTH(age_commitment_h)=64);
+ COMMENT ON COLUMN refresh_commitments.age_commitment_h
+ IS 'SHA512 hash of the age commitment of the old coin, iff the corresponding
+ denomimination has support for age restriction, NULL otherwise.';
+ COMMIT;
+
+Note the constraint on ``refresh_commitments.age_commitment_h``: It can be
+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
+----------------
+
+Refresh - melting phase
+~~~~~~~~~~~~~~~~~~~~~~~
+
+During the melting phase of the refresh, the wallet has to present the hash
+value of the age commitment (for denominations with support for age
+restriction). Therefore, in the ``/coins/$COIN_PUB/melt`` POST request, the
+``MeltRequest`` object is extended with an optional field
+``age_commitment_hash``:
+
+.. ts:def:: MeltRequest
+
+ interface MeltRequest {
+ ...
+
+ // SHA512 hash of the age commitment of the coin, IFF the denomination
+ // has age restriction support. MUST be omitted otherwise.
+ age_commitment_hash?: HashCode;
+
+ ...
+ }
+
+The responses to the POST request remain the same.
+
+For normal denominations *without* support for age restriction, the calculation
+for the signature check is as before (borrowing notation from
+`Florian's thesis <https://taler.net/papers/thesis-dold-phd-2019.pdf>`_):
+
+.. math::
+ \text{FDH}_N(C_p)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}\,N
+
+Here, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is its
+signature and :math:`\langle e, N \rangle` is the RSA public key of the
+denomination.
+
+For denominations *with* support for age restriction, the exchange takes the
+hash value ``age_commitment_hash`` (abbreviated as :math:`h_a`) into account
+when verifying the coin's signature:
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+
+
+
+Refresh - reveal phase
+~~~~~~~~~~~~~~~~~~~~~~
+
+During the reveal phase -- that is upon POST to ``/refreshes/$RCH/reveal`` --
+the client has to provide the original age commitment of the old coin (i.e. the
+vector of public keys), iff the corresponding denomination had support for age
+restriction. The size of the vector ist defined by the Exchange implicetly as
+the amount of age groups defined in the field ``.age_groups`` of the
+``ExtensionAgeRestriction``.
+
+.. ts:def:: RevealRequest
+
+ interface RevealRequest {
+ ...
+
+ // Iff the corresponding denomination has support for age restriction,
+ // the client MUST provide the original age commitment, i.e. the vector
+ // of public keys.
+ // The size of the vector ist defined by the Exchange implicetly as the
+ // amount of age groups defined in the field ``.age_groups`` of the
+ // ``ExtensionAgeRestriction``.
+ old_age_commitment?: EddsaPublicKey[];
+
+
+ ...
+ }
+
+TODO: describe how the exchange derives the κ-1 other age-restriction vectors
+and compares them to the one in ``.old_age_commitment``.
+
+
+Deposit
+~~~~~~~
+
+As always, the merchant has to provide the public key of a coin during a POST
+to ``/coins/$COIN_PUB/deposit``. However, for coins with age restriction, the
+signature check requires the hash of the age commitment. Therefore the request
+object ``DepositRequest`` is extended by an optional field
+``age_commitment_hash`` which MUST be set (with the SHA512 hash of the age
+commitment), iff the corresponding denomination had support for age restriction
+enabled. The merchant has received this value prior from the customer during
+purchase.
+
+.. ts:def:: DepositRequest
+
+ interface DepositRequest {
+ ...
+
+ // Iff the corresponding denomination had support for age restriction
+ // enabled, this field MUST contain the SHA512 value of the age commitment that
+ // was provided during the purchase.
+ age_commitment_hash?: HashCode;
+
+ ...
+ }
+
+Again, the exchange can now check the validity of the coin with age restriction
+by evaluating
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+Also again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
+its signature, :math:`\langle e, N \rangle` is the RSA public key of the
+denomination and :math:`h_a` is the value from ``age_commitment_hash``.
+
+TODO: maybe rename this field into something more opaque, like
+``opaque_signature_salt`` or so?
+
+
+
+
+Changes in the Merchant API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+Claiming the order
+------------------
+
+If an order requires a minimum age, the merchant MUST express that required
+minimum age in response to order claim by the wallet, that is, a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/claim``.
+
+The object ``ContractTerms`` is extended by an optional field
+``required_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
+
+ interface ContractTerms {
+ ...
+
+ // If the order requires a minimum age greater than 0, this field is set
+ // to the integer value of that age. In reality this value will not be
+ // smaller than, say, 8, and not larger than, say, 21.
+ required_minimum_age?: Integer;
+
+ ...
+ }
+
+By sending the contract term with the field ``required_minimum_age`` set to an
+non-zero integer value, the merchant implicetly signals that it understands the
+extension ``age_restriction.v1`` for age restriction from the exchange.
+
+
+Making the payment
+------------------
+
+If the ``ContractTerms`` had a non-zero value in field
+``required_minimum_age``, the wallet has to provide evidence of that minimum
+age by
+
+#. *either* using coins which are of denominations that had *no* age support
+ enabled,
+
+#. *or* using coins which are of denominations that have support for age
+ restriction enabled
+
+ * and then ―for each such coin― it has the right private key of the
+ restricted age commitment to the age group into which the required minimum
+ age falls (i.e. a non-empty entry at the right index in vector of EdDSA
+ keys, see above).
+
+ * and signs the required minimum age with each coin's private key
+ corresponding to the age group,
+
+ * and sends ―for each coin― the complete age commitment and the signature to
+ the merchant.
+
+The object ``CoinPaySig`` used within a ``PayRequest`` during a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/pay`` is extended as follows:
+
+.. ts:def:: CoinPaySig
+
+ export interface CoinPaySig {
+ ...
+
+ // If a minimum age was required by the order and the wallet had coins that
+ // are at least commited to the corresponding age group, this is the
+ // signature of the minimum age as a string, using the private key to the
+ // corresponding age group.
+ minimum_age_sig?: EddsaSignature;
+
+ // If a minium age was required by the order, this is age commitment bound
+ // to the coin, i.e. the complete vector of EdDSA public keys, one for each
+ // age group (as defined by the exchange).
+ age_commitment?: EddsaPublicKey[];
+
+ }
+
+
+The merchant can now verify
+
+#. the validity of each (age restricted) coin by evaluating
+
+ .. math:: \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+ Again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is its
+ signature, :math:`\langle e, N \rangle` is the RSA public key of the
+ denomination and :math:`h_a` is the SHA512 hash value of the vector in
+ ``age_commitment``.
+
+#. the minimum age requirement by checking the signature in ``minimum_age_sig``
+ against the public key ``age_commitment[k]`` of the corresponding age group,
+ say, ``k``. (The minimum age must fall into the age group at index ``k`` as
+ defined by the exchange).
+
+**Note**: This applies only to coins for denominations that have support for
+age restriction. Denominations *without* support for age restriction *always*
+fullfill 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.
+
+
+
+Alternatives
+============
+
+TODO.
+
+* ID-based systems
+* credit/debit card based systems
+
+
+Drawbacks
+=========
+
+TODO.
+
+* age groups, once defined, are set permanently
+* age restricted coins are basically shared between ward and warden.
+
+Also discuss:
+
+* storage overhead
+* computational overhead
+* bandwidth overhead
+* legal issues?
+
+Discussion / Q&A
+================
+
+We had some very engaged discussions on the GNU Taler `mailing list <taler@gnu.org>`__:
+
+* `Money with capabilities <https://lists.gnu.org/archive/html/taler/2021-08/msg00005.html>`_
+
+
+* `On age-restriction (was: online games in China) <https://lists.gnu.org/archive/html/taler/2021-09/msg00006.html>`__
+
+* `Age-restriction is about coins, not currencies <https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`__
+
+
+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.
diff --git a/design-documents/025-withdraw-from-wallet.rst b/design-documents/025-withdraw-from-wallet.rst
new file mode 100644
index 0000000..473c5d0
--- /dev/null
+++ b/design-documents/025-withdraw-from-wallet.rst
@@ -0,0 +1,69 @@
+DD 025: Withdraw coins manually starting from the wallet
+#########################################################
+
+Summary
+=======
+
+This document presents the design discussion about the manual withdraw screens
+
+Motivation
+==========
+
+To have a way to initiate a withdrawal process and complete with a bank that is
+not aware of GNU Taler
+
+Proposed Solution
+=================
+
+Access to the feature
+^^^^^^^^^^^^^^^^^^^^^
+
+Adding a withdraw button in the main balance page to initiate the process.
+
+This feature can be use in the Pay call-to-action when there is not enough coins.
+
+Start
+^^^^^
+
+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
+ :width: 800
+
+
+Success
+^^^^^^^
+
+Here the user will see the account details where it needs to send money to
+complete the withdrawal.
+
+.. image:: ../wallet-confirm-withdraw.svg
+ :width: 800
+
+Transaction history
+^^^^^^^^^^^^^^^^^^^
+
+The account information should be added into the Withdrawal Transaction detail
+screen if the withdrawal is still pending. This will also affect the use case
+when the user started the transaction from a taler-aware bank (in which case the
+user doesn't need to do an extra step to complete the process) so the text
+should be consistent in both scenarios.
+
+Alternatives
+============
+
+* removing the amount field, the exchange will send coins equal to the amount it received
+
+* showing the fees, can we calculate the withdrawal fee?
+
+* should we show the terms of service?
+
+* exchange field first has been discussed, but the exchange list its only showing the
+ current currency exchanges, the user need to switch the currency first. Adding a
+ new exchange sould be done in a different context that can be accessed using the
+ ``add exchange`` link
+
+
+
diff --git a/design-documents/index.rst b/design-documents/index.rst
index f5b8a93..444e475 100644
--- a/design-documents/index.rst
+++ b/design-documents/index.rst
@@ -14,6 +14,7 @@ and protocol.
003-tos-rendering
004-wallet-withdrawal-flow
005-wallet-backup-sync
+ 006-extensions
007-payment
008-fees
009-backup
@@ -30,4 +31,7 @@ and protocol.
020-backoffice-tips-management
021-exchange-key-continuity
022-wallet-auditor-reports
+ 023-taler-kyc
+ 024-age-restriction
+ 025-withdraw-from-wallet
999-template
diff --git a/frags/installing-anastasis-gtk.rst b/frags/installing-anastasis-gtk.rst
deleted file mode 100644
index b9d5dc3..0000000
--- a/frags/installing-anastasis-gtk.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-The following steps assume at least the GNUnet, gnunet-gtk and Anastasis
-dependencies are installed.
-
-First, unpack the anastasis-gtk tarball and change into the resulting
-directory. Then, use the following commands to build and install
-anastasis-gtk:
-
-.. code-block:: console
-
- $ ./configure [--prefix=PFX] \
- [--with-gnunet=GNUNETPFX] \
- [--with-exchange=EXCHANGEPFX] \
- [--with-anastasis=ANASTASISPFX]
- $ # Each dependency can be fetched from non standard locations via
- $ # the '--with-<LIBNAME>' option. See './configure --help'.
- $ make
- # make install
-
-If you did not specify a prefix, anastasis-gtk will be installed to
-``/usr/local``, which requires you to run the last step as ``root``.
-
-You have to specify ``-with-anastasis=/usr/local``, ``--with-exchange=/usr/local`` and/or
-``--with-gnunet=/usr/local`` if you installed the exchange and/or
-GNUnet to ``/usr/local`` in the previous steps.
-
-Depending on the prefixes you specified for the installation and the
-distribution you are using, you may have to edit ``/etc/ld.so.conf``, adding
-lines for ``GNUNETPFX/lib/`` and ``EXCHANGEPFX/lib/`` and ``PFX/lib/``
-(replace the prefixes with the actual paths you used). Afterwards, you should
-run ``ldconfig``. Without this step, it is possible that the linker may not
-find the installed libraries and launching anastasis-gtk would then fail.
diff --git a/frags/installing-anastasis.rst b/frags/installing-anastasis.rst
deleted file mode 100644
index 7978b71..0000000
--- a/frags/installing-anastasis.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-The following steps assume all dependencies are installed.
-
-First, unpack the Anastasis tarball and change into
-the resulting directory.
-Then, use the following commands to build and install Anastasis:
-
-.. code-block:: console
-
- $ ./configure [--prefix=PFX] \
- [--with-gnunet=GNUNETPFX] \
- [--with-exchange=EXCHANGEPFX]
- $ # Each dependency can be fetched from non standard locations via
- $ # the '--with-<LIBNAME>' option. See './configure --help'.
- $ make
- # make install
-
-If you did not specify a prefix, Anastasis will be installed to
-``/usr/local``, which requires you to run the last step as ``root``.
-
-You have to specify ``--with-exchange=/usr/local`` and/or
-``--with-gnunet=/usr/local`` if you installed the exchange and/or
-GNUnet to ``/usr/local`` in the previous steps.
-
-Depending on the prefixes you specified for the installation and the
-distribution you are using, you may have to edit ``/etc/ld.so.conf``, adding
-lines for ``GNUNETPFX/lib/`` and ``EXCHANGEPFX/lib/`` and ``PFX/lib/``
-(replace the prefixes with the actual paths you used). Afterwards, you should
-run ``ldconfig``. Without this step, it is possible that the linker may not
-find the installed libraries and launching the Anastasis backend would
-then fail.
diff --git a/frags/installing-debian.rst b/frags/installing-debian.rst
index 24f4d20..0110b24 100644
--- a/frags/installing-debian.rst
+++ b/frags/installing-debian.rst
@@ -1,47 +1,14 @@
To install the GNU Taler Debian packages, first ensure that you have
the right Debian distribution. At this time, the packages are built for
-Sid, which means you should use a system which at least includes
-unstable packages in its source list. We recommend using APT pinning
-to limit unstable packages to those explicitly requested. To do this,
-set your ``/etc/apt/preferences`` as follows:
+Bullseye.
-.. code-block::
-
- Package: *
- Pin: release a=stable
- Pin-Priority: 700
-
- Package: *
- Pin: release a=testing
- Pin-Priority: 650
-
- Package: *
- Pin: release a=unstable
- Pin-Priority: 600
-
- Package: *
- Pin: release l=Debian-Security
- Pin-Priority: 1000
-
-A typical ``/etc/apt/sources.list`` file for this setup
-which combines Debian stable with more recent packages
-from testing and unstable would look like this:
-
-.. code-block::
-
- deb http://ftp.ch.debian.org/debian/ buster main
- deb http://security.debian.org/debian-security buster/updates main
- deb http://ftp.ch.debian.org/debian/ testing main
- deb http://ftp.ch.debian.org/debian/ unstable main
-
-Naturally, you may want to use different mirrors depending on your region.
-Additionally, you must add a file to import the GNU Taler packages. Typically,
+You need to add a file to import the GNU Taler packages. Typically,
this is done by adding a file ``/etc/apt/sources.list.d/taler.list`` that
looks like this:
.. code-block::
- deb https://deb.taler.net/apt/debian sid main
+ deb https://deb.taler.net/apt/debian bullseye main
Next, you must import the Taler Systems SA public package signing key
into your keyring and update the package lists:
diff --git a/frags/installing-trisquel.rst b/frags/installing-trisquel.rst
new file mode 100644
index 0000000..4ca8bcc
--- /dev/null
+++ b/frags/installing-trisquel.rst
@@ -0,0 +1,4 @@
+To install the GNU Taler Trisquel packages, first ensure that you have
+the right Trisquel distribution. Packages are currently available for
+Trisquel GNU/Linux 10.0. Simply follow the same instructions provided
+for Ubuntu 20.04 LTS (Focal Fossa).
diff --git a/index.rst b/index.rst
index 6e18201..c16a912 100644
--- a/index.rst
+++ b/index.rst
@@ -44,9 +44,9 @@ components, internal architecture of key components, and how to get them
installed. In addition to this documentation, we have source-level
documentation which you can find converted to HTML at these pages:
- * `Exchange <https://docs.taler.net/doxygen/exchange/>`_
- * `Merchant <https://docs.taler.net/doxygen/merchant/>`_
- * `Wallet-core <https://docs.taler.net/doxygen/wallet-core/>`_
+* `Exchange <https://docs.taler.net/doxygen/exchange/>`_
+* `Merchant <https://docs.taler.net/doxygen/merchant/>`_
+* `Wallet-core <https://docs.taler.net/doxygen/wallet-core/>`_
Documentation Overview
----------------------
@@ -57,6 +57,7 @@ Documentation Overview
core/index
taler-exchange-manual
+ taler-exchange-setup-guide
taler-merchant-manual
taler-wallet-cli-manual
taler-nfc-guide.rst
diff --git a/libeufin/api-nexus.rst b/libeufin/api-nexus.rst
index 0a0b73e..5f3e7a6 100644
--- a/libeufin/api-nexus.rst
+++ b/libeufin/api-nexus.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
Nexus API
###########
@@ -82,7 +84,7 @@ User Management
.. code-block:: ts
interface UserChangePassword {
- newPassword: string;
+ newPassword: string;
}
.. http:post:: {nexusBase}/users
@@ -201,7 +203,7 @@ Test API
``C53``.
**Response**
-
+
The successful case should respond with a ``200 OK`` and a empty JSON body.
@@ -231,6 +233,23 @@ manages payment initiations of the account and tracks the initiations of payment
ownerName: string;
}
+
+.. http:get:: {nexusBase}/bank-accounts/{my-acct}
+
+ Get basic information about the bank account named ``my-acct``.
+
+ interface BankAccountInfoWithBalance {
+ // ID number of the database row being the default bank connection
+ // of `my-acct`.
+ defaultBankConnection: number;
+ // Payto://-URI of `my-acct`.
+ accountPaytoUri: string;
+ // Balance of `my-acct` as it was downloaded from the bank
+ // along the last Camt document. A human-readable message
+ // will inform the requester, should this value not be found.
+ lastSeenBalance: string;
+ }
+
.. http:post:: {nexusBase}/bank-accounts/{acctid}/payment-initiations/{pmtid}/submit
Ask nexus to submit one prepare payment at the bank.
@@ -373,12 +392,17 @@ manages payment initiations of the account and tracks the initiations of payment
bankConnection: string;
}
- **Response:** Tells how many transactions were never downloaded before:
+ **Response:**
.. code-block:: ts
interface NewTransactions {
- newTransactions: number;
+ // How many transactions are new to Nexus.
+ newTransactions: number;
+ // How many transactions got downloaded by the request.
+ // Note that a transaction can be downloaded multiple
+ // times but only counts as new once.
+ downloadedTransactions: number;
}
.. http:get:: {nexusBase}/bank-accounts/{acctid}/transactions
@@ -414,7 +438,7 @@ manages payment initiations of the account and tracks the initiations of payment
// FIXME
valueDate: string;
-
+
// When this payment got booked. In the form YYYY-MM-DD
bookingDate: string;
@@ -468,14 +492,14 @@ Scheduling API
--------------
.. http:post:: {nexusBase}/bank-accounts/{acctid}/schedule
-
+
This endpoint allows the caller to define a recurrent
execution of a task.
**Request**
.. ts:def:: ScheduleTask
-
+
interface ScheduleTask {
name: string;
@@ -499,7 +523,7 @@ Scheduling API
.. http:get:: {nexusBase}/bank-accounts/{acctid}/schedule/{taskId}
-
+
**Response**
.. ts:def:: NexusTask
@@ -509,7 +533,7 @@ Scheduling API
// the scheduling feature.
interface NexusTask {
- // FIXME: document all.
+ // FIXME: document all.
resourceType: string;
resourceId: string;
taskName: string;
@@ -522,7 +546,7 @@ Scheduling API
.. http:delete:: {nexusBase}/bank-accounts/{acctid}/schedule/{taskId}
-
+
This call deletes the task associated to ``taskId``.
.. http:get:: {nexusBase}/bank-accounts/{acctid}/schedule
@@ -758,7 +782,7 @@ to the real bank.
code: string;
// the unique identifier of the message.
- messageId: string;
+ messageId: string;
// bytes length of the message.
length: number;
@@ -804,7 +828,7 @@ Facades
}
.. http:delete:: {nexus}/facades/{fcid}
-
+
Delete a facade.
.. http:post:: {nexus}/facades
@@ -820,7 +844,7 @@ Facades
name: string;
// Type of the facade.
- // For example, "taler-wire-gateway".
+ // For example, "taler-wire-gateway" or "anastasis".
type: string;
// Bank accounts that the facade has read/write
@@ -888,6 +912,115 @@ They are namespaced under the ``/ebics/`` sub-resource.
in the nexus database. It will just make a request to the bank
and return the answer.
+Anastasis API.
+--------------
+
+This is a read-only API offering a view over *only* the incoming
+transactions of a bank account. It is named after the typical user -
+a Anastasis service - but can be used in any case where only the
+incoming transactions are of interest.
+
+.. http:get:: ${BASE_URL}/history/incoming
+
+ Return a list of transactions made to the customer.
+
+ The bank account of the customer is determined via the base URL and/or the
+ user name in the ``Authorization`` header. In fact the transaction history
+ might come from a "virtual" account, where multiple real bank accounts are
+ merged into one history.
+
+ Transactions are identified by an opaque numeric identifier, referred to here
+ as *row ID*. The semantics of the row ID (including its sorting order) are
+ determined by the bank server and completely opaque to the client.
+
+ The list of returned transactions is determined by a row ID *starting point*
+ and a signed non-zero integer *delta*:
+
+ * If *delta* is positive, return a list of up to *delta* transactions (all matching
+ the filter criteria) strictly **after** the starting point. The transactions are sorted
+ in **ascending** order of the row ID.
+ * If *delta* is negative, return a list of up to *-delta* transactions (all matching
+ the filter criteria) strictly **before** the starting point. The transactions are sorted
+ in **descending** order of the row ID.
+
+ If *starting point* is not explicitly given, it defaults to:
+
+ * A value that is **smaller** than all other row IDs if *delta* is **positive**.
+ * A value that is **larger** than all other row IDs if *delta* is **negative**.
+
+ **Request**
+
+ :query start: *Optional.*
+ Row identifier to explicitly set the *starting point* of the query.
+ :query delta:
+ The *delta* value that determines the range of the query.
+ :query long_poll_ms: *Optional.* If this parameter is specified and the
+ result of the query would be empty, the bank will wait up to ``long_poll_ms``
+ milliseconds for new transactions that match the query to arrive and only
+ then send the HTTP response. A client must never rely on this behavior, as
+ the bank may return a response immediately or after waiting only a fraction
+ of ``long_poll_ms``.
+
+ **Response**
+
+ :http:statuscode:`200 OK`: JSON object of type `IncomingHistory`.
+ :http:statuscode:`400 Bad request`: Request malformed. The bank replies with an `ErrorDetail` object.
+ :http:statuscode:`401 Unauthorized`: Authentication failed, likely the credentials are wrong.
+ :http:statuscode:`404 Not found`: The endpoint is wrong or the user name is unknown. The bank replies with an `ErrorDetail` object.
+
+ .. ts:def:: IncomingHistory
+
+ interface IncomingHistory {
+
+ // Array of incoming transactions.
+ incoming_transactions : IncomingBankTransaction[];
+
+ }
+
+ .. ts:def:: IncomingBankTransaction
+
+ interface IncomingBankTransaction {
+
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount transferred.
+ amount: Amount;
+
+ // Payto URI to identify the receiver of funds.
+ // This must be one of the exchange's bank accounts.
+ credit_account: string;
+
+ // Payto URI to identify the sender of funds.
+ debit_account: string;
+
+ // subject of the incoming payment.
+ subject: string;
+
+ }
+
+The anastasis facade
+--------------------
+
+The ``anastasis`` facade has the following configuration:
+
+
+.. ts:def:: AnastasisFacadeConfig
+
+ interface AnastasisFacadeConfig {
+ // Bank account and connection that is abstracted over.
+ bankAccount: string;
+ bankConnection: string;
+
+ currency: string;
+
+ // Corresponds to whether we trust C52, C53 or C54 (SEPA ICT)
+ // for incoming transfers.
+ reserveTransferLevel: "statement" | "report" | "notification";
+ }
The taler-wire-gateway facade
-----------------------------
diff --git a/libeufin/api-sandbox.rst b/libeufin/api-sandbox.rst
index 1b555b0..b75fcf8 100644
--- a/libeufin/api-sandbox.rst
+++ b/libeufin/api-sandbox.rst
@@ -1,97 +1,92 @@
+.. target audience: developer, core developer
.. _sandbox-api:
-Sandbox API
-###########
-
-
-HTTP API
-========
+Current Sandbox API
+###################
..
- Payments.
+ Current Sandbox endpoints.
+ GET /
+ Welcome message.
-.. http:post:: /admin/payments/camt
+ ** Camt debug **
- Return the history of one IBAN in Camt.053 format.
+ POST /admin/payments/camt
+ give last statement of requesting account
- **Request**
+ ** Bank accounting **
- .. code-block:: tsref
+ POST /admin/bank-accounts/$accountLabel
+ create bank account (no EBICS involved)
- interface CamtParams {
+ GET /admin/bank-accounts
+ Give summary of all the bank accounts.
- // IBAN managed by the running Sandbox, for which
- // the Camt.053 response is going to be generated.
- iban: string;
+ GET /admin/bank-accounts/$accountLabel
+ give general information about a bank account
- // The Camt type to return. Only '53' is allowed
- // at this moment.
- type: number;
- }
+ ** Transactions **
- **Response**
+ POST /admin/bank-accounts/$accountLabel/simulate-incoming-transaction
+ Book one incoming transaction for $accountLabel.
+ The debtor (not required to be in the same bank)
+ information is taken from the request.
- The expected Camt.053 document.
+ GET /admin/bank-accounts/$accountLabel/transactions
+ Inform about all the transactions of one bank account.
+ POST /admin/bank-accounts/$accountLabel/generate-transactions
+ Generate one incoming and one outgoing transaction
+ for one bank account.
-.. http:get:: /admin/payments
+ ** EBICS subscribers management **
- Return the list of *all* the payments known by the sandbox.
+ POST /admin/ebics/subscribers
+ Create a new EBICS subscriber.
- **Response**
+ GET /admin/ebics/subscribers
+ Give details of all the EBICS subscribers in the bank.
- .. code-block:: tsref
-
- interface SandboxPayments {
+ POST /admin/ebics/bank-accounts
+ Create a *new* bank account and link it with a existing
+ EBICS subscriber.
- // The list of all known payments, regardless
- // of any IBAN involved in them.
- payments: SandboxPayment[];
- }
+ ** EBICS host management **
-.. http:post:: /admin/payments
+ POST /admin/ebics/hosts/$hostID/rotate-keys
+ Change the bank's keys used in EBICS communication.
- Adds a new payment to the book. Mainly used for testing
- purposes.
+ POST /admin/ebics/hosts
+ Create a new EBICS host.
- **Request:**
+ GET /admin/ebics/hosts
+ Show details of all the EBICS hosts in the bank.
- One object of type `SandboxPayment`
+ ** EBICS serving **
- .. ts:def:: SandboxPayment
+ POST /ebicsweb
+ Processes a EBICS request.
- interface SandboxPayment {
+ ** Taler .. **
- // IBAN that will receive the payment.
- creditorIban: string;
- // FIXME
- creditorBic: string;
- // FIXME
- creditorName: string;
+ GET /taler
+ Show one taler://-URI to initiate a withdrawal.
- // IBAN that will send the payment.
- debitorIban: string;
- // FIXME
- debitorBic: string;
- // FIXME
- debitorName: string;
+ - Taler integration API.
- amount: string;
- currency: string;
+ - Demobank configuration API.
+ ToDo.
- // subject of the payment.
- subject: string;
+ - Taler access API.
+ ToDo.
- // Whether the payment is credit or debit *for* the
- // account being managed *by* the running sandbox.
- // Can take the values: "CRDT" or "DBIT".
- direction: string;
- }
+EBICS.
+^^^^^^
-..
- Host management.
+Hosts.
+++++++
.. http:post:: /admin/ebics/hosts
@@ -133,8 +128,37 @@ HTTP API
meant for tests (as the Sandbox itself is) and no backup will be
produced along this operation.
-..
- Subscriber management.
+
+Subscribers.
+++++++++++++
+
+.. http:post:: /admin/ebics/bank-accounts
+
+ Associates a new bank account to an existing subscriber.
+
+ **Request:**
+
+ .. ts:def:: BankAccountRequest
+
+ interface BankAccountRequest {
+
+ // Ebics subscriber
+ subscriber: string;
+
+ // IBAN
+ iban: string;
+
+ // BIC
+ bic: string;
+
+ // human name
+ name: string;
+
+ // bank account label
+ label: string;
+
+ }
+
.. http:post:: /admin/ebics/subscribers
@@ -189,36 +213,205 @@ HTTP API
}
-.. http:post:: /admin/ebics/bank-accounts
- Associates a new bank account to an existing subscriber.
+Bank accounts.
+^^^^^^^^^^^^^^
- **Request:**
+.. http:get:: /admin/bank-accounts
- .. ts:def:: BankAccountRequest
+ Give summary of all the bank accounts.
- interface BankAccountRequest {
+.. http:get:: /admin/bank-accounts/$accountLabel
- // Ebics subscriber
- subscriber: string;
+ Give information about a bank account.
- // IBAN
- iban: string;
- // BIC
- bic: string;
+Main EBICS service.
+^^^^^^^^^^^^^^^^^^^
- // human name
- name: string;
+.. http:post:: /ebicsweb
- // bank account label
- label: string;
+ Serves all the Ebics requests.
+
+
+Transactions.
+^^^^^^^^^^^^^
+
+JSON.
++++++
+
+.. http:get:: /admin/bank-accounts/$accountLabel/transactions
+
+ Inform about all the transactions of one bank account.
+
+.. http:post:: /admin/bank-accounts/$accountLabel/generate-transactions
+
+ Generate one incoming and one outgoing transaction for one bank account.
+
+.. http:post:: /admin/bank-accounts/$accountLabel/simulate-incoming-transaction
+
+ Book one incoming transaction for $accountLabel.
+ The debtor (not required to be in the same bank)
+ information is taken from the request.
+
+Camt.
++++++
+
+.. http:post:: /admin/payments/camt
+
+ Return the last statement of the requesting account.
+
+ **Request**
+
+ .. code-block:: tsref
+
+ interface CamtParams {
+
+ // label of the bank account being queried.
+ bankaccount: string;
+
+ // The Camt type to return. Only '53' is allowed
+ // at this moment.
+ type: number;
}
-..
- Main EBICS service.
+ **Response**
-.. http:post:: /ebicsweb
+ The expected Camt.053 document.
- Serves all the Ebics requests.
+Future Sandbox API
+##################
+
+Resources.
+----------
+
+Sandbox serves the following resources:
+
+ - Demobanks.
+ - Bank accounts.
+ - Subscribers.
+ - Transactions.
+ - Customers.
+ - Reports.
+
+The resources are subject to all CRUD operations, via by two
+types of users: the 'admin' and the customers. The admin has
+rights on all of them.
+
+One of the main differences with the previous versions is the
+removal of the "/admin" initial component. If the administrator
+authenticates for one operation, then this one is of type `admin`:
+no need for a dedicate and extra URI part.
+
+For example, mocking transactions in the system was a typical
+/admin-operation, but since transactions themselves are resources
+and any resource is subject to CRUD operations, then mocking one
+becomes just a `C` operation on the 'transactions' resources. If
+a test case wants to simplify the authentication - by hard-coding
+the credentials, for example - before mocking one transaction, then
+it can impersonate the administrator.
+
+.. note::
+
+ ``POST``-ing to a endpoint with a trailing ``$id`` means
+ modification of a existing resource.
+
+Demobanks
+^^^^^^^^^
+
+Demobanks are the main containers of all the other resources.
+They take configuration values, like the currency for example.
+
+.. http:get:: /admin/demobanks
+.. http:get:: /admin/demobanks/$id
+.. http:post:: /admin/demobanks
+.. http:post:: /admin/demobanks/$id
+.. http:delete:: /admin/demobanks/$id
+
+Bank accounts.
+^^^^^^^^^^^^^^
+
+Bank accounts collect money of customers and participate
+in transactions.
+
+The ``$base`` is one demobank, in the form ``/demobanks/$id``.
+That is due because a bank account must inherit some defaults
+from the demobank, like the currency.
+
+.. http:get:: $base/bankaccounts
+.. http:get:: $base/bankaccounts/$id
+.. http:post:: $base/bankaccounts
+.. http:post:: $base/bankaccounts/$id
+.. http:delete:: $base/bankaccounts/$id
+
+Subscribers.
+^^^^^^^^^^^^
+
+Subscribers are (optional) customers identities for protocols
+other than the native one.
+
+A subscriber is not required to have a bank account `soon`. Therefore,
+it can be created, and later be linked to one bank account. For this
+reason, the ``$base`` should not mention one bank account.
+
+.. note::
+
+ Creating a subscriber is a time-consuming operation that goes often
+ through slow channels, therefore it should basically never be done
+ more than once.
+
+.. http:get:: $base/subscribers
+.. http:get:: $base/subscribers/$id
+.. http:post:: $base/subscribers
+.. http:post:: $base/subscribers/$id
+.. http:delete:: $base/subscribers/$id
+
+Transactions.
+^^^^^^^^^^^^^
+
+Transactions are money movements between bank accounts.
+
+For convenience, ``$base`` **could** mention one bank account
+to let customers see their transactions without defining "history"
+query parameters: like ``$demobank/bankaccounts/$bankaccountId/transactions``.
+
+At the same time, transactions should be easy to query regardless
+of whose bank account they involve -- for administrative reasons, for
+example. Hence, a "global" URI scheme like ``$demobank/transactions?param=XXX``
+should be offered as well.
+
+.. http:get:: $base/transactions
+.. http:get:: $base/transactions/$id
+.. http:post:: $base/transactions
+.. http:post:: $base/transactions/$id
+.. http:delete:: $base/transactions/$id
+
+Customers.
+^^^^^^^^^^
+
+Customers are persons that **can** have one bank account. As
+with subscribers, ``$base`` should not mention any bank account
+so as to leave more flexibility to assign new bank accounts to
+the same customer.
+
+.. http:get:: $base/customers
+.. http:get:: $base/customers/$id
+.. http:post:: $base/customers
+.. http:post:: $base/customers/$id
+.. http:delete:: $base/customers/$id
+
+Reports.
+^^^^^^^^
+
+Reports are persistent documents witnessing transactions.
+A typical example is a Camt.053 statement. A report lives
+even after a bank account gets deleted or a customer leaves
+the bank, therefore ``$base`` should only mention a demobank
+instance.
+
+.. http:get:: $base/reports
+.. http:get:: $base/reports/$id
+.. http:post:: $base/reports
+.. http:post:: $base/reports/$id
+.. http:delete:: $base/reports/$id
diff --git a/libeufin/bank-transport-ebics.rst b/libeufin/bank-transport-ebics.rst
index 5afdd5d..7d26cbc 100644
--- a/libeufin/bank-transport-ebics.rst
+++ b/libeufin/bank-transport-ebics.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
The EBICS Bank Transport
========================
diff --git a/libeufin/banking-protocols.rst b/libeufin/banking-protocols.rst
index 8a1bc09..2eaffb3 100644
--- a/libeufin/banking-protocols.rst
+++ b/libeufin/banking-protocols.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
Banking Protocols
#################
diff --git a/libeufin/concepts.rst b/libeufin/concepts.rst
index a0f2bb7..6a25199 100644
--- a/libeufin/concepts.rst
+++ b/libeufin/concepts.rst
@@ -1,3 +1,5 @@
+.. target audience: operator, developer
+
###################
Conceptual Overview
###################
diff --git a/libeufin/demo-deployment-gv.rst b/libeufin/demo-deployment-gv.rst
new file mode 100644
index 0000000..4946257
--- /dev/null
+++ b/libeufin/demo-deployment-gv.rst
@@ -0,0 +1,49 @@
+.. target audience: operator, developer
+
+Deploying Taler with libEufin
+#############################
+
+.. contents:: Table of Contents
+
+This guide shows the steps to set up libEufin as the bank
+for Taler. At this stage, only three constant bank accounts
+are allowed, and the withdrawal can be triggered via the
+command line interface.
+
+This document is more a set of notes for internal use than
+a actual document for system administrators.
+
+Deployment on Gv
+----------------
+
+After having pulled the latest code of deployment.git:
+
+.. code-block:: console
+
+ # The step below destroys data!
+ $ rm -fr ~/taler-data/*
+ # For the step below, the environment must be called 'int'.
+ $ taler-deployment bootstrap
+ $ taler-deployment build
+ # The step below destroys data!
+ $ taler-deployment-prepare-with-eufin resetDb
+ $ taler-deployment-restart-with-eufin
+
+At this point, the file ~/libeufin_admin_password should exist
+and contain the password for the 'admin' user of the Sandbox. This
+user is *also* the one that can withdraw with Taler.
+
+Withdraw with Taler
+-------------------
+
+.. code-block:: console
+
+ $ http --auth admin:$password $hostname/eufin/sandbox/taler
+
+The previous step should have returned a taler://-URI that
+can be passed to the wallet:
+
+
+.. code-block:: console
+
+ $ taler-wallet-cli handle-uri $taler_uri
diff --git a/libeufin/ebics.rst b/libeufin/ebics.rst
index 5cabc48..058804e 100644
--- a/libeufin/ebics.rst
+++ b/libeufin/ebics.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
EBICS Implementation Notes
##########################
diff --git a/libeufin/frontend.rst b/libeufin/frontend.rst
index 19fba47..1fe1c83 100644
--- a/libeufin/frontend.rst
+++ b/libeufin/frontend.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
###################
LibEuFin Frontend
###################
diff --git a/libeufin/index.rst b/libeufin/index.rst
index 4141f44..0e18486 100644
--- a/libeufin/index.rst
+++ b/libeufin/index.rst
@@ -17,3 +17,4 @@ LibEuFin is a project providing free software tooling for European FinTech.
transaction-identification
frontend
nexus-tutorial
+ demo-deployment-gv
diff --git a/libeufin/iso20022.rst b/libeufin/iso20022.rst
index e45222a..a4c0bf7 100644
--- a/libeufin/iso20022.rst
+++ b/libeufin/iso20022.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
ISO 20022
#########
diff --git a/libeufin/nexus-tutorial.rst b/libeufin/nexus-tutorial.rst
index 6b6eff6..51e0cd2 100644
--- a/libeufin/nexus-tutorial.rst
+++ b/libeufin/nexus-tutorial.rst
@@ -1,3 +1,5 @@
+.. target audience: operator, developer
+
LibEuFin How-To
###############
@@ -19,14 +21,6 @@ steps in :ref:`Configuring the Sandbox <configuring-the-sandbox>`.
Installing LibEuFin
===================
-.. warning::
-
- LibEuFin does not yet ship with any systemd unit files.
-
- There is an `open bug report <https://bugs.gnunet.org/view.php?id=6719>`__
- for this issue.
-
-
Installation on Debian
----------------------
@@ -54,28 +48,28 @@ At this point, the services can be started on boot:
.. code-block:: console
- # systemctl enable nexus # use 'disable' to rollback
- # systemctl enable sandbox
+ # systemctl enable libeufin-nexus # use 'disable' to rollback
+ # systemctl enable libeufin-sandbox # only if you want the sandbox
Or just manually:
.. code-block:: console
- # systemctl start nexus # use 'stop' to terminate.
- # systemctl start sandbox
+ # systemctl start libeufin-nexus # use 'stop' to terminate.
+ # systemctl start libeufin-sandbox # only if you want the sandbox
The following command should inform the user about the status
of the running / terminated service:
.. code-block:: console
- # systemctl status nexus
+ # systemctl status libeufin-nexus
For more diagnostics, use:
.. code-block:: console
- # journalctl -u nexus
+ # journalctl -u libeufin-nexus
Run-time dependencies
---------------------
@@ -133,7 +127,7 @@ In case of success, the three following commands should be found:
.. _configuring-the-sandbox:
-(Optional) Configuring the Sandbox
+Configuring the Sandbox (Optional)
==================================
If you don't have access to a real bank account with an EBICS API, you can set
@@ -143,11 +137,17 @@ core banking system with EBICS access to bank accounts.
The sandbox relies on a database, which you must specify using a JDBC
connection URI with the ``LIBEUFIN_SANDBOX_DB_CONNECTION`` environment
variable, before invoking any commands.
-(If this variable is not set, ``libeufin-sandbox`` complains and exits.)
+If this variable is not set, ``libeufin-sandbox`` complains and exits:
-Only *SQLite* (e.g. ``jdbc:sqlite:/tmp/libeufintestdb``) and *PostgreSQL (via TCP)*
-(e.g. ``jdbc:postgresql://localhost:$port/libeufintestdb?user=$username&password=$password``)
-are supported right now.
+.. code-block:: console
+
+ $ libeufin-sandbox serve
+ DB connection string not found/valid in the env variable LIBEUFIN_SANDBOX_DB_CONNECTION.
+ The following two examples are valid connection strings:
+ jdbc:sqlite:/tmp/libeufindb.sqlite3
+ jdbc:postgresql://localhost:5432/libeufindb?user=Foo&password=secret
+
+Only *SQLite* and *PostgreSQL (via TCP)* are supported right now.
For the following commands, the sandbox service must be running.
The sandbox service is started with the following command:
@@ -155,7 +155,7 @@ The sandbox service is started with the following command:
.. code-block:: console
$ export LIBEUFIN_SANDBOX_DB_CONNECTION=jdbc:sqlite:/tmp/libeufintestdb
- $ libeufin-sandbox serve --port 5000
+ $ libeufin-sandbox serve --port 5016
To reset the state of the sandbox, delete the database.
@@ -165,7 +165,7 @@ service:
.. code-block:: console
- $ export LIBEUFIN_SANDBOX_URL=http://localhost:5000/
+ $ export LIBEUFIN_SANDBOX_URL=http://localhost:5016/
Verify that the sandbox is running:
@@ -184,7 +184,7 @@ Now an EBICS host can be created:
$ libeufin-cli sandbox ebicshost create --host-id testhost
Note that most ``libeufin-cli`` subcommands will ask for input interactively if
-the respective value is not specified as a command line option.
+the respective value is not specified on the command line.
Next, create an EBICS subscriber (identified by the partner ID and user ID) for the host:
@@ -219,6 +219,15 @@ on EBICS):
$ libeufin-cli sandbox bankaccount generate-transactions testacct01
+.. code-block:: console
+
+ $ libeufin-cli sandbox bankaccount simulate-incoming-transaction testacct01 \
+ --debtor-iban DE06500105174526623718 \
+ --debtor-bic INGDDEFFXXX \
+ --debtor-name "Joe Foo" \
+ --subject "Hello World" \
+ --amount 10.50
+
Payments to a sandbox bank account can be listed as follows:
.. code-block:: console
@@ -251,11 +260,15 @@ Use the following command to run the nexus service:
.. code-block:: console
$ export LIBEUFIN_NEXUS_DB_CONNECTION=jdbc:postgresql://localhost:5433/libeufindb?user=foo&password=secret
- $ libeufin-nexus serve --port 5001
+ $ libeufin-nexus serve --port 5017
-This sets up the PostgreSQL database to listen on port 5433,
-for internal communication with the nexus service.
-The nexus service itself listens on port 5001.
+This assumes that the PostgreSQL service with a database
+called ``libeufindb`` listens on port 5433
+for requests from the nexus service.
+The nexus service itself listens on port 5017.
+Note that you must have the ``LIBEUFIN_NEXUS_DB_CONNECTION``
+environment variable set for most uses of the libeufin-nexus
+command.
At this point a superuser account needs to be created:
@@ -276,14 +289,15 @@ The command line interface needs the following three values
to be defined in the environment: ``LIBEUFIN_NEXUS_URL``, ``LIBEUFIN_NEXUS_USERNAME``,
and ``LIBEUFIN_NEXUS_PASSWORD``. In this example, ``LIBEUFIN_NEXUS_USERNAME`` should be
set to ``foo``, and ``LIBEUFIN_NEXUS_PASSWORD`` to the value given for its password
-in the previous step (with the ``libeufin-nexus superuser`` command).
+in the previous step (with the ``libeufin-nexus superuser`` command). The
+``LIBEUFIN_NEXUS_URL`` could be given as ``http://localhost:5017/``.
Next, we create a EBICS *bank connection* that nexus can use to communicate with the bank.
.. note::
- For the sandbox setup in this guide, the EBICS base URL
- is ``http://localhost:5000/ebicsweb``.
+ For the sandbox setup in this guide, the ``EBICS_BASE_URL``
+ should be ``http://localhost:5016/ebicsweb``.
.. code-block:: console
@@ -354,12 +368,18 @@ It is now possible to list the accounts offered by the connection.
list-offered-bank-accounts \
$CONNECTION_NAME
+.. note::
+
+ The ``nexusBankAccountId`` field should at this step be ``null``,
+ as we have not yet imported the bank account and thus the account
+ does not yet have a local name.
+
Nexus now needs an explicit import of the accounts it should manage. This
step is needed to let the user pick a custom name for such accounts.
.. code-block:: console
- $ libeufin-cli
+ $ libeufin-cli \
connections \
import-bank-account \
--offered-account-id testacct01 \
@@ -413,15 +433,15 @@ The following command prepares a payment:
.. code-block:: console
$ libeufin-cli accounts prepare-payment \
- --creditor-iban $IBAN_TO_SEND_MONEY_TO \
- --creditor-bic $BIC_TO_SEND_MONEY_TO \
- --creditor-name $CREDITOR_NAME \
- --payment-amount $AMOUNT \
- --payment-subject $SUBJECT \
+ --creditor-iban=$IBAN_TO_SEND_MONEY_TO \
+ --creditor-bic=$BIC_TO_SEND_MONEY_TO \
+ --creditor-name=$CREDITOR_NAME \
+ --payment-amount=$AMOUNT \
+ --payment-subject=$SUBJECT \
$LOCAL_ACCOUNT_NAME
-Note: the ``$AMOUNT`` value needs the format ``X.Y:CURRENCY``; for example
-``10:EUR``, or ``1.01:EUR``.
+Note: the ``$AMOUNT`` value needs the format ``CURRENCY:X.Y``; for example
+``EUR:10``, or ``EUR:1.01``.
The previous command should return a value (``$UUID``) that uniquely
identifies the prepared payment in the Nexus system. That is needed
@@ -451,8 +471,8 @@ once at 11pm every day :
.. code-block:: console
$ libeufin-cli accounts task-schedule myacct \
- --task-type="submit"
- --task-name='submit-payments-hourly'
+ --task-type="submit" \
+ --task-name='submit-payments-hourly' \
--task-cronspec='0 0 *'
$ libeufin-cli accounts task-schedule myacct \
@@ -486,11 +506,18 @@ INI and HIA secret keys will be restored for the requesting user.
.. code-block:: console
- $ libeufin-cli connection \ restore-backup
- --passphrase $SECRET \
- --backup-file $BACKUP_FILE \
+ $ libeufin-cli connections \
+ restore-backup \
+ --passphrase=$SECRET \
+ --backup-file=$BACKUP_FILE \
$CONNECTION_NAME
+..
+ FIXME:
+ On a bad password given, Nexus responds
+ "bad backup given" instead of "authentication failed".
+
+
Creating a Taler facade
=======================
@@ -501,11 +528,13 @@ want to refuse payments that do not conform to certain rules.
At this moment, only the *Taler facade type* is implemented
in the Nexus, and the command below instantiates one under a
-existing bank account / connection pair.
+existing bank account / connection pair. You can freely
+assign an identifier for the ``$FACADE_NAME`` below:
.. code-block:: console
- $ libeufin-cli facades new-facade \
+ $ libeufin-cli facades new-taler-wire-gateway-facade \
+ --currency EUR \
--facade-name $FACADE_NAME \
$CONNECTION_NAME \
$LOCAL_ACCOUNT_NAME
@@ -514,6 +543,49 @@ At this point, the additional *taler-wire-gateway* (FIXME: link
here to API here) API becomes offered by the Nexus. The purpose
is to let a Taler exchange rely on Nexus to manage its bank account.
+The base URL of the facade that can be used by the Taler exchange
+as the Taler Wire Gateway base URL located when listing the facades:
+
+.. code-block:: console
+
+ $ libeufin-cli facades list
+
+Creating a Anastasis facade
+===========================
+
+The following command creates a Anastasis facade. At this point, *only*
+the superuser is able to create facades; please set the environment variables
+``LIBEUFIN_NEXUS_USERNAME`` and ``LIBEUFIN_NEXUS_PASSWORD`` accordingly.
+
+.. code-block:: console
+
+ $ libeufin-cli facades new-anastasis-facade \
+ --currency EUR \
+ --facade-name $FACADE_NAME \
+ $CONNECTION_NAME \
+ $LOCAL_ACCOUNT_NAME
+
+If the previous command succeeded, it is possible to see the
+facade's base URL with:
+
+.. code-block:: console
+
+ $ libeufin-cli facades list
+
+At this point, to allow a non superuser to use the facade, a history
+permission needs to be set:
+
+.. code-block:: console
+
+ $ libeufin-cli permissions grant \
+ user $USERNAME \
+ facade $FACADENAME \
+ facade.anastasis.history
+
+.. note::
+
+ There is no need to set/unset a transfer permission on the facade
+ since this one does not offer any endpoint to issue wire transfers.
Managing Permissions and Users
==============================
diff --git a/libeufin/sepa.rst b/libeufin/sepa.rst
index 9555256..29baca0 100644
--- a/libeufin/sepa.rst
+++ b/libeufin/sepa.rst
@@ -1,3 +1,5 @@
+.. target audience: core developer
+
SEPA Payments
#############
diff --git a/libeufin/transaction-identification.rst b/libeufin/transaction-identification.rst
index e9a7cf0..6c88a72 100644
--- a/libeufin/transaction-identification.rst
+++ b/libeufin/transaction-identification.rst
@@ -1,3 +1,5 @@
+.. target audience: developer, core developer
+
Transaction Identification
##########################
diff --git a/manpages/taler-bank-benchmark.1.rst b/manpages/taler-bank-benchmark.1.rst
new file mode 100644
index 0000000..52078fa
--- /dev/null
+++ b/manpages/taler-bank-benchmark.1.rst
@@ -0,0 +1,89 @@
+taler-bank-benchmark(1)
+#######################
+
+.. only:: html
+
+ Name
+ ====
+
+ **taler-bank-benchmark** - benchmark only the 'bank' and the 'taler-exchange-wirewatch' tool
+
+Synopsis
+========
+
+**taler-bank-benchmark**
+[**-c** *FILENAME* | **--config=**\ ‌\ *FILENAME*]
+[**-h** | **--help**]
+[**-K** | **--linger**]
+[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
+[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
+[**-m** *MODE* | **--mode=**\ \ *MODE*]
+[**-p** *NPROCS* | **--worker-parallelism=**\ \ *NPROCS*]
+[**-P** *NTHREADS* | **--service-parallelism=**\ \ *NTHREADS*]
+[**-r** *NRESERVES* | **--reserves=**\ \ *NRESERVES*]
+[**-s** *HISTSIZE* | **--size=**\ \ *HISTSIZE*]
+[**-V** | **--verbose**]
+[**-v** | **--version**]
+[**-w** | **--wirewatch**]
+
+
+Description
+===========
+
+**taler-bank-benchmark** is a command-line tool to benchmark only the "bank"
+and the ``taler-exchange-wirewatch`` tool.
+
+The options for **taler-bank-benchmark** are:
+
+**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
+ Use the configuration and other resources for the merchant to operate
+ from *FILENAME*.
+
+**-h** \| **--help**
+ Print short help on options.
+
+**-K** \| **--linger**
+ Linger around until key press.
+
+**-L** *LOGLEVEL* \| **--loglevel=**\ ‌\ *LOGLEVEL*
+ Specifies the log level to use. Accepted values are: ``DEBUG``, ``INFO``,
+ ``WARNING``, ``ERROR``.
+
+**-l** *FILENAME* \| **--logfile=**\ ‌\ *FILENAME*
+ Send logging output to *FILENAME*.
+
+**-m** *MODE* \| **--mode=**\ \ *MODE*
+ Run as ``bank``, ``client`` or ``both``.
+ If unspecified, *MODE* defaults to ``both``.
+
+**-P** *NTHREADS** \| **--service-parallelism=**\ \ *NTHREADS*
+ Run with *NTHREADS* service threads.
+
+**-p** *NPROCS* \| **--worker-parallelism=**\ \ *NPROCS*
+ Run with *NPROCS* client processes.
+
+**-r** *NRESERVES* \| **--reserves=**\ \ *NRESERVES*
+ Create *NRESERVES* reserves per client.
+
+**-s** *HISTSIZE* \| **--size=**\ \ *HISTSIZE*
+ Configure the fakebank to keep in memory at most *HISTSIZE* history elements.
+
+**-V** \| **--verbose**
+ Display more output than usual.
+
+**-v** \| **--version**
+ Print version information.
+
+**-w** \| **--wirewatch**
+ Run the ``taler-exchange-wirewatch`` tool.
+
+See Also
+========
+
+taler-exchange-httpd(1), taler.conf(5).
+
+Bugs
+====
+
+Report bugs by using https://bugs.taler.net/ or by sending electronic
+mail to <taler@gnu.org>.
diff --git a/manpages/taler-exchange-aggregator.1.rst b/manpages/taler-exchange-aggregator.1.rst
index cf8f3ed..4a15c22 100644
--- a/manpages/taler-exchange-aggregator.1.rst
+++ b/manpages/taler-exchange-aggregator.1.rst
@@ -27,6 +27,10 @@ Description
to the same merchant into larger wire transfers. The actual transfers are then
done by **taler-exchange-transfer**.
+The AGGREGATOR_SHARD_SIZE option can be used to allow multiple aggregator processes to run in parallel and share the load. This is only recommended if a single aggregator is insufficient for the workload.
+
+The aggregator uses a special table to lock shards it is working on. If an aggregator process dies (say due to a power failure), these shard locks may prevent the aggregator from resuming normally. In this case, you must run "taler-exchange-dbinit -s" to release the shard locks before restarting the aggregator.
+
**-c** *FILENAME* \| **--config=**\ ‌\ *FILENAME*
Use the configuration and other resources for the exchange to operate
from *FILENAME*.
diff --git a/manpages/taler-exchange-dbinit.1.rst b/manpages/taler-exchange-dbinit.1.rst
index e91cafc..0ee8354 100644
--- a/manpages/taler-exchange-dbinit.1.rst
+++ b/manpages/taler-exchange-dbinit.1.rst
@@ -19,6 +19,7 @@ Synopsis
[**-L** *LOGLEVEL* | **--loglevel=**\ ‌\ *LOGLEVEL*]
[**-l** *FILENAME* | **--logfile=**\ ‌\ *FILENAME*]
[**-r** | **--reset**]
+[**-s** | **--shardunlock**]
[**-v** | **--version**]
Description
@@ -52,6 +53,9 @@ Its options are as follows:
Drop tables. Dangerous, will delete all existing data in the database
before creating the tables.
+**-s** \| **--shardunlock**
+ Clears the (revolving) shards table. Needed to clear locks that may be held after a crash (of taler-exchange-aggregator or the operating system, say due to power outage) or if the AGGREGATOR_SHARD_SIZE option is changed in the configuration file.
+
**-v** \| **–version**
Print version information.
diff --git a/manpages/taler-exchange-offline.1.rst b/manpages/taler-exchange-offline.1.rst
index f3036be..4641b41 100644
--- a/manpages/taler-exchange-offline.1.rst
+++ b/manpages/taler-exchange-offline.1.rst
@@ -30,12 +30,15 @@ the exchange’s long-term offline signing key and should be run in a secure
subcommands as arguments which are then processed sequentially.
The tool includes two subcommands to interact *online* with the exchange's
-REST APIs. The ``download`` subcommand downloads the future public keys from the
-running exchange. The resulting data serves as input to the ``sign`` and ``show``
-subcommands. The ``upload`` subcommand uploads the signatures created with the
-private master key to the exchange. It handles the output of all subcommands
-(except ``download``). The ``download`` and ``upload`` subcommands must naturally be
-run "online" and do not require access to the offline key.
+REST APIs. To determine how to talk to the exchange, these two subcommands
+rely on the ``BASE_URL`` configuration option from the ``[exchange]`` section
+of the configuration file. The ``download`` subcommand downloads the future
+public keys from the running exchange. The resulting data serves as input to
+the ``sign`` and ``show`` subcommands. The ``upload`` subcommand uploads the
+signatures created with the private master key to the exchange. It handles
+the output of all subcommands (except ``download``). The ``download`` and
+``upload`` subcommands must naturally be run "online" and do not require
+access to the offline key.
All other subcommands are intended to be run "offline". However, especially
when testing, it is of course possible to run the subcommands online as well.
@@ -215,12 +218,12 @@ enable-account
--------------
This subcommand
-informs an exchange that it should advertise a bank account as belonging to
+informs an exchange that it should advertize a bank account as belonging to
the exchange on its ``/wire`` endpoint. Note that this does *not* ensure that
the exchange will use this bank account for incoming or outgoing wire
transfers! For this, the **taler-exchange-transfer** and
**taler-exchange-wirewatch** tools must be configured. Furthermore, the bank
-account information advertised could theoretically differ from that which
+account information advertized could theoretically differ from that which
these tool actually use, for example if the public bank account is only a
front for the actual internal business accounts.
diff --git a/manpages/taler-wire-gateway-client.1.rst b/manpages/taler-exchange-wire-gateway-client.1.rst
index 8c6e7e9..0533c35 100644
--- a/manpages/taler-wire-gateway-client.1.rst
+++ b/manpages/taler-exchange-wire-gateway-client.1.rst
@@ -1,17 +1,17 @@
-taler-wire-gateway-client(1)
-############################
+taler-exchange-wire-gateway-client(1)
+#####################################
.. only:: html
Name
====
- **taler-wire-gateway-client** - trigger a transfer at the bank (or obtain transaction history)
+ **taler-exchange-wire-gateway-client** - trigger a transfer at the bank (or obtain transaction history)
Synopsis
========
-**taler-wire-gateway-client**
+**taler-exchange-wire-gateway-client**
[**-a** *VALUE* | **--amount=**\ ‌\ *VALUE*]
[**-b** *URL* | **--bank=**\ ‌\ *URL*]
[**-C** *ACCOUNT* | **--credit=**\ ‌\ *ACCOUNT*]
@@ -32,7 +32,7 @@ Synopsis
Description
===========
-**taler-wire-gateway-client** is a command-line tool to trigger bank transfers or
+**taler-exchange-wire-gateway-client** is a command-line tool to trigger bank transfers or
inspect wire transfers for exchange accounts using the wire API. The tool is
expected to be used during testing or for diagnostics.
@@ -94,7 +94,7 @@ Options
Use *SUBJECT* for the wire transfer subject. Must be a reserve public key for credit operations and a wire transfer identifier for debit operations. If not specified, a random value will be generated instead.
**-s** *ACCOUNT_SECTION* \| **--section=**\ ‌\ *ACCOUNT-SECTION*
- Obtain exchange account information from the *ACCOUNT-SECTION* of the configuration. Conflicts with **-u**, **-p** and **-b**. Note that either **-b** or **-s** must be specified.
+ Obtain exchange account information from the *ACCOUNT-SECTION* of the configuration. The argument must be a ``[exchange-accountcredentials-$NAME]`` section name and thus start with the ``exchange-accountcredentials-`` prefix. Conflicts with **-u**, **-p** and **-b**. Note that either **-b** or **-s** must be specified.
**-u** *USERNAME* \| **--user=**\ ‌\ *USERNAME*
Specifies the username for authentication. Optional and conflicts with **-s**. If neither **-u** nor **-s** are used, we will attempt to talk to the bank without authentication.
diff --git a/manpages/taler.conf.5.rst b/manpages/taler.conf.5.rst
index 268391d..c148d33 100644
--- a/manpages/taler.conf.5.rst
+++ b/manpages/taler.conf.5.rst
@@ -52,7 +52,11 @@ overrides these defaults.
A configuration file may include another, by using the ``@INLINE@`` directive,
for example, in ``main.conf``, you could write ``@INLINE@ sub.conf`` to
include the entirety of ``sub.conf`` at that point in ``main.conf``.
-.. TODO: Document ‘taler-config -V’ in light of ‘@INLINE@’ in taler-config(1).
+
+Be extra careful when using ``taler-config -V VALUE`` to change configuration
+values: it will destroy all uses of ``@INLINE@`` and furthermore remove all
+comments from the configuration file!
+
GLOBAL OPTIONS
--------------
@@ -112,9 +116,24 @@ BASE_URL
Added to wire transfers to enable tracking by merchants.
AGGREGATOR_IDLE_SLEEP_INTERVAL
- For how long should the aggregator sleep when it is idle
+ For how long should the taler-exchange-aggregator sleep when it is idle
+ before trying to look for more work? Default is 60 seconds.
+
+CLOSER_IDLE_SLEEP_INTERVAL
+ For how long should the taler-exchange-closer sleep when it is idle
+ before trying to look for more work? Default is 60 seconds.
+
+TRANSFER_IDLE_SLEEP_INTERVAL
+ For how long should the taler-exchange-transfer sleep when it is idle
+ before trying to look for more work? Default is 60 seconds.
+
+WIREWATCH_IDLE_SLEEP_INTERVAL
+ For how long should the taler-exchange-wirewatch sleep when it is idle
before trying to look for more work? Default is 60 seconds.
+AGGREGATOR_SHARD_SIZE
+ Which share of the range from [0,..2147483648] should be processed by one of the shards of the aggregator. Useful only for Taler exchanges with ultra high-performance needs. When changing this value, you must stop all aggregators and run "taler-exchange-dbinit -s" before resuming. Default is 2147483648 (no sharding).
+
SIGNKEY_LEGAL_DURATION
For how long are signatures with signing keys legally valid?
@@ -155,6 +174,42 @@ PRIVACY_DIR
PRIVACY_ETAG
Works the same as ``TERMS_ETAG``, just for the privacy policy.
+KYC_MODE
+ Set to "NONE" to disable KYC for this exchange (but check with your lawyer first).
+ Set to "OAUTH2" to use OAuth2 for KYC.
+
+KYC_WITHDRAW_LIMIT
+ Maximum amount that can be withdrawn in
+ KYC_WITHDRAW_PERIOD without needing KYC.
+ Only used if KYC_MODE is not "NONE".
+
+KYC_WITHDRAW_PERIOD
+ The time period over which transactions
+ are considered for the KYC_WITHDRAW_LIMIT.
+ Only used if KYC_MODE is not "NONE".
+
+KYC_WALLET_BALANCE_LIMIT
+ Maximum amount that a wallet is allowed to hold without
+ having to undergo the KYC process of the issuing
+ exchange. Optional option, if not given there
+ is no limit.
+
+
+EXCHANGE KYC OAUTH2 OPTIONS
+---------------------------
+
+The following options must be in the section "[exchange-kyc-oauth2]".
+
+
+KYC_OAUTH2_URL
+ URL of the OAuth2 endpoint to be used for KYC checks. Requires KYC_ENABLED to be "OAUTH2".
+
+KYC_OAUTH2_CLIENT_ID
+ Client ID of the exchange when it talks to the KYC OAuth2 endpoint. Requires KYC_ENABLED to be "OAUTH2".
+
+KYC_OAUTH2_CLIENT_SECRET
+ Client secret of the exchange to use when talking to the KYC Oauth2 endpoint. Requires KYC_ENABLED to be "OAUTH2".
+
EXCHANGE OFFLINE SIGNING OPTIONS
--------------------------------
@@ -270,8 +325,7 @@ EXCHANGE ACCOUNT OPTIONS
An exchange (or merchant) can have multiple bank accounts. The following
options are for sections named “[exchange-account-SOMETHING]”. The ``SOMETHING`` is
arbitrary and should be chosen to uniquely identify the bank account for
-the operator. These options are used by the **taler-exchange-transfer**
-and **taler-exchange-wirewatch** tools.
+the operator. These options are used by the **taler-exchange-aggregator**, **taler-exchange-closer**, **taler-exchange-transfer** and **taler-exchange-wirewatch** tools.
PAYTO_URI
Specifies the payto://-URL of the account. The general format is
@@ -281,6 +335,18 @@ PAYTO_URI
``payto://iban/DE67830654080004822650/`` (providing the BIC is optional).
Note: only the wire-method is actually used from the URI.
+ENABLE_DEBIT
+ Must be set to ``YES`` for the accounts that the
+ **taler-exchange-aggregator** and **taler-exchange-closer** should debit.
+
+ENABLE_CREDIT
+ Must be set to ``YES`` for the accounts that the **taler-exchange-wirewatch**
+ should check for credits. It is yet uncertain if the merchant
+ implementation may check this flag as well.
+
+
+Additionally, for each enabled account there MUST be another matching section named “[exchange-accountcredentials-SOMETHING]”. This section SHOULD be in a ``secret/`` configuration file that is only readable for the **taler-exchange-wirewatch** and **taler-exchange-transfer** processes. It contains the credentials to access the bank account:
+
WIRE_GATEWAY_URL
URL of the wire gateway. Typically of the form
``https://$HOSTNAME[:$PORT]/taler-wire-gateway/$USERNAME/``
@@ -300,14 +366,6 @@ USERNAME
PASSWORD
Password for ``basic`` authentication with the wire gateway.
-ENABLE_DEBIT
- Must be set to ``YES`` for the accounts that the
- **taler-exchange-aggregator** and **taler-exchange-closer** should debit.
-
-ENABLE_CREDIT
- Must be set to ``YES`` for the accounts that the **taler-exchange-wirewatch**
- should check for credits. It is yet uncertain if the merchant
- implementation may check this flag as well.
EXCHANGE COIN OPTIONS
diff --git a/merchant-spec/public-orders-get.ts b/merchant-spec/public-orders-get.ts
new file mode 100644
index 0000000..4a98aab
--- /dev/null
+++ b/merchant-spec/public-orders-get.ts
@@ -0,0 +1,232 @@
+// Merchant's DB state for one particular order.
+// Invariants:
+// paid => claimed
+// claimed => !!contractHash
+// requireClaimToken => !!claimToken
+// !!lastPaidSessionId => paid
+interface MerchantOrderInfo {
+ orderId: string;
+ requireClaimToken: boolean;
+ claimToken?: string;
+ contractHash?: string;
+ claimed: boolean;
+ paid: boolean;
+ // Refund hasn't been picked up yet
+ refundPending: boolean;
+ fulfillmentUrl?: string;
+ publicReorderUrl?: string;
+ lastPaidSessionId?: string;
+}
+
+// Data from the client's request to /orders/{id}
+interface Req {
+ orderId: string;
+ contractHash?: string;
+ claimToken?: string;
+ sessionId?: string;
+ accept: "json" | "html";
+}
+
+// (Abstract) response to /orders/{id}
+interface Resp {
+ httpStatus: string;
+ contentType: "json" | "html";
+ // Schema type of the response
+ responseType: string;
+ // Redirect "Location: " if applicable to status code
+ redirectLocation?: string;
+ // "Taler: " header in the response
+ talerHeader?: string;
+ // Additional details about response
+ response?: any;
+}
+
+// Abstracted merchant database
+type MerchantOrderStore = { [orderId: string]: MerchantOrderInfo };
+
+// Logic for /orders/{id}
+function handlePublicOrdersGet(mos: MerchantOrderStore, req: Req): Resp {
+ const ord = mos[req.orderId];
+ if (!ord) {
+ return respNotFound(req);
+ }
+
+ const authMissing = !!req.contractHash && !!req.claimToken;
+ // For this endpoint, when the order does not have a claim token,
+ // the order status can be accessed *without* h_contract.
+ const authOk =
+ ord.contractHash === req.contractHash ||
+ (ord.requireClaimToken && ord.claimToken === req.claimToken) ||
+ !ord.requireClaimToken;
+
+ if (authMissing && ord.requireClaimToken) {
+ // Client is trying to get the order status of an
+ // order. However, the client is not showing authentication.
+ //
+ // This can happen when the fulfillment URL includes the order ID,
+ // and the storefront redirects the user to the backend QR code
+ // page, because the order is not paid under the current session.
+ // This happens on bookmarking / link sharing.
+ if (!ord.publicReorderUrl) {
+ return respForbidden(req);
+ }
+ return respGoto(req, ord.publicReorderUrl);
+ }
+
+ // Even if an order is paid for,
+ // we still need the ord.claimToken, because
+ // the QR code page will poll until it gets a
+ // fulfillment URL, but we decided that the
+ // fulfillment URL should only be returned
+ // when the client is authenticated.
+ // (Otherwise, guessing the order ID might leak the
+ // fulfillment URL).
+ if (!authOk) {
+ return respForbidden(req);
+ }
+
+ if (!!req.sessionId && req.sessionId !== ord.lastPaidSessionId) {
+ if (!!ord.fulfillmentUrl) {
+ const alreadyPaidOrd = findAlreadyPaid(
+ mos,
+ req.sessionId,
+ ord.fulfillmentUrl
+ );
+ if (!!alreadyPaidOrd) {
+ return respAlreadyPaid(req, alreadyPaidOrd);
+ }
+ }
+ }
+
+ if (ord.paid) {
+ return respPaid(req, ord);
+ }
+
+ return respUnpaid(req, ord);
+}
+
+function respNotFound(req: Req): Resp {
+ return {
+ contentType: req.accept,
+ httpStatus: "404 Not Found",
+ responseType: "TalerError",
+ };
+}
+
+function respForbidden(req: Req): Resp {
+ return {
+ contentType: req.accept,
+ httpStatus: "403 Forbidden",
+ responseType: "TalerError",
+ };
+}
+
+function respAlreadyPaid(req: Req, alreadyPaidOrd: MerchantOrderInfo): Resp {
+ // This could be called with an empty fulfillment URL, but that doens't
+ // really make sense for the client's perspective.
+ if (req.accept === "html") {
+ return {
+ httpStatus: "302 Found",
+ contentType: "html",
+ redirectLocation: alreadyPaidOrd.fulfillmentUrl,
+ responseType: "empty",
+ };
+ }
+ return {
+ httpStatus: "402 PaymentRequired",
+ contentType: "json",
+ responseType: "StatusUnpaidResponse",
+ response: {
+ fulfillment_url: alreadyPaidOrd.fulfillmentUrl,
+ already_paid_order_id: alreadyPaidOrd.orderId,
+ },
+ };
+}
+
+function respGoto(req: Req, publicReorderUrl: string): Resp {
+ if (req.accept === "html") {
+ return {
+ httpStatus: "302 Found",
+ contentType: "html",
+ redirectLocation: publicReorderUrl,
+ responseType: "empty",
+ };
+ }
+ return {
+ httpStatus: "202 Accepted",
+ contentType: "json",
+ responseType: "StatusGotoResponse",
+ response: {
+ public_reorder_url: publicReorderUrl,
+ },
+ };
+}
+
+function respUnpaid(req: Req, ord: MerchantOrderInfo): Resp {
+ if (req.accept === "html") {
+ return {
+ httpStatus: "402 Payment Required",
+ contentType: "html",
+ responseType: "StatusUnpaidResponse",
+ // This must include the claim token. The same taler://
+ // URI should also be shown as the QR code.
+ talerHeader: "taler://pay/...",
+ };
+ }
+ return {
+ httpStatus: "402 Payment Required",
+ contentType: "json",
+ responseType: "StatusUnpaidResponse",
+ response: {
+ // Required for repurchase detection
+ fulfillmentUrl: ord.fulfillmentUrl,
+ },
+ };
+}
+
+function respPaid(req: Req, ord: MerchantOrderInfo): Resp {
+ if (req.accept === "html") {
+ if (ord.refundPending) {
+ return {
+ httpStatus: "402 Payment Required",
+ contentType: "html",
+ responseType: "QRCodeRefundPage",
+ talerHeader: "taler://refund/...",
+ };
+ }
+ // We do not redirect here. Only
+ // the JS on the QR code page automatically redirects.
+ // Without JS, the user has to manually click through to
+ // the fulfillment URL.
+ return {
+ httpStatus: "200 OK",
+ contentType: "html",
+ responseType: "OrderStatusHtmlPage",
+ };
+ }
+ return {
+ httpStatus: "200 OK",
+ contentType: "json",
+ responseType: "StatusPaidResponse",
+ response: {
+ fulfillmentUrl: ord.fulfillmentUrl,
+ },
+ };
+}
+
+// Helper to find an already paid order ID.
+function findAlreadyPaid(
+ mos: MerchantOrderStore,
+ sessionId: string,
+ fulfillmentUrl: string
+): MerchantOrderInfo | undefined {
+ for (const orderId of Object.keys(mos)) {
+ if (
+ mos[orderId].lastPaidSessionId === sessionId &&
+ mos[orderId].fulfillmentUrl === fulfillmentUrl
+ ) {
+ return mos[orderId];
+ }
+ }
+ return undefined;
+}
diff --git a/taler-auditor-manual.rst b/taler-auditor-manual.rst
index 6a7a302..58dc946 100644
--- a/taler-auditor-manual.rst
+++ b/taler-auditor-manual.rst
@@ -332,7 +332,7 @@ The ``helper`` user that is used to download information from the exchange
needs to know details about the exchange. Similarly, the ``offline`` user
needs to check signatures signed with the exchange's offline key. Hence, you
need to obtain the ``MASTER_PUBLIC_KEY`` from the exchange operator (they need
-to run `taler-exchange-offline setup`) and the REST endpoint of the exchange
+to run ``taler-exchange-offline setup``) and the REST endpoint of the exchange
and configure these:
.. code-block:: console
@@ -370,7 +370,7 @@ is to extract the public key:
This public key must then be provided in the configuration file
of the ``auditor`` user in the ``[auditor]]`` configuration section:
-- ``PUBLIC_KEY``: Public key of the auditor, in Base32 encoding.
+- ``PUBLIC_KEY``: Public key of the auditor, in Base32 encoding.
Set from value printed by ``taler-auditor-offline setup``.
You can set this configuration value using:
diff --git a/taler-exchange-manual.rst b/taler-exchange-manual.rst
index ef0d501..7054b3d 100644
--- a/taler-exchange-manual.rst
+++ b/taler-exchange-manual.rst
@@ -332,6 +332,10 @@ offline signing and the terms of service.
Sample configuration files for the HTTP reverse proxy can be found in
``/etc/taler-exchange/``.
+Installing the GNU Taler binary packages on Trisquel
+----------------------------------------------------
+
+.. include:: frags/installing-trisquel.rst
Installing the GNU Taler binary packages on Ubuntu
--------------------------------------------------
diff --git a/taler-exchange-setup-guide.rst b/taler-exchange-setup-guide.rst
new file mode 100644
index 0000000..d56a51b
--- /dev/null
+++ b/taler-exchange-setup-guide.rst
@@ -0,0 +1,987 @@
+..
+ This file is part of GNU Taler.
+
+ Copyright (C) 2021 Taler Systems SA
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Florian Dold
+
+GNU Taler Exchange Setup Guide
+##############################
+
+This setup guide walks a system administrator through all steps required to
+install an exchange and check that it is functional. For more background,
+please read the :doc:`Operator Manual <taler-exchange-manual>`.
+
+
+System Requirements
+===================
+
+This guide assumes that you are running Ubuntu 20.04 (Focal Fossa).
+
+We recommend the setup of offline signing keys to be done on a second machine that
+does not have Internet access.
+
+In this guide's shell-session fragments, the command prompt shows two pieces
+of information:
+
+* Who is performing the command
+ (``$user`` vs ``root``, and ending character ``$`` vs ``#``).
+* Host where the command is supposed to be executed
+ (``exchange-offline`` vs ``exchange-online``).
+ It is possible to do the entire setup on one machine,
+ but we do not recommend this for security reasons.
+
+
+Before you start
+================
+
+To deploy this with a real bank, you need:
+
+* IBAN of the bank account to use
+* BIC of the bank
+* EBICS host, user and partner IDs
+
+Information to write down during the installation:
+
+* LibEuFin Nexus superuser password
+* Taler facade base URL
+* exchange Nexus username and password
+
+
+
+Installation
+============
+
+We assume that the system is a minimal installation of Ubuntu 20.04 LTS.
+Ideally, you should have two hosts, ``exchange-online`` and
+``exchange-offline``. It is also possible to run the HTTPS nginx server or
+the PostgreSQL database on yet another host, but in these instructions we will
+assume that only two hosts are used. Alas, the instructions will also work if
+you run everything on one system, but then you have the security drawback of
+not keeping the high-security private keys disconnected from the Internet.
+
+To install the exchange, first make sure that your system is up-to-date
+and that the ``gnupg`` package has been installed.
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# apt-get update
+ [root@exchange-online]# apt-get upgrade
+
+Next, add the ``focal-fossa`` apt repository provided by Taler Systems S.A. to
+your package sources:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# cat > /etc/apt/sources.list.d/taler.list <<EOF
+ deb https://deb.taler.net/apt/ubuntu focal-fossa main
+ EOF
+ [root@exchange-online]# dpkg --remove-architecture i386
+
+The second command is optional. It tells ``apt-get update`` to not
+bother with the ``i386`` architecture (thus avoiding a warning, later).
+If you use this, make sure you do not use any 32-bit applications.
+
+Before installing Taler packages, you need to add the Taler Systems S.A. package
+signing key to your list of trusted keys and update the package index:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# wget -O - https://taler.net/taler-systems.gpg.key | apt-key add -
+ [root@exchange-online]# apt-get update
+
+Finally, the required packages can be installed:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# apt-get install -y nginx postgresql
+ [root@exchange-online]# apt-get install -y taler-exchange taler-exchange-offline
+ [root@exchange-online]# apt-get install -y taler-wallet-cli
+
+By default, all installed services will be disabled. You need to enable
+and start them later.
+
+While ``taler-merchant`` and ``taler-wallet`` are not required to operate an
+exchange, they are useful for testing. When asked about using dbconfig to configure
+the merchant's database, select ``yes``.
+
+
+Configuration Basics
+====================
+
+The configuration for all Taler components uses a single configuration file
+as entry point: ``/etc/taler/taler.conf``.
+
+System defaults are automatically loaded from files in
+``/usr/share/taler/config.d``. These default files should never be modified.
+
+The default configuration ``taler.conf`` configuration file also includes all
+configuration files in ``/etc/taler/conf.d``. The settings from files in
+``conf.d`` are only relevant to particular components of Taler, while
+``taler.conf`` contains settings that affect all components.
+
+
+The directory ``/etc/taler/secrets`` contains configuration file snippets with
+values that should only be readable to certain users. They are included with the ``@inline-secret@``
+directive and should end with ``.secret.conf``.
+
+To view the entire configuration annotated with the source of each configuration option, you
+can use the ``taler-config`` helper:
+
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-config --diagnostics
+ < ... annotated, full configuration ... >
+
+.. warning::
+
+ While ``taler-config`` also supports rewriting configuration files, we strongly
+ recommend to edit configuration files manually, as ``taler-config`` does not
+ preserve comments and, by default, rewrites ``/etc/taler/taler.conf``.
+
+
+Services, users, groups and file system hierarchy
+=================================================
+
+The *taler-exchange-httpd* package will create several system users
+to compartmentalize different parts of the system:
+
+* ``taler-exchange-httpd``: runs the HTTP daemon with the core business logic.
+* ``taler-exchange-secmod-rsa``: manages the RSA private online signing keys.
+* ``taler-exchange-secmod-eddsa``: manages the EdDSA private online signing keys.
+* ``taler-exchange-closer``: closes idle reserves by triggering wire transfers that refund the originator.
+* ``taler-exchange-aggregator``: aggregates deposits into larger wire transfer requests.
+* ``taler-exchange-wire``: performs wire transfers with the bank (via LibEuFin/Nexus).
+* ``postgres``: runs the Postgres database (from *postgres* package).
+* ``www-data``: runs the frontend HTTPS service with the TLS keys (from *nginx* package).
+
+.. note::
+
+ The *taler-merchant-httpd* package additionally creates a taler-merchant-httpd user
+ to runs the HTTP daemon with the merchant business logic.
+
+
+The exchange setup uses the following system groups:
+
+* ``taler-exchange-db``: group for all Taler users with direct database access, specifically taler-exchange-httpd, taler-exchange-wire, taler-exchange-closer and taler-exchange-aggregator.
+* ``taler-exchange-secmod``: group for processes with access to online signing keys; this group must have three users: taler-exchange-secmod-rsa, taler-exchange-secmod-eddsa and taler-exchange-httpd.
+* ``taler-exchange-offline``: group for the access to the offline private key (only used on the offline host and not used on the online system).
+
+
+
+The package will deploy systemd service files in
+``/usr/lib/systemd/system/`` for the various components:
+
+* ``taler-exchange-aggregator.service``: service that schedules wire transfers
+ which combine multiple deposits to the same merchant.
+* ``taler-exchange-closer.service``: service that watches for reserves that have been abandoned and schedules wire transfers to send the money back to the originator.
+* ``taler-exchange-httpd.service``: main Taler exchange logic with the public REST API.
+* ``taler-exchange-httpd.socket``: systemd socket activation for the Taler exchange HTTP daemon.
+* ``taler-exchange-secmod-eddsa.service``: software security module for making EdDSA signatures.
+* ``taler-exchange-secmod-rsa.service``: software security module for making RSA signatures.
+* ``taler-exchange-transfer.service``: service that triggers outgoing wire transfers (pays merchants).
+* ``taler-exchange-wirewatch.service``: service that watches for incoming wire transfers (first step of withdraw).
+* ``taler-exchange.target``: Main target for the Taler exchange to be operational.
+
+
+The deployment creates the following key locations in the system:
+
+* ``/etc/taler/``: configuration files.
+* ``/run/taler/``: contains the UNIX domain sockets for inter-process communication (IPC).
+* ``/var/lib/taler/``: serves as the $HOME for all Taler users and contains sub-directories
+ with the private keys; which keys are stored here depends on the host:
+
+ * online system: exchange-secmod-eddsa and exchange-secmod-rsa keys.
+ * offline system: exchange-offline keys.
+
+
+Setup Linting
+=============
+
+The ``taler-wallet-cli`` package comes with a experimental tool that runs various
+checks on the current GNU Taler exchange deployment:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-wallet-cli deployment lint-exchange
+
+You can optionally pass the ``--debug`` option to get more verbose output, and
+``--continue`` to continue with further checks even though a previous one has
+failed.
+
+Basic Setup: Currency and Denominations
+=======================================
+
+A Taler exchange only supports a single currency. The currency
+and the smallest currency unit supported by the bank system
+must be specified in ``/etc/taler/taler.conf``.
+
+.. code-block:: ini
+ :caption: /etc/taler/taler.conf
+
+ [taler]
+ CURRENCY = EUR
+ CURRENCY_ROUND_UNIT = EUR:0.01
+
+ # ... rest of file ...
+
+.. warning::
+
+ When editing ``/etc/taler/taler.conf``, take care to not accidentally remove
+ the @inline-matching@ directive to include the configuration files in ``conf.d``.
+
+Next, the electronic cash denominations that the exchange offers must be
+specified. The ``taler-wallet-cli`` has a helper command that generates a
+reasonable denomination structure.
+
+.. code-block:: shell-session
+
+ taler-wallet-cli deployment gen-coin-config --min-amount EUR:0.01 --max-amount EUR:100 > /etc/taler/conf.d/exchange-coins.conf
+
+You can manually review and edit the generated configuration file. The main
+change that is possibly required is updating the various fees.
+
+
+Wire Gateway Setup
+==================
+
+The Taler Wire Gateway is an API that connects the Taler exchange to
+the underlying core banking system.
+
+LibEuFin is an implementation of the Wire Gateway API for the EBICS protocol.
+This section will walk through (1) installing and configuring LibEuFin and
+(2) connecting the Taler Exchange to LibEuFin.
+
+.. note::
+
+ If you do not have a bank account with EBICS but want to test these instructions,
+ you can use the EBICS sandbox as described in the
+ :doc:`LibEuFin Tutorial <libeufin/nexus-tutorial>`.
+
+
+Installation and Basic Configuration
+------------------------------------
+
+First, install the ``libeufin`` package. This can be done on the ``exchange-online``
+machine or a different one.
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# apt-get install -y libeufin
+
+The main component of LibEuFin is called the Nexus. It implements a Web
+service that provides a JSON abstraction layer to access bank accounts.
+
+The Nexus currently uses an sqlite3 database as storage by default.
+We currently recommend to stick with this default. In future
+versions, there will be a migration path to a PostgreSQL database.
+
+The HTTP port and database connection string can be edited in the configuration:
+
+.. code-block:: ini
+ :caption: /etc/libeufin/nexus.env
+
+ LIBEUFIN_NEXUS_PORT=5017
+ LIBEUFIN_NEXUS_DB_CONNECTION=jdbc:sqlite:/var/lib/libeufin/nexus/nexus-db.sqlite3
+
+After configuring the database, you can start the service.
+The database is initialized automatically.
+
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# systemctl enable libeufin-nexus
+ [root@exchange-online]# systemctl start libeufin-nexus
+
+You can now create a superuser account. The command to
+create the superuser needs direct database access, thus
+the configuration file is sourced first, and the relevant
+environment variable is exported.
+
+.. code-block:: console
+
+ [root@exchange-online]# source /etc/libeufin/nexus.env
+ [root@exchange-online]# export LIBEUFIN_NEXUS_DB_CONNECTION
+ [root@exchange-online]# NEXUS_ADMIN_PW=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13)
+ [root@exchange-online]# libeufin-nexus superuser admin --password $NEXUS_ADMIN_PW
+
+If you omit ``--password $NEXUS_ADMIN_PW``, you will interactively be asked for a password.
+For simplicity, a superuser can as well act as a normal user, but an API
+to create less privileged users is offered.
+
+.. note::
+
+ User and permissions management in LibEuFin is still under development.
+ In particular, permissions for non-superusers are very limited at the moment.
+
+
+Connecting Nexus with an EBICS account
+--------------------------------------
+
+The command line interface of the LibEuFin Nexus needs the following three
+values to be defined in the environment: ``LIBEUFIN_NEXUS_URL``,
+``LIBEUFIN_NEXUS_USERNAME``, and ``LIBEUFIN_NEXUS_PASSWORD``. In this example,
+``LIBEUFIN_NEXUS_USERNAME`` should be set to ``admin``, and
+``LIBEUFIN_NEXUS_PASSWORD`` to the value hold in ``NEXUS_ADMIN_PW`` from the
+previous step (the ``libeufin-nexus superuser`` command). The
+``LIBEUFIN_NEXUS_URL`` could be given as ``http://localhost:5017/``.
+
+Next, we create a EBICS *bank connection* that Nexus can use to communicate with the bank.
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ new-ebics-connection \
+ --ebics-url $EBICS_BASE_URL \
+ --host-id $EBICS_HOST_ID \
+ --partner-id $EBICS_PARTNER_ID \
+ --ebics-user-id $EBICS_USER_ID \
+ $CONNECTION_NAME
+
+If this step executes correctly, Nexus will have created all the cryptographic
+material that is needed on the client side; in this EBICS example, it created
+the signature and identification keys. It is therefore advisable to *make
+a backup copy* of such keys.
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ export-backup \
+ --passphrase $SECRET \
+ --output-file $BACKUP_FILE \
+ $CONNECTION_NAME
+
+At this point, Nexus needs to both communicate its keys to the bank, and
+download the bank's keys. This syncronization happens through the INI, HIA, and
+finally, HPB message types.
+
+After the electronic synchronization, the subscriber must confirm their keys
+by sending a physical mail to the bank. The following command helps generating
+such letter:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli connections get-key-letter $CONNECTION_NAME out.pdf
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ connect \
+ $CONNECTION_NAME
+
+..
+ FIXME: Maybe is not 100% clear that 'connecting' means exchanging keys
+ wiht the bank?
+
+Once the connection is synchronized, Nexus needs to import locally the data
+corresponding to the bank accounts offered by the bank connection just made.
+The command below downloads the list of the bank accounts offered by ``$CONNECTION_NAME``.
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ download-bank-accounts \
+ $CONNECTION_NAME
+
+It is now possible to list the accounts offered by the connection.
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ list-offered-bank-accounts \
+ $CONNECTION_NAME
+
+.. note::
+
+ The ``nexusBankAccountId`` field should at this step be ``null``,
+ as we have not yet imported the bank account and thus the account
+ does not yet have a local name.
+
+Nexus now needs an explicit import of the accounts it should manage. This
+step is needed to let the user pick a custom name for such accounts.
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli \
+ connections \
+ import-bank-account \
+ --offered-account-id testacct01 \
+ --nexus-bank-account-id $LOCAL_ACCOUNT_NAME \
+ $CONNECTION_NAME
+
+Once a Nexus user imported a bank account (``$LOCAL_ACCOUNT_NAME``)
+under a certain connection (``$CONNECTION_NAME``), it is possible
+to accomplish the usual operations for any bank account: asking for the
+list of transactions, and making a payment.
+
+Testing: Requesting the transaction history
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The LibEuFin Nexus keeps a local copy of the bank account's transaction
+history. Before querying transactions locally, it is necessary
+to request transactions for the bank account via the bank connection.
+
+This command asks Nexus to download the latest transaction reports/statements
+through the bank connection:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli accounts fetch-transactions $LOCAL_ACCOUNT_NAME
+
+.. note::
+
+ By default, the latest available transactions are fetched. It is also
+ possible to specify a custom date range (or even all available transactions)
+ and the type of transactions to fetch (inter-day statements or intra-day
+ reports).
+
+..
+ FIXME: Possibly the date range filter is still missing, see #6243.
+
+Once Nexus has stored all the information in the database, the
+client can ask to actually see the transactions:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli accounts transactions $LOCAL_ACCOUNT_NAME
+
+Testing: Making payments
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Payments pass through two phases: preparation and submission. The preparation
+phase assigns the payment initiation a unique ID, which prevents accidental
+double submissions of payments in case of network failures or other
+disruptions.
+
+
+The following command prepares a payment:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli accounts prepare-payment \
+ --creditor-iban=$IBAN_TO_SEND_MONEY_TO \
+ --creditor-bic=$BIC_TO_SEND_MONEY_TO \
+ --creditor-name=$CREDITOR_NAME \
+ --payment-amount=$AMOUNT \
+ --payment-subject=$SUBJECT \
+ $LOCAL_ACCOUNT_NAME
+
+Note: the ``$AMOUNT`` value needs the format ``X.Y:CURRENCY``; for example
+``EUR:10``, or ``EUR:1.01``.
+
+The previous command should return a value (``$UUID``) that uniquely
+identifies the prepared payment in the Nexus system. That is needed
+in the next step, to **send the payment instructions to the bank**:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli accounts submit-payment \
+ --payment-uuid $UUID \
+ $LOCAL_ACCOUNT_NAME
+
+Automatic scheduling
+~~~~~~~~~~~~~~~~~~~~
+
+With an EBICS bank connection, the LibEuFin Nexus needs to regularly query for
+new transactions and (re-)submit prepared payments.
+
+It is possible to schedule these tasks via an external task scheduler such as
+cron(8). However, the nexus also has an internal task scheduling mechanism for
+accounts.
+
+
+The following three commands create a schedule for submitting payments hourly,
+fetching transactions (intra-day reports) every 5 minutes, and (inter-day statements)
+once at 11pm every day:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli accounts task-schedule $LOCAL_ACCOUNT_NAME \
+ --task-type="submit" \
+ --task-name='submit-payments-hourly' \
+ --task-cronspec='0 0 *'
+
+ [root@exchange-online]# libeufin-cli accounts task-schedule $LOCAL_ACCOUNT_NAME \
+ --task-type="fetch" \
+ --task-name='fetch-5min' \
+ --task-cronspec='0 */5 *' \
+ --task-param-level=report \
+ --task-param-range-type=latest
+
+ [root@exchange-online]# libeufin-cli accounts task-schedule $LOCAL_ACCOUNT_NAME \
+ --task-type="fetch" \
+ --task-name='fetch-daily' \
+ --task-cronspec='0 0 23' \
+ --task-param-level=statement \
+ --task-param-range-type=latest
+
+The cronspec has the following format, which is slightly non-standard due to
+the ``SECONDS`` field
+
+.. code-block:: none
+
+ SECONDS MINUTES HOURS DAY-OF-MONTH[optional] MONTH[optional] DAY-OF-WEEK[optional]
+
+
+Creating a Taler facade
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Facades are additional abstraction layers that can serve
+specific purposes. For example, one application might need
+a filtered version of the transaction history, or it might
+want to refuse payments that do not conform to certain rules.
+
+At this moment, only the *Taler facade type* is implemented
+in the Nexus, and the command below instantiates one under a
+existing bank account / connection pair. You can freely
+assign an identifier for the ``$FACADE_NAME`` below:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli facades new-taler-wire-gateway-facade \
+ --currency EUR \
+ --facade-name $FACADE_NAME \
+ $CONNECTION_NAME \
+ $LOCAL_ACCOUNT_NAME
+
+At this point, the additional :doc:`taler-wire-gateway API <core/api-wire>`
+becomes offered by the Nexus. The purpose is to let a Taler exchange rely on
+Nexus to manage its bank account.
+
+The base URL of the facade that can be used by the Taler exchange
+as the Taler Wire Gateway base URL can be seen by listing the facades:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli facades list
+
+Managing Permissions and Users
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This guide has so far assumed that a superuser is accessing the LibEuFin Nexus.
+However, it is advisable that the Nexus is accessed with users that only have a
+minimal set of permissions.
+
+The Nexus currently only has support for giving non-superusers access to Taler
+wire gateway facades.
+
+To create a new user, use the ``users`` subcommand of the CLI:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli users list
+ # [ ... shows available users ... ]
+
+ [root@exchange-online]# libeufin-cli users create $USERNAME
+ # [ ... will prompt for password ... ]
+
+Permissions are managed with the ``permissions`` subcommand.
+The following commands grant permissions to view the transaction history
+and create payment initiations with a Taler wire gateway facade:
+
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli permissions grant \
+ user $USERNAME \
+ facade $FACADENAME \
+ facade.talerwiregateway.history
+
+ [root@exchange-online]# libeufin-cli permissions grant \
+ user $USERNAME \
+ facade $FACADENAME \
+ facade.talerwiregateway.transfer
+
+..
+ FIXME: The two commands above output a empty JSON object
+ when successful. Possibly, we should suppress that (just like
+ the other commands do).
+
+The list of all granted permissions can be reviewed:
+
+.. code-block:: console
+
+ [root@exchange-online]# libeufin-cli permissions list
+
+
+Exchange Wire Configuration
+---------------------------
+
+The exchange must be configured with the right settings to
+access the Taler Wire Gateway. An exchange can be configured
+to use multiple bank accounts by using multiple Wire Gateways.
+Typically only one Wire Gateway is used.
+
+A Taler Wire Gateway is configured in a configuration section that follows the
+pattern ``exchange-account-$id``, where ``$id`` is an internal identifier for
+the bank account accessed by the exchange. The basic information for an account should
+be put in ``/etc/taler/conf.d/exchange-business.conf``.
+The secret credentials to access the Taler Wire Gateway API should
+be put into a corresponding ``exchange-accountcredentials-$id`` section
+in ``/etc/taler/secrets/exchange-accountcredentials.conf``.
+The latter file
+should already be only readable for the ``taler-exchange-wire`` user. Other
+exchange processes should not have access to this information.
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-business.conf
+
+ [exchange-account-1]
+ enable_credit = yes
+ enable_debit = yes
+
+ # Account identifier in the form of an RFC-8905 payto:// URI.
+ # For SEPA, looks like payto://sepa/$IBAN?receiver-name=$NAME
+ # Make sure to URL-encode spaces in $NAME!
+ payto_uri =
+
+ @inline-secret@ exchange-accountcredentials-1 ../secrets/exchange-accountcredentials.secret.conf
+
+
+.. code-block:: ini
+ :caption: /etc/taler/secrets/exchange-accountcredentials.secret.conf
+
+ [exchange-accountcredentials-1]
+
+ # LibEuFin expects basic auth.
+ wire_gateway_auth_method = basic
+
+ # Username and password set in LibEuFin.
+ username = ...
+ password = ...
+
+ # Base URL of the wire gateway set up with LibEuFin.
+ wire_gateway_url = ...
+
+
+The Wire Gateway configuration can be tested with the following command:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-exchange-wire-gateway-client \
+ --section exchange-accountcredentials-1 --debit-history
+ [root@exchange-online]# taler-exchange-wire-gateway-client \
+ --section exchange-accountcredentials-1 --credit-history
+
+
+
+Exchange Database Setup
+=======================
+
+The access credentials for the exchange's database are configured in
+``/etc/taler/secrets/exchange-db.secret.conf``. Currently, only PostgreSQL is
+supported as a database backend.
+
+The following users must have access to the exchange database:
+
+* taler-exchange-httpd
+* taler-exchange-wire
+* taler-exchange-aggregator
+* taler-exchange-closer
+
+These users are all in the taler-exchange-db group, and the
+``exchange-db.secret.conf`` should already be only readable by users in
+this group.
+
+To create a database for the Taler exchange on the local system, run:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# su - postgres
+ [postgres@exchange-online]# createuser taler-exchange-httpd
+ [postgres@exchange-online]# createuser taler-exchange-wire
+ [postgres@exchange-online]# createuser taler-exchange-aggregator
+ [postgres@exchange-online]# createuser taler-exchange-closer
+ [postgres@exchange-online]# createdb -O taler-exchange-httpd taler-exchange
+ [postgres@exchange-online]# exit
+
+This will create a ``taler-exchange`` database owned by the
+``taler-exchange-httpd`` user. We will use that user later to perform
+database maintenance operations.
+
+
+Assuming the above database setup, the database credentials to configure
+in the configuration file would simply be:
+
+.. code-block:: ini
+ :caption: /etc/taler/secrets/exchange-db.secret.conf
+
+ [exchangedb-postgres]
+ CONFIG=postgres:///taler-exchange
+
+
+If the database is run on a different host, please follow the instructions
+from the PostgreSQL manual for configuring remote access.
+
+After configuring the database credentials, the exchange database needs
+to be initialized with the following command:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# sudo -u taler-exchange-httpd taler-exchange-dbinit
+
+Finally we need to grant the other accounts limited access:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# sudo -u taler-exchange-httpd bash
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT SELECT,INSERT,UPDATE ON ALL TABLES IN SCHEMA public TO "taler-exchange-aggregator";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT SELECT,INSERT,UPDATE ON ALL TABLES IN SCHEMA public TO "taler-exchange-closer";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT SELECT,INSERT,UPDATE ON ALL TABLES IN SCHEMA public TO "taler-exchange-wire";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO "taler-exchange-aggregator";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO "taler-exchange-closer";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# echo 'GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO "taler-exchange-wire";' \
+ | psql taler-exchange
+ [taler-exchange-httpd@exchange-online]# exit
+
+.. note::
+
+ The above instructions for changing database permissions only work *after*
+ having initialized the database with ``taler-exchange-dbinit``, as
+ the tables to exist before permissions can be granted on them. The
+ ``taler-exchange-dbinit`` tool cannot setup these permissions, as it
+ does not know which users will be used for which processes.
+
+
+Offline Signing Setup
+=====================
+
+The offline signing keys of the exchange should be stored on a different machine.
+The responsibilities of this offline signing machine are:
+
+* Generation of the exchange's offline master signing key.
+* Secure storage of the exchange's offline master signing key.
+* Generation of certificates (signed with the offline master signing key) that will be imported by the exchange.
+
+
+.. code-block:: shell-session
+
+ [root@exchange-offline]# sudo -u taler-exchange-offline taler-exchange-offline setup
+ < ... prints the exchange master public key >
+
+The public key printed as the output of this command must be put into the configuration
+of the online machine:
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-business.conf
+
+ [exchange]
+ MASTER_PUBLIC_KEY = YE6Q6TR1ED...
+
+ # ... rest of file ...
+
+
+Exchange Web service / API Setup
+================================
+
+By default, the ``taler-exchange-httpd`` service listens for HTTP connections
+on a UNIX domain socket. To make the service publicly available, a reverse
+proxy such as nginx should be used. We strongly recommend to configure nginx
+to use TLS.
+
+The public URL that the exchange will be served under should
+be put in ``/etc/taler/conf.d/exchange-business.conf`` configuration file.
+
+.. code-block:: ini
+ :caption: /etc/taler/conf.d/exchange-business.conf
+
+ [exchange]
+ BASE_URL = https://example.com/
+
+ # ... rest of file ...
+
+The ``taler-exchange`` package ships with a sample configuration that can be
+enabled in nginx:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# vim /etc/nginx/sites-available/taler-exchange
+ < ... customize configuration ... >
+ [root@exchange-online]# ln -s /etc/nginx/sites-available/taler-exchange \
+ /etc/nginx/sites-enabled/taler-exchange
+ [root@exchange-online]# systemctl reload nginx
+
+
+The exchange HTTP service can now be started:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# systemctl start taler-exchange.target
+
+
+.. note::
+
+ At this point, the exchange service is not yet fully operational.
+
+
+To check whether the exchange is running correctly under the advertized
+base URL, run:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# export BASE_URL=$(taler-config -s exchange -o base_url)
+ [root@exchange-online]# wget ${BASE_URL}management/keys
+
+The request might take some time to complete on slow machines, because
+a lot of key material will be generated.
+
+Offline Signing Procedure
+=========================
+
+The exchange HTTP service should be running now, but is not yet completely
+operational. To make the exchange HTTP service operational, the following
+steps involving the offline signing machine must be completed:
+
+1. The public keys of various online keys used by the exchange service are exported
+ via a management HTTP API.
+2. The offline signing system validates this request and signs it.
+ Additionally, the offline signing system signs policy messages
+ to configure the exchange's bank accounts and associated fees.
+3. The messages generated by the offline signing system are uploaded
+ via the management API of the exchange HTTP service.
+
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-exchange-offline \
+ download > sig-request.json
+
+ [root@exchange-offline]# taler-exchange-offline \
+ sign < sig-request.json > sig-response.json
+ [root@exchange-offline]# taler-exchange-offline \
+ enable-account payto://sepa/$IBAN?receiver-name=$NAME > acct-response.json
+ [root@exchange-offline]# taler-exchange-offline \
+ wire-fee 2021 sepa EUR:0 EUR:0 > fee-response.json
+ [root@exchange-online]# taler-exchange-offline upload < sig-response.json
+ [root@exchange-online]# taler-exchange-offline upload < acct-response.json
+ [root@exchange-online]# taler-exchange-offline upload < fee-response.json
+
+
+
+
+Testing and Troubleshooting
+===========================
+
+The following shell session illustrates how the wallet can be used to withdraw
+electronic cash from the exchange and subsequently spend it. For these steps,
+a merchant backend is not required, as the wallet acts as a merchant.
+
+
+.. code-block:: shell-session
+
+ # This will now output a payto URI that money needs to be sent to in order to allow withdrawal
+ # of taler coins.
+ $ taler-wallet-cli advanced withdraw-manually --exchange $EXCHANGE_URL --amount EUR:10.50
+
+
+Show the status of the manual withdrawal operation.
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli transactions
+
+At this point, a bank transfer to the exchange's bank account
+needs to be made with the correct subject / remittance information
+as instructed by the wallet after the first step. With the
+above configuration, it should take about 5 minutes after the
+wire transfer for the incoming transfer to be observed by the
+Nexus.
+
+Run the following command to check whether the exchange received
+an incoming bank transfer:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-exchange-wire-gateway-client \
+ --section exchange-accountcredentials-1 --credit-history
+
+Once the transfer has been made, try completing the withdrawal
+using:
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli run-pending
+
+Afterwards, check the status of transactions and show the
+current wallet balance:
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli transactions
+ $ taler-wallet-cli balance
+
+
+Now, we can directly deposit coins via the exchange into a target
+account. (Usually, a payment is made via a merchant. The wallet
+provides this functionality for testing.)
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli deposit create EUR:5 \
+ payto://sepa/$IBAN?receiver-name=Name
+ $ taler-wallet-cli run-pending
+
+
+Check if this transaction was successful (from the perspective
+of the wallet):
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli transactions
+
+If the transaction failed, fix any open issue(s) with the exchange and
+run the "run-pending" command.
+
+The wallet can also track if the exchange wired the money to the merchant
+account. The "deposit group id" can be found in the output of the
+transactions list.
+
+.. code-block:: shell-session
+
+ $ taler-wallet-cli deposit track $DEPOSIT_GROUP_ID
+
+You can also check using the exchange-tools whether the exchange sent
+the an outgoing transfer:
+
+.. code-block:: shell-session
+
+ [root@exchange-online]# taler-exchange-wire-gateway-client \
+ --section exchange-accountcredentials-1 --debit-history
+
+After enough time has passed, the money should arrive at the specified IBAN.
+
+
+FIXMEs
+======
+
+* We should have some summary with the inventory of services that should be
+ running. Systemd by default doesn't show this nicely. Maybe suggest running
+ "systemd list-dependencies taler-exchange.target"?
+* When multiple TWGs are configured, which one will be used by the taler-exchange-transfer? CG: ALL!
+
+ * FD: Sure, for incoming transactions. But how does taler-exchange-transfer decide which TWG to use for an outgoing transaction?
+
+* What happens when the TWG doesn't like one particular outgoing transaction?
+ How to recover from that as a sysadmin when it happens in practice?
diff --git a/taler-mcig.rst b/taler-mcig.rst
index f93d3d1..d1247ae 100644
--- a/taler-mcig.rst
+++ b/taler-mcig.rst
@@ -67,20 +67,21 @@ This guide assumes you and the customer agree to use the Taler payment system.
At this point, you generate a *contract* and present it to the customer for
authorization.
The contract includes:
+
- the total amount due;
- a short summary;
- a *fulfillment URI*;
-- the *duration* of the offer
- (how long the customer has to authorize before timeout);
-- (optional) an itemized product list, with
+- the *duration* of the offer (how long the customer has to authorize before timeout);
+- (optional) an itemized product list, with:
+
- (optional) some kind of identification for the selected product(s);
+
- (optional) applicable taxes and fee limits;
- (optional) an order ID (if omitted, the backend will auto-generate one);
- (optional) information which details are *forgettable*;
- (optional) a *claim token* that the customer can use later;
- (optional) information on the *refund deadline*;
-- (optional) information on the the *auto-refund period* (how long does
- the wallet check for refunds without user prompting for it).
+- (optional) information on the the *auto-refund period* (how long does the wallet check for refunds without user prompting for it).
If the customer does nothing (timeout / the contract expires),
the merchant backend automatically *unlocks* the product(s),
@@ -151,24 +152,24 @@ are demonstrated in the next section.
which you may choose to pay or to pass on to the customer.
This can be configured to a maximum amount, per order.
- You can set ``default_max_deposit_fee`` in :http:post:`/private/instances`,
+ You can set ``default_max_deposit_fee`` in :http:post:`/management/instances`,
or override the default by setting ``max_fee`` when creating an order.
There is also the *wire fee* (see step 6, above),
which you may choose to pay or to pass on to the customer.
- You can set ``default_max_wire_fee`` in :http:post:`/private/instances`,
+ You can set ``default_max_wire_fee`` in :http:post:`/management/instances`,
and ``max_wire_fee`` in the contract.
If unspecified, the default value is zero (meaning you bear the entire fee).
You can *amortize* the wire fee across a number of customers
- by setting ``default_wire_fee_amortization`` in :http:post:`/private/instances`,
+ by setting ``default_wire_fee_amortization`` in :http:post:`/management/instances`,
and ``wire_fee_amortization`` in the contract.
This is the number of customer transactions over which you expect to
amortize wire fees on average.
If unspecified, the default value is one.
- .. Note:: :http:post:`/private/instances` must be done at
+ .. Note:: :http:post:`/management/instances` must be done at
instance-setup time (prior to any purchase).
**forgettable customer details**
@@ -287,7 +288,7 @@ above => refer to other guide, but of course specify how we can
override defaults from instance setup per-order.)
-M: :http:post:`/private/instances`
+M: :http:post:`/management/instances`
.. code-block:: javascript
@@ -400,12 +401,11 @@ M: :http:post:`/instances/default/private/orders`
}
Notes:
+
- There is no ``refund_delay`` field (no refunds possible).
-- We show the ``create_token`` field with value ``true`` even though
- that is the default (for illustrative purposes).
+- We show the ``create_token`` field with value ``true`` even though that is the default (for illustrative purposes).
- The ``order`` value is actually a ``MinimalOrderDetail`` object.
-- The ``fulfillment_URI`` value includes the product ID and the literal
- string ``${ORDER_ID}``, to be replaced by the backend-generated order ID.
+- The ``fulfillment_URI`` value includes the product ID and the literal string ``${ORDER_ID}``, to be replaced by the backend-generated order ID.
The backend returns ``200 OK`` with the body:
@@ -436,10 +436,10 @@ W: :http:post:`/orders/G93420934823/claim`
}
Notes:
+
- The ``nonce`` value is a randomly-generated string.
- The POST endpoint includes the order ID ``G93420934823``.
-- The ``token`` value is the claim token ``TEUFHEFBQALK``
- received in the ``PostOrderResponse``.
+- The ``token`` value is the claim token ``TEUFHEFBQALK`` received in the ``PostOrderResponse``.
The backend returns ``200 OK`` with body:
@@ -483,6 +483,7 @@ The backend returns ``200 OK`` with body:
}
Notes:
+
- The backend determined both fees to be 0.015 KUDOS.
Because the amortization is 1 (one), both fees (processing and wire
transfer) are included in full.
@@ -535,6 +536,7 @@ W: :http:post:`/orders/G93420934823/pay`
}
Notes:
+
- There is no session ID in the ``PayRequest`` object.
- The total of the contribution is 8.0 + 2.0 = 10.0 KUDOS,
which is enough to cover the purchase price (9.900 KUDOS
diff --git a/taler-merchant-manual.rst b/taler-merchant-manual.rst
index f6b2e1a..b076684 100644
--- a/taler-merchant-manual.rst
+++ b/taler-merchant-manual.rst
@@ -110,10 +110,10 @@ merchant’s signing keys and bank account information are encapsulated within
the Taler backend.
A typical deployment will additionally include a full-blown Web server (like
-Apache or Nginx). Such a Web server would be responsible for TLS termination
-and access control to the ``/private/`` API endpoints of the merchant backend.
-Please carefully review the section on :ref:`Secure setup <Secure-setup>` before
-deploying a Taler merchant backend to production.
+Apache or Nginx). Such a Web server would be responsible for TLS termination and
+access control to the ``/private/`` and ``/management/`` API endpoints of the
+merchant backend. Please carefully review the section on :ref:`Secure setup
+<Secure-setup>` before deploying a Taler merchant backend to production.
Terminology
@@ -194,6 +194,13 @@ actors claiming orders is problematic (say because of limited stocks). The use
of claim tokens is optional, but if a claim token is used, it must be provided
to the wallet as part of the order URI.
+Additionally, when stocks are limited, you can configure Taler to
+set a *product lock* on items (say, while composing the shopping cart).
+These locks, as well as the *order lock* (when the order is complete),
+can be configured to auto-unlock at certain times.
+
+.. FIXME: Is "can be configured" correct? (Are there controls surfaced?)
+
A wallet may *pay* for a claimed order, at which point the order turns into
a (paid) contract. Orders have an expiration date after which the commercial
offer expires and any stock of products *locked* by the order is released,
@@ -234,7 +241,7 @@ Tipping
Taler does not only allow a Website to be paid, but also to make voluntary,
non-contractual payments to visitors, called *tips*. Such tips could be
-granted as a reward for filling in surveys or watching advertisements. For
+granted as a reward for filling in surveys or watching advertizements. For
tips, there is no contract, tips are always voluntary actions by the Web
site that do not arise from a contractual obligation. Before a Web site
can create tips, it must establish a reserve. Once a reserve has been
@@ -350,6 +357,12 @@ Installing the GNU Taler binary packages on Debian
.. include:: frags/apt-install-taler-merchant.rst
+Installing the GNU Taler binary packages on Trisquel
+----------------------------------------------------
+
+.. include:: frags/installing-trisquel.rst
+
+
Installing the GNU Taler binary packages on Ubuntu
--------------------------------------------------
@@ -368,10 +381,10 @@ Installing Taler on Debian GNU/Linux from source
.. index:: Stretch
.. index:: Debian
-Debian wheezy is too old and lacks most of the packages required.
-Debian jessie is better, but still lacks PostgreSQL 9.6.
+Debian Wheezy is too old and lacks most of the packages required.
+Debian Jessie is better, but still lacks PostgreSQL 9.6.
-On Debian stretch, only GNU libmicrohttpd needs to be compiled from
+On Debian Stretch, only GNU libmicrohttpd needs to be compiled from
source. To install dependencies on Debian stretch, run the following
commands:
@@ -415,12 +428,12 @@ For more recent versions of Debian, you should instead run:
postgresql-9.6 \
libmicrohttpd-dev
-Note that stretch requires ``libargon2-0-dev``,
+Note that Stretch requires ``libargon2-0-dev``,
while later versions of Debian require ``libargon2-dev``.
For the rest of the installation, follow the generic installation
instructions starting with the installation of libgnunetutil. Note that
-if you used the Debian stretch instructions above, you need to pass
+if you used the Debian Stretch instructions above, you need to pass
``--with-microhttpd=/usr/local/`` to all ``configure`` invocations.
@@ -498,6 +511,19 @@ To run the Taler backend on TCP port 8888, use:
$ taler-config -s MERCHANT -o SERVE -V TCP
$ taler-config -s MERCHANT -o PORT -V 8888
+.. note::
+
+ When using the Debian/Ubuntu packages, these options are already
+ configured in the ``/etc/taler/conf.d/merchant.conf`` configuration file.
+
+ If you need to change them, you should edit ``/etc/taler/merchant-overrides.conf``,
+ for example by passing ``-c /etc/taler/merchant-overrides.conf`` to the
+ ``taler-config`` commands above. By default, the Taler merchant
+ package when installed on Debian/Ubuntu will use a UNIX domain socket
+ at ``/run/taler/merchant-httpd/merchant-http.sock``. For the best possible
+ security, it is recommended to leave this in place and configure a reverse
+ proxy (nginx or Apache) as described below.
+
Currency
@@ -518,6 +544,15 @@ https://exchange.demo.taler.net/:
$ taler-config -s TALER -o CURRENCY -V KUDOS
+.. note::
+
+ When using the Debian/Ubuntu packages, these options should be
+ configured in the ``/etc/taler/taler.conf`` configuration file
+ (alternatively, you can also edit ``/etc/taler/merchant-overrides.conf``).
+ However, you must edit the ``taler.conf`` file manually and **must not**
+ use ``taler-config`` to do this, as that would inline the include
+ directives and destroy the carefully setup path structure.
+
Database
^^^^^^^^
@@ -535,7 +570,15 @@ specifies which DBMS is to be used. However, currently only the value
In addition to selecting the DBMS software, the backend requires
DBMS-specific options to access the database.
-For postgres, you need to provide:
+.. note::
+
+ When using the Debian/Ubuntu packages, the database should already
+ be configured in the ``/etc/taler/secrets/merchant-db.secret.conf``
+ configuration file. The ``talermerchant`` database is also already
+ configured (unless you answered ``no`` when asked the question during
+ installation), so you can skip everything in this section.
+
+For the ``postgres`` backend, you need to provide:
.. code-block:: ini
@@ -631,6 +674,14 @@ exchanges must use the same currency: If the currency does not match the main
currency from the ``TALER`` section, the exchange is ignored. If you need to
support multiple currencies, you need to configure a backend per currency.
+.. note::
+
+ Manually setting up exchanges is only recommended under special
+ circumstances. In general, GNU Taler will include trustworthy
+ auditors (for each currency) in the default configuration, and
+ there is rarely a good reason for trusting an exchange without
+ an accredited auditor.
+
Auditor
@@ -639,7 +690,7 @@ Auditor
To add an auditor to the list of trusted auditors (which implies
that all exchanges audited by this auditor will be trusted!)
you create a section with a name that starts with “MERCHANT-AUDITOR-”. In
-that section, the following options need to be configured:
+4that section, the following options need to be configured:
- The ``AUDITOR_BASE_URL`` option specifies the auditor’s base URL.
For example, to use the Taler demonstrator's auditor, specify:
@@ -675,6 +726,14 @@ auditors must use the same currency: If the currency does not match the main
currency from the ``TALER`` section, the auditor is ignored. If you need to
support multiple currencies, you need to configure a backend per currency.
+.. note::
+
+ Manually adding auditors is only recommended under special
+ circumstances. In general, GNU Taler will include trustworthy
+ auditors (for each currency) in the default configuration, and
+ there is rarely a good reason for adding an auditor that is
+ not coordinating its activities with the Taler developers.
+
.. _Sample-backend-configuration:
@@ -743,6 +802,20 @@ you should use systemd, cron or some other init system of your operating
system to launch the process. Consult the documentation of your operating
system for how to start and stop daemons.
+.. note::
+
+ When using the Debian/Ubuntu packages, the systemd configuration
+ will already exist. You only need to enable and start the service
+ using ``systemctl enable taler-merchant-httpd`` and
+ ``systemctl start taler-merchant-httpd``. Additionally, you should
+ review the ``/etc/apache2/sites-available/taler-merchant.conf``
+ or ``/etc/nginx/sites-available/taler-merchant`` (these files
+ contain additional instructions to follow), symlink it to
+ ``sites-enabled/`` and restart your HTTP server. After that, you
+ should be able to visit the merchant backend at the respective
+ HTTP(S) endpoint.
+
+
If everything worked as expected, the command
.. code-block:: console
@@ -767,7 +840,29 @@ and use TLS for improved network privacy, see :ref:`Secure setup <Secure-setup>`
Instance setup
==============
-Before using the backend, you must at least configure the "default" instance.
+First of all, we recommend the use of the single-page administration
+application that is served by default at the base URL of the merchant backend.
+You can use it to perform all steps described in this section (and more!),
+using a simple Web interface instead of the ``curl`` commands given below.
+
+The first step for using the backend involves the creation of a ``default``
+instance. The ``default`` instance can also create / delete / configure other
+instances, similar to the ``root`` account on UNIX. When no instance exists
+and ``taler-merchant-httpd`` was started without the ``--auth`` option, then
+the backend is reachable without any access control (unless you configured
+some in the reverse proxy).
+
+The following documentation shows how to handle any instance. Thus, if you
+want to have multiple instances, you may need to perform the steps multiple
+times, once for each instance.
+
+.. note::
+ A security concern is that normal API usage leaks instance existence.
+ This means unauthorized users can distinguish between the case where the
+ instance does not exist (HTTP 404) and the case where access is denied
+ (HTTP 403).
+ This is all moot behind a properly configured
+ :ref:`reverse proxy <reverse-proxy-configuration>`.
KUDOS Accounts
@@ -806,7 +901,7 @@ Setup
------
With the knowledge of the ``payto://``-URI, instances can be configured by POSTing
-a request to :http:post:`/private/instances`. To create a first instance,
+a request to ``/management/instances``. To create a first instance,
create a file ``instance.json`` with an `InstanceConfigurationMessage`
.. code-block:: json
@@ -839,7 +934,7 @@ You can then create the instance using:
.. code-block:: console
- $ wget --post-file=instance.json http://localhost:8888/private/instances
+ $ wget --post-file=instance.json http://localhost:8888/management/instances
The base URL for the instance will then be
``http://localhost:8888/instances/default``. You can create additional
@@ -884,6 +979,7 @@ host-based firewall to block access to the TCP port of the merchant backend,
but this is *not recommended*. Relying on NAT or network firewalls for access
control is gross negligence.
+.. _reverse-proxy-configuration:
Reverse proxy configuration
---------------------------
@@ -932,26 +1028,26 @@ Note that the above again assumes your domain name is ``example.com`` and that
you have TLS configured. Note that you must add the ``https`` header unless
your site is not available via TLS.
-The above configuration(s) are both incomplete. You must still additionally
-setup access control!
-
+The above configurations are both incomplete. You must still additionally
+set up access control!
Access control
--------------
-All endpoints with ``/private/`` in the URL must be restricted to authorized users
-of the respective instance. Specifically, the HTTP server must be configured
-to only allow access to ``$BASE_URL/private/`` to the authorized users of the
-default instance, and to ``$BASE_URL/instances/$ID/private/`` to the
-authorized users of the instance ``$ID``.
+All endpoints with ``/private/`` in the URL must be restricted to authorized
+users of the respective instance. Specifically, the HTTP server must be
+configured to only allow access to ``$BASE_URL/private/`` and
+``$BASE_URL/management/`` to the authorized users of the default instance, and
+to ``$BASE_URL/instances/$ID/private/`` to the authorized users of the instance
+``$ID``.
How access control is done (TLS client authentication, HTTP basic or digest
authentication, etc.) is completely up to the merchant and does not matter to
the Taler merchant backend.
-Note that all of the other endpoints (without ``/private/``) are expected to be
-fully exposed to the Internet, and wallets may have to interact with those
-endpoints directly without client authentication.
+Note that all of the other endpoints (without ``/private/`` or ``/management/``)
+are expected to be fully exposed to the Internet, and wallets may have to
+interact with those endpoints directly without client authentication.
Nginx
^^^^^
@@ -967,6 +1063,12 @@ follows:
}
proxy_pass ...; // as above
}
+ location /management/ {
+ if ($http_authorization !~ "(?i)ApiKey SECURITYTOKEN") {
+ return 401;
+ }
+ proxy_pass ...; // as above
+ }
Here, ``SECURITYTOKEN`` should be replaced with the actual shared secret. Note
that the ``~`` ensures that the above matches all endpoints that include the
@@ -998,6 +1100,12 @@ each instance:
}
proxy_pass ...; # as above
}
+ location /management/ {
+ if ($http_authorization !~ "(?i)ApiKey MASTERTOKEN") {
+ return 401;
+ }
+ proxy_pass ...; # as above
+ }
location ~ /private/ {
return 401; # access to instances not explicitly configured is forbidden
}
@@ -1019,6 +1127,7 @@ Then, you can restrict to an access control token using:
RewriteEngine On
RewriteCond "%{HTTP:AUTHORIZATION}" "!=SECURITYTOKEN"
RewriteRule "(.+)/private/" "-" [F]
+ RewriteRule "/management/" "-" [F]
ProxyPass "unix:/some/path/here.sock|http://example.com/"
</Location>
@@ -1026,7 +1135,7 @@ Then, you can restrict to an access control token using:
Here, ``SECURITYTOKEN`` should be replaced with the actual shared secret. Note
that the ``(.+)`` ensures that the above matches all endpoints that include the
string ``/private/``. If you only run a single instance, you could simply
-specify ``/private/`` without the ``~`` to only configure the access policy for
+specify ``/private/`` without the ``(.+)`` to only configure the access policy for
the default instance.
If you are running different instances on the same backend, you
@@ -1055,6 +1164,7 @@ each instance:
RewriteEngine On
RewriteCond "%{HTTP:AUTHORIZATION}" "!=MASTERTOKEN"
RewriteRule "/private/" "-" [F]
+ RewriteRule "/management/" "-" [F]
RewriteRule "(.+)/private/" "-" [F] # reject all others
ProxyPass ... # as above
@@ -1069,6 +1179,32 @@ restrict access to the internal API to authorized clients.
System administrators are strongly advised to test their access control
setup before going into production!
+Status code remapping
+---------------------
+
+Normal API usage leaks instance existence information.
+Distinguishing between 404 (Not found) and 403 (Forbidden)
+is useful for diagnostics.
+
+For higher security (by leaking less information),
+you can add the following fragment,
+which remaps all 404 response codes to 403.
+
+Nginx
+^^^^^
+
+.. code-block:: nginx
+
+ error_page 404 =403 /empty.gif;
+
+Apache
+^^^^^^
+
+.. code-block:: apacheconf
+
+ cond %{STATUS} =404
+ set-status 403
+
Customization
=============
@@ -1226,8 +1362,7 @@ Authorize a tip
When your frontend has reached the point where a client is supposed to receive
a tip, it needs to first authorize the tip. For this, the frontend must use
-the :http:post:`/private/reserves/$RESERVE_PUB/authorize-tip`
-API of the backend. To authorize a
+a POST to ``/private/reserves/$RESERVE_PUB/authorize-tip``. To authorize a
tip, the frontend has to provide the following information in the body of the
POST request:
@@ -1257,7 +1392,7 @@ Picking up of the tip
---------------------
The wallet will POST a JSON object to the shop’s
-:http:post:`/tips/$TIP_ID/pickup` handler.
+``/tips/$TIP_ID/pickup`` handler.
The frontend must then forward this request to the backend. The response
generated by the backend can then be forwarded directly to the wallet.
diff --git a/wallet-confirm-withdraw.svg b/wallet-confirm-withdraw.svg
new file mode 100644
index 0000000..899728a
--- /dev/null
+++ b/wallet-confirm-withdraw.svg
@@ -0,0 +1,16 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 645.5 1015.5" width="645.5" height="1015.5">
+ <!-- svg-source:excalidraw -->
+ <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2baVfiSFx1MDAxN8ffz6fIcd62TO2L79hUXFxQREV55jmeXHUwMDAwgUBCXHUwMDEyQ9ic0999KqBcdTAwMTAgYJrWXHUwMDE2p+V4VKqyVKp+91/31q3884em7Vx1MDAwNWPP2DvQ9oxRXbfbXHJfXHUwMDFm7n1cdTAwMGLLXHUwMDA3ht9ru46qQpPvPbfv1ydHmkHg9Vx1MDAwZf76a35Gqu52p2dcdTAwMTm20TWcoKeO+5/6rmn/TH5H7uNcdTAwMWL1QHdatjE5YVJcdTAwMTW5XHUwMDE1IculRdeZ3Fx1MDAxNlx1MDAwMkCQ5Fx1MDAxOMrZXHUwMDEx7V5O3S8wXHUwMDFhqrqp2z1jXlx1MDAxM1x1MDAxNu05h82K/mhJK1t8urox3Y7h30du22zbdjlcdTAwMTjb04fS62bfj9T2XHUwMDAy37WMSrtcdTAwMTGY4d2Xymfn9Vxc1Vx1MDAwNfOzfLffMlx1MDAxZKPXWzjH9fR6O1x1MDAxOE+fYlY67YVcdTAwMDNtXjKaXHUwMDFjIVFcblwiOitcdTAwMWNP+oWmOF1qQ9a1XT9sw59g8pm3oqbXrZZqitOYXHUwMDFkXHUwMDEz+LrT83RfXHLO/Ljh89MxhFJERj6zI0yj3TJcdTAwMDN1iMRCtVx1MDAwMMw+kVx1MDAwZTEmXHUwMDAzXHUwMDAwXHUwMDAxRoBcblxu5s1cZpvgXHUwMDE1XHUwMDFhXHUwMDEzXHUwMDE4/r/cgabue89cdTAwMWS111x1MDAwYr9Emlx1MDAxZrY8PyXp5fRJ3fdv2zCF1zNcdTAwMDWJelx1MDAxMkokT8xU//Ixa8G+mb0s92tiP91qmHn/XHUwMDEzMIVTgi8yXHUwMDA1hXxHoHCKk41AUbpCXHUwMDEw5MrEXHUwMDA1pfDXXHUwMDEzXHUwMDE0XHUwMDE4oyBcdTAwMGVcdTAwMWXI6Fp4kIBcdTAwMTBwSHBieDrp5tktyT9cdTAwMTVcdTAwMWac/qEv789rQ1bdeXggXHUwMDAzK/AgQd5TkDCKIYasalx1MDAwZZaAM0ZcdTAwMDF+XHUwMDE3YuZcdTAwMDPjOkG5/Vx1MDAxNPZccmZcdTAwMGKlh3q3bY9fJskpUiFK6lx1MDAwNlx1MDAxOd2xtMlTNlxyX2tcdTAwMTiB3rZ70S7pXHUwMDE5dtuZXFxz8eS03W6F7O3ZRnNcdTAwMTHKoK1m3Fl14Hp728tcIlx1MDAxNmxcdTAwMWTZXHUwMDE4XHUwMDEzIJRmJFdFNFx1MDAxZYlcdTAwMDZo3Vxic2h2XHUwMDAyUW0/le67O1x1MDAwZjZcdTAwMDWrM60yaZFC68muS6QjfXuyIWcpyUKZU8qBXHUwMDA0QyxcdTAwMDb0xalcdTAwMTbIXHUwMDE17lWrhZRcdTAwMDLGz7XPZeFjZlx1MDAxZYeD0dg0WVx0XFyOQXNUrKcv957rP0RQMV+LXHUwMDFkwUjNx5zQxNjlrnGt10Z6JX/bqTw1feT27d2fjClU2lx0I1xiLFx1MDAwMYhIXG5cdTAwMDNMn1x1MDAwZlx1MDAwMez9WCRsoSGArLKIYGrWlPB3RFx1MDAxNF5glERNhVJGJPtcdTAwMDNgjNNqKFI0vqNfke66roi0tWE7MMPwRrdjZVx1MDAxYvK1XfMmMj5l/DZbqY/S/ea5azHLs1x1MDAwNo1zt16bXy7e3l7m7Vx1MDAwNbIwh9E5e0ZB7LDjXHJcdTAwMTS/1Wz/0fa5ra/wXHUwMDAyPlwigqtRZzw6uC9RXHUwMDA3XHUwMDA0y6Uzx1G5LJJySZNEsqusO33bXiH20jZcdTAwMTSYithoXHUwMDBmRkxcdTAwMDGBJOz/XGKy8Vx1MDAxNrFcdTAwMDHkk9vi3SG7vW3rXHUwMDFkcthcdTAwMTlfX42veDKQXHUwMDExlqkl/5OQeJZ5jN/4xXJcdTAwMDKWXHUwMDAxxJxSXHUwMDE2iVx0I0FcdTAwMTDasCojXHUwMDEwUvJcdTAwMGaS+IpcdFkuZNLFXHUwMDAzzYKogztcdTAwMTY0XHUwMDExtjq7yvRZ/r5cIk/R5aDcwLend9XHXrNMtmaay1imyVx1MDAxN9PbMU2USkjJXHUwMDExjGM6XHUwMDEyMy4zLSlRXG6dLP5JyHSmkD3QbFx1MDAwNK1cdTAwMGW2LVx1MDAwNPHOXHUwMDEyfcFBoXfSsi46VXJcdTAwMDOD2khvutVtiaZcYkRjqVx1MDAxOdFcdTAwMTJ+XHUwMDExvVxy0YRcdTAwMGLGaTTsSybSUlx1MDAwMoJcdTAwMTB/S4ejqHeNXHUwMDAzLT+qm2q0XGat4NRTu4r0XHI+hFx1MDAwNZEp034uyF9XR5afXHUwMDA2eGukXHUwMDE5jEWagi+kt1x1MDAxMmnBVfiIXHUwMDA0imV6lfT56itcdTAwMTdUXGLI3pDpdFdcdTAwMWRcdTAwMTdcdTAwMWNoN+WcesTdwznZulx1MDAxZVx1MDAxNuv6jEjClFxmJE+gjdvSO9r3qmnPr41L18EhbFYvdlx1MDAwN71169WcKVx1MDAxM50vny2ur1x1MDAxMLLeXHUwMDA2MSPNOv+JZWuxkPVgMi6yZikqsfK2XHUwMDExJVx1MDAxMkBOli1cdTAwMDJcdTAwMDHOIYUwPlx1MDAwYvJcXKZK8zJzedFpl5npXHUwMDFlNa7cXHUwMDBi6Fx1MDAxN4boQ9f2XHUwMDEwW+9TcSwxZkzAxPBJViqdZrqw4qOzYlx1MDAxNz9cdTAwMTZcdTAwMDeeXdx9+Fx1MDAwNE5cdFwiXHUwMDAwllxiqnAqXCJPU/hoXG5AwoGSLoQphutcdTAwMTf3fpbFcFx1MDAxNXFGmVT3W0VcdTAwMTHSXHUwMDE0lFxmcEEmRypcdTAwMTdh1YWWqq3stZW9d0UxToYhSk16XHUwMDE3cUKVn1x1MDAxZbGzXHUwMDA1VZ6P4Wxlz/XG8ZJcdTAwMGLW9sUvzMnQtYvjysFcdTAwMDBIXHUwMDE52Fx1MDAwZmRcdTAwMWKv97PVo5wrx7etk0y/drJcdTAwMWZcXIPD3TegzeotVERcdTAwMDEweUFcdTAwMWK9m/28iZZjrGJcdTAwMWSqPJVXXGaocGtm3caZkb7tdkeg1KpegLtcdTAwMGbWcrFcdTAwMTI1v6CIIFKqIH4kUXNcdTAwMDXK40H15tJ0jkv57sNRPzvSdz9Rs6zlZFx0RZpCULyMPsL43Vh8XHUwMDFiLWdcXKhaRj9cdTAwMTTG30rN0TpcdTAwMTOSQvl3jIjkXHUwMDE5dlbbz1x1MDAxZlt2SVx1MDAxNo+vXHUwMDFl8oEvPLt+tPtcdTAwMTa0UcwpgilCP42YQ6hcXFig5uDXPHPr3LpcdTAwMDSPXHUwMDE39X1cdTAwMWSUee5hjMtew/hYNecrOar5XG4+VC5cdTAwMWVhOLljsY+rxlXFXHUwMDFiUL+Sz2TuLnnNxbe7z+JGNadq+Fx0i6j5bos5goBcYkkweU3M35XF30nMydrYliFAIWIwuTt0fnVcdTAwMGby11x1MDAxN8fnd4Nz+fTY4Sg38nbfgDaLOUMp+XnEXHUwMDFjXHUwMDAxXHTBYpIn3oBcdTAwMWPT9PtwRJ9aslghx/r9iXXrfPAyy9pcdTAwMTU+5Y4ub1x1MDAxOXuNxVx1MDAwMjTcgs6650f0yTJhqcdzJ3T3Wdws5oyn5OdcdTAwMTFzVS3DXHIh8jXH4l1Z/G+I+Vx1MDAxNGpvKNOnN96x5dxcdTAwMWTJ/dLJqWVVTlZTPjGKP837kIUkT/iI0ZW8mVx1MDAxMFx1MDAwMZSiq1x1MDAwM46g0sno5ne+geavNNCLXHUwMDFjI2Wn0eA0XCJ4dG1qk1xudebCtpVcdTAwMWbNXHUwMDAybaDo/qyTQydtcZx7aNIsyFQvS1x1MDAwM7BKUdzWO6VcdTAwMGJLXHUwMDAwcS7jXHUwMDEyh1TEXHUwMDEwXHUwMDE08eq+iFmXOORcXI094ihux1x1MDAxMlwi6+NcdTAwMWQuXHUwMDA041x1MDAxY9Mk8U7SxKFcdTAwMWRcdTAwMTi+o1x1MDAwN+2BYY+/aWO3r9V1R1OXcrVe+F9gtnta6Uqru1xyQ3N9zfVcZkfTro9cdTAwMGJl7axQPNX+dsJzTH1gaLpW01x1MDAxZKvttLS052ltp1x1MDAxN+i2bTTUJfRA6/U9z/WDnnZ1mNWEXHUwMDA00Yn6VycpXHTYYDqH9kMh91RcdTAwMTj2xdNcdTAwMTHP3e9XXHUwMDFhNMhvm3NnSmdjNkZh9pVz325cdTAwMWKJJFx1MDAwMtM4u5FrXHUwMDE3faFyuVx1MDAxMCRRc/tps+n1a1x1MDAxZDVcdTAwMDNcdTAwMWZopUpejXu6nFM/XHUwMDA0U/VV/fNZ8++rU9jsvVx1MDAxYVx1MDAwMpmKOn4gXHUwMDAxX2FOXHRfPDxcZq2j8ztGQFvW+589UFTO32dK4UDGJVDWXHUwMDAy498xmzvnpnuq81IlYId687pf22/lR1wi+8E5nPV70EOviXKeaPvMc1x1MDAwZce6OVx1MDAxYVp1/9pcdTAwMTjw6jBbv/H54Gn3WdxcdTAwMTgoMlx1MDAxNeAs5HB2O1CkXGZBqa7x2qLfu6L4X4pcdTAwMTPh9X32qGqWzvrGXcutXzL1sDfJPHxcIlx1MDAxNklSnMW49yTiZm7n3lx1MDAxYspZ/VxyfVx1MDAxNCg45pBSXHUwMDE093JccpQrzsv83VUslWNcdTAwMTh5V/in3ZRz3TKU5+1cdTAwMWJa4Gr9nvpjXHUwMDFhypP3QzdAe/ZhvmmuKvWH7Wn1307XdYyxNlSDoDluoOm+r4JcdTAwMDPlzk9DgWHo01x1MDAwN1x1MDAxZrljdqP33qlcdTAwMWTfyv6ooVx1MDAxZlx1MDAwZfRcdTAwMGJqV+C41o9cdHzXLZ9AxFNLXHUwMDFlPGNxXHUwMDFlPIUxwa+InvxlXHUwMDFma+yDc07C7UBx6yXrd1x1MDAxMYV7XHUwMDBiXHUwMDA1QeQnzGNcdTAwMDM210FcdTAwMTftXHUwMDFm2/nSWdlr2KDLhU7TyYK+yG6T6auKILWKXHUwMDBij4v3yFx1MDAwZrDym8Z7XHUwMDEwIMhcdTAwMDSFq9HdpHKDj6hmNZFsw3BCLd1iazVeuMhPh3ZyXHUwMDAzwEG2g24ydXZxWfQy+/jee1x1MDAwMFYuXHUwMDExwJitrFqERTFcdTAwMWVcdTAwMDGKcVxivlx1MDAxNi1cdTAwMTK9bStcdTAwMTFYeM18XHUwMDBl8YblvslcdTAwMWJgXHUwMDE4kDeEOHBcdTAwMGZ2YHHij2dXfk/3vHKgXHUwMDA3Yd2U571B21x1MDAxOGZWsfizOfmEXHUwMDBl8fc/vv9cdTAwMGIhXG42wCJ9<!-- payload-end -->
+ <defs>
+ <style>
+ @font-face {
+ font-family: "Virgil";
+ src: url("https://excalidraw.com/Virgil.woff2");
+ }
+ @font-face {
+ font-family: "Cascadia";
+ src: url("https://excalidraw.com/Cascadia.woff2");
+ }
+ </style>
+ </defs>
+ <rect x="0" y="0" width="645.5" height="1015.5" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 66.75) rotate(0 311.25 469.375)"><path d="M0.1 -0.43 C191.81 -1, 384.73 -0.48, 622.99 0.04 M0.24 0.31 C237.23 1.21, 474.12 1.74, 622.31 0.32 M622.81 0.69 C619.82 265.46, 618.57 531.76, 622.65 939.19 M622.53 0.18 C625.38 220.28, 625.13 441.42, 622.35 938.57 M622.74 938.73 C459.11 938.32, 294.82 938.61, -0.09 938.67 M622.29 938.85 C380.68 940.76, 139.59 940.06, 0.15 938.78 M0.56 938.83 C-0.16 733.07, 0.31 526.84, -0.04 -0.27 M-0.24 938.63 C-1.96 647.7, -2.08 356.96, -0.3 0.05" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(11.75 10) rotate(0 311.875 27.5)"><path d="M-0.43 -0.54 C226.54 -0.29, 453.87 0.19, 623.79 0.65 M0.31 0.19 C148.17 -1.46, 297.24 -1.68, 624.07 -0.07 M625.48 1.56 C623.23 10.11, 625.05 23.69, 624.84 53.7 M624.2 0.53 C623.31 20.31, 624.81 40.45, 623.3 54.63 M623.73 55.12 C400.92 55.1, 178.89 55.67, -0.08 55.7 M623.85 54.74 C383.85 53.9, 142.86 53.83, 0.03 54.68 M0.19 54.52 C0.68 39.24, -0.45 22, -0.67 0.2 M-0.3 54.31 C0.38 35.05, 1.01 16.31, 0.12 -0.68" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(78.75 105.75) rotate(0 164.5 20.5)"><text x="0" y="32" font-family="Helvetica, Segoe UI Emoji" font-size="36px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Bank transfer details</text></g><g stroke-linecap="round" transform="translate(420 929.25) rotate(0 88.48214285714312 24.375)"><path d="M1.19 1.55 C68.32 -0.77, 134.91 1.85, 176.01 1.6 M0.39 0.86 C50.84 0.56, 99.35 1.47, 177.15 0.55 M177.1 0.91 C178.37 10.19, 177.1 24.64, 176.22 47.86 M177.27 -0.03 C177.39 12.18, 176.42 25.35, 176.85 48.65 M175.91 49.23 C106.99 51.87, 40.71 48.37, 0.75 48.88 M177.66 48.85 C137.93 47.4, 99.73 46.84, -0.05 48.41 M-1.2 48.14 C-1.6 33.39, -2.18 19.55, -1.51 0.24 M-0.21 47.95 C-0.76 37.33, -0.29 26.84, -0.34 0.86" stroke="#c92a2a" stroke-width="1" fill="none"></path></g><g transform="translate(432.58928571428623 945.3035714285706) rotate(0 73.357142857143 10.678571428571445)"><text x="0" y="17.357142857142897" font-family="Helvetica, Segoe UI Emoji" font-size="18.57142857142862px" fill="#c92a2a" text-anchor="start" style="white-space: pre;" direction="ltr">cancel withdrawal</text></g><g transform="translate(77.875 192.75) rotate(0 52 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Please wire</text></g><g transform="translate(157.625 262.75) rotate(0 89.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">IBAN: k12j3jk1h23kj</text></g><g transform="translate(157.625 300.75) rotate(0 74.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">BIC: l21kj3lk213j</text></g><g transform="translate(157.625 341.25) rotate(0 95.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Name: Exchange Inc.</text></g><g transform="translate(157.625 382.25) rotate(0 75 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Amount: USD 10</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 261) rotate(0 19.374999999999886 13.296568627450881)"><path d="M0.2 1.6 C6.8 -0.4, 15.21 2.08, 40.58 0.44 M0.54 -0.57 C14.67 0.42, 29.71 0.1, 39.41 0.7 M39.39 0.92 C36.83 9.01, 39.01 14.69, 39.87 25.7 M39.27 0.69 C39.52 11.43, 39.74 20.45, 38.03 26.49 M38.22 25.75 C22.92 25.11, 8.14 27.35, -1.75 26.82 M39.03 26.1 C27.25 26.21, 14.83 26.88, 0.93 26.14 M-0.74 24.9 C0.74 19.34, 1.19 11.08, 0.05 -0.27 M0.7 25.62 C1.09 16.81, -0.57 8.72, 0.31 -0.89" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568687 266.01470588235316) rotate(0 12.156862745097897 7.598039215686214)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 301.2034313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-1.8 -0.14 C12.37 1.47, 28.62 1.27, 37.36 -1.73 M0.39 1 C13.1 -0.86, 25.79 0.16, 37.85 -0.43 M38.24 1.81 C37.08 7.65, 38.9 17.94, 40.18 24.78 M39.45 0.2 C39.01 7.37, 39.26 12.39, 38.56 26.23 M39.88 27.03 C24.89 26.77, 10.63 25.1, 1.58 27.03 M39.75 27.54 C28.74 27.01, 18.79 27.01, 0.77 27.26 M1.89 28.26 C0.17 21.47, -0.68 14.05, 0.4 -0.35 M0.39 25.68 C0.67 15.44, 0.45 5.81, 0.11 0.6" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 306.21813725490233) rotate(0 12.156862745097897 7.598039215686214)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 342.4534313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.38 -0.73 C16.13 0.66, 29.47 1.3, 39.19 -0.71 M0.79 0.22 C12.09 0.74, 24.2 1.24, 39.69 0.34 M40.29 1.33 C37.37 11.2, 38.46 21.39, 40.42 26.95 M38.95 -0.18 C39.64 9.14, 37.8 18.36, 37.83 27.31 M38.96 27.79 C28.84 25.41, 21.91 27.74, -1.04 28.06 M38.26 26.44 C28 27.11, 17.37 27.04, -0.97 26.55 M-0.09 25.8 C-0.26 15.61, -1.09 7.69, -0.49 0.26 M-0.4 27.52 C0.35 19.06, 0.53 12.64, -0.83 0.34" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 347.46813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 383.9534313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.49 -0.78 C15.26 -0.5, 31.64 0.17, 36.97 0.79 M0.5 0.28 C8.18 0.84, 15.8 -0.41, 39.54 0.52 M38.8 1.45 C37.97 6.29, 39.23 13.66, 39.55 26.93 M38.41 -0.99 C38.69 5.01, 38.56 10.33, 38.16 27.15 M40.58 24.93 C27.27 28.29, 18.6 25.21, 1.55 26.35 M38.83 27.42 C29.93 26.92, 21.21 25.9, -0.76 26.99 M1.87 26.4 C-1.77 17.07, 1.42 10.3, 1.88 0.91 M0.53 26.14 C-0.51 17.99, -0.02 10.03, 0.66 0.83" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 388.96813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g stroke-linecap="round" transform="translate(159.125 690) rotate(0 151.25 108.125)"><path d="M-0.59 -0.65 C83.72 -0.73, 165.78 1.76, 301.11 -0.16 M-0.3 0.73 C68.13 1.91, 137.74 1.33, 302.4 0.68 M303.7 1.55 C302.89 46.8, 303.22 91.56, 301.87 215.05 M302.76 -0.38 C303.92 68.39, 303.93 138.49, 303.34 215.92 M301.06 217.22 C191.77 218.63, 83.96 217.97, 1.38 217.12 M303.01 216.46 C187.06 215.84, 72.43 214.73, -0.58 215.7 M-0.49 214.79 C2.29 161.3, 1.47 109.67, 1.42 -0.99 M-0.83 215.67 C0.89 145.95, -0.16 76.71, -0.78 0.74" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(42.125 600.25) rotate(0 292.5 22.5)"><text x="0" y="17.5" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Alternatively, you can also scan this QR code or open THIS LINK </text><text x="0" y="40" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">you have a banking App installed that supports RFC 8905</text></g><g transform="translate(157.625 423.75) rotate(0 180 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">subject: QWE123ASDASD435QWEASD</text></g><g stroke-linecap="round" transform="translate(94.12500000000023 428.2034313725492) rotate(0 19.374999999999886 13.296568627450881)"><path d="M-0.38 1.12 C8.3 2.21, 20.85 0.78, 38.1 -1.47 M-0.14 0.95 C8.36 -0.35, 17.46 -0.48, 38.72 -0.75 M40.24 -0.22 C38.82 5.85, 37.43 11.71, 36.87 24.97 M37.87 -0.72 C38.61 10.14, 39.32 21.4, 39.72 26.66 M40.72 26.3 C28.27 25.29, 15.24 27.74, 1.91 25.32 M38.39 27.31 C25.46 26.86, 10.75 27.7, -0.74 26.71 M-0.24 28.49 C1.24 17.12, 1.53 5.62, 2 -0.76 M-0.96 25.67 C-0.53 18.42, -0.61 10.78, 0.5 0.34" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(101.72303921568641 433.21813725490233) rotate(0 12.156862745097897 7.598039215686185)"><text x="0" y="10.196078431372422" font-family="Virgil, Segoe UI Emoji" font-size="12.15686274509794px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">copy</text></g><g transform="translate(65.875 504.25) rotate(0 226.5 22.5)"><text x="0" y="17.5" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#e67700" text-anchor="start" style="white-space: pre;" direction="ltr">Make sure to use the correct subject, otherwise the</text><text x="0" y="40" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#e67700" text-anchor="start" style="white-space: pre;" direction="ltr">money will not arrive in this wallet.</text></g><g stroke-linecap="round" transform="translate(45.375 487.75) rotate(0 257.5 43.75)"><path d="M0.34 0.72 C200.37 1.93, 399 2.26, 514.51 -0.13 M0.03 -0.22 C123.26 0.72, 246 1.36, 514.88 0.35 M514.83 -1.24 C515.78 20.87, 515.26 40.81, 513.13 85.59 M514.86 -0.18 C515.13 22.54, 514.29 45.85, 514.42 87.17 M515.15 87.04 C350.04 88.8, 185.58 89.06, 0.52 86.83 M514.84 87.7 C403.52 86.46, 292.49 86.9, -0.2 87.35 M0.93 86.82 C-0.35 56.29, -1.91 25.03, -1.42 -1.99 M-0.54 87.01 C0.32 55.97, -0.86 25.45, 0.04 -0.52" stroke="#e67700" stroke-width="1" fill="none"></path></g><g transform="translate(198.875 191.5) rotate(0 35 12)"><text x="0" y="19" font-family="Cascadia, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">USD 10</text></g><g transform="translate(287.625 190.25) rotate(0 11.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">to:</text></g></svg> \ No newline at end of file
diff --git a/wallet-start-manual-withdraw.svg b/wallet-start-manual-withdraw.svg
new file mode 100644
index 0000000..708b4cd
--- /dev/null
+++ b/wallet-start-manual-withdraw.svg
@@ -0,0 +1,16 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 649.25 613" width="649.25" height="613">
+ <!-- svg-source:excalidraw -->
+ <!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO2aWXPaOFx1MDAxY8Df+ylcdTAwMTj2taW6JfeNJqXlyEXClnRnp+PYXHUwMDAym/igtrnS6Xdf2STYYENdSnazMzCZJNaBZOmn/6nvryqVarRcdTAwMTjL6rtKVc5cct2xzUCfVV/H5VNcdTAwMTmEtu+pKpQ8h/4kMJKWVlx1MDAxNI3Dd2/fpj1qhu8ue0lHutKLQtXuL/VcXKl8T35nxlx0pFx1MDAxMene0JFJh6QqXHUwMDFkXG5cIr5Zeu57ybBcdTAwMWFcIphcdTAwMGLG8KqBXHUwMDFknqrhXCJpqtqB7oQyrYmLqro+ciZcdTAwMWW6gNc9+G0442cyYN101IHtONfRwlm+k25YkyAzpzBcbvx7+dk2Iyue10b5ql/oq1x1MDAxNUh7XHUwMDA1/mRoeTJcZtf6+GPdsKNFXFxcdTAwMDbAqnS5XGLvKmnJXFw9YU5Xz3FcdTAwMGZEUY1ujH7iO35cdTAwMTCP/lx1MDAwN0g+6fh3unE/VJPwzFWbKNC9cKxcdTAwMDdqV9J2s8f3Yohnvt2S9tCKVDHF2eJQJktcZjHhkKk9QKuaeKhx00x2++/NJbL0YPy4XHUwMDE01TB+yEwznuGHJSpP3ZO6XHUwMDFmr/eBhpNt0CDGXHUwMDExwFx1MDAxNGmloWmF0Fx1MDAxYlx1MDAxYi1cXO9p/S/NXHUwMDFlnUze3338XHUwMDFmQFNbx1x1MDAwNmr8XHUwMDE5oVx1MDAxMTVeRE1cdTAwMWVcdTAwMTmqcUSpwPjfQ2a5je2BOb+zvzgmXHUwMDFkTCdu53YyMCaZbdzBVbyehIK11cSU1Vx1MDAxMM0tXHUwMDA0XHUwMDEyqPD0ZEvzXHUwMDFidqjt+K9R3Hcnn9jgXGJcIo0xnp7N9EhraLPw6URzJYFcYtFwivf2XHUwMDEznWfGmzjOXHUwMDBlarri/JN9XHUwMDFkdq6QNVx1MDAxZVx1MDAxOTfWXGL1vmp5alwiOY82gcG1XHLBjVx1MDAxMclcdTAwMDHDWZ5cdTAwMTaEj6z8lFx1MDAxNUaxgIQzWMCK2GoyXGJcdTAwMDFcdTAwMDBcdTAwMTNcdTAwMWPCvVlJtz7ecjU/Y1x1MDAxMqiVNVx1MDAxNpml9b3o2n5ItFxyWCtt6K7tLJ5cZqjV19RcdTAwMWR7XHUwMDE4z7vqyEG0ps9cIltcdTAwMTlUq+rIz4o/PZSO7cnk4OwgeN41Qlx1MDAxOTVbJ99cdTAwMWGyaTeaI3tuXHL2lntEXHUwMDAzOYYxQDWiZT55pMlR/JVcdTAwMTB/QGNcdTAwMTCjjNLMWNxgq0VDiOpcdTAwMDRZXHUwMDE5g+aXxV87asvTL2bvZEIuKVuMOmdh0Chcclx1MDAwZuNrSnKpN9cshSeEaLHRucvKOVKTbLDggkIqYFx1MDAxMTRcdTAwMTBsVZpqXHUwMDA2XHUwMDEwXHUwMDAxRNmzaE23fv+V9q3ueHo1PNV7oze9Py+uy2lNuElcZqF5rSmOWnM/XHUwMDExg4hQnlx1MDAwMWGFtJDttFCgiTJudkmdKeeGpTZJvlSdiU5cdTAwMWEtfNd5oIEwyc2cXnwx5Hkpflx1MDAxOdpwuyiAtby8g+hI8F5cdTAwMDRrXHUwMDFjY1x1MDAwNDgvXHUwMDA0mImtdlx1MDAxZqGMaMpLOFx1MDAxY8OmdP1apDsyqHkyeqkkXHUwMDFicsE7/WFjMGjctuyW8/H8ejQqRTJcdTAwMTdrXHUwMDFjY46KXHUwMDE0N8FHjveUxJrS3MXGXHUwMDFlyznAqdpmMciadkBh/KHXfan0dj41xt1cdTAwMTnisD1tNHufXHUwMDAy7E6ih1L0kpxcdTAwMWPORFx1MDAxNVZhq6NcdTAwMTDeXHUwMDBiXlx1MDAwNDDgXHUwMDA0IFxc5HxDgLfSi6FiXHUwMDE3UnpAMay7qt2LXHUwMDE1v64xXHUwMDFiKMup0WOQf5hd3t+a901Y3vnOUcxggftccotcXKc1O/pI8lx1MDAxNpI5Rlx1MDAwNFx0rOWQTcxcdLBccmSm3FjONED35nhcdTAwMDczV5/aXHUwMDFkrWt/XHLOUPiZd/Wvp1edSWlm+FqiJl4uXHUwMDBleIFcdTAwMDWKMCig5tfi1JiRgcH/39A8lsV7PJqC3tBzZ/3eotG2NK970V9UXHUwMDFm6/dS8Yhwylx1MDAwMC5cbmfvyFBcdGVcdTAwMTlcdTAwMTDB+fN45qAhXHUwMDFh1sx84/7puv3LnjA/10vFsznflEVcdTAwMWPRXCKLXHUwMDEw4oL43y9p1SNWP1x1MDAxMVlcZlx1MDAxMEpcdTAwMGJF1naqIFx1MDAxMqpcdTAwMGbnv1x1MDAxMfDJ6d4w0oOoMrMjK075685L1cL3yOxdynpfmPVcdTAwMDdXXHUwMDFmoiGhLVxcPoopclFMklx0QK2MSVxcmFx1MDAwMiX8mPorIys5XHUwMDA3yqwsVsRgq1wi1rhiWlx1MDAxOZXPoojRtLlcYupcdTAwMDPR6UxcdTAwMWbshkXDTmNo5rHRg8CfbVxiS8hrXGZoyodmXGKuhc6WOVx1MDAxNFFjyvVTUlx1MDAxZSjjTbB8gFx1MDAxM9Wwsi5cdTAwMTDEXFxoQFx1MDAxOcyY5MlCqlx1MDAxMeFcdTAwMDRRqDFBIKb8yFnKWfIuXHUwMDA1nos6zeqnMMWMtK2cKV2nYYZcdTAwMGbhdo99O3tcdTAwMWIq/qT/VdItS1x1MDAxZVb///26sPV2TpLaXHUwMDFjIen35Vx1MDAxNJCjh9GJ77p2pF7qMp7k5sxcdTAwMTNx/972TNtcdTAwMWJu1knP3FKT9KrHZ8SSulnQL1v3eJhcblx1MDAwZuWW85bZv7xOTLVcdTAwMWZcdTAwMDXKYdVomcTv8vT32ouzm8nJYorE6LSNwVxitvz+yzlcYsXXfpRcdTAwMTCtXHUwMDAxSrhcIlx1MDAxNaq/mUtcdTAwMTPLXHUwMDE4XHUwMDFlrFHOkFDOO6RcXMvcRzj0jaDfXHUwMDE0YE9qXHUwMDAxUFx1MDAxNpstmZTyvlx1MDAwMqDottBvkl3yxPxcIvsvVVDsUJTdeed6tFBA4v74/OpcXGveydGslFMhNp1VXG5woVOBRFx1MDAwMT/HUN3PXHIrJfnii1x1MDAwNblcdTAwMWIxscWaiz2vxCVhVNkmnFx1MDAxZTDMXFw3zcpLz/stTlx1MDAwNqY/XHUwMDFlP7Q/3p663HjjXHUwMDBm0G1U2lEgJJe8Zlx1MDAxMFx1MDAxNuJcZoqCL8eQXVx1MDAxOaJcdFx1MDAxMFRgVpg42ZHBXHUwMDE2UHlcdTAwMTecPk9UpXFyo79pXd54l/2JRlxmy/t48eG+XFy+WNu8JchcdTAwMTCtiWOm7WC8UI0pq0jk71x1MDAwNMZrulx1MDAxNVx1MDAxN6VcIpWfoFx1MDAxNObhXHUwMDA04IHybIZcdTAwMWFFXHUwMDA2OySfa5tmVjRcdTAwMTVcYr9Xj3ZCVVx1MDAxZo+vIz2K65ZIV6e2nL3Pk/HHIPnElvmPVz/+XHUwMDAxs5+03iJ9<!-- payload-end -->
+ <defs>
+ <style>
+ @font-face {
+ font-family: "Virgil";
+ src: url("https://excalidraw.com/Virgil.woff2");
+ }
+ @font-face {
+ font-family: "Cascadia";
+ src: url("https://excalidraw.com/Cascadia.woff2");
+ }
+ </style>
+ </defs>
+ <rect x="0" y="0" width="649.25" height="613" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 65.5) rotate(0 313.75 268.75)"><path d="M-0.64 0.27 C183.16 -1.41, 366.24 -0.99, 627.16 0.51 M-0.15 -0.1 C236.49 0.74, 472.1 0.89, 627.58 0.04 M627.21 -0.72 C626.48 193.78, 626.92 387.94, 628.16 537.25 M627.6 0.23 C626.3 157.65, 626.19 315.27, 627.48 537.61 M628.24 537.4 C433.34 538.98, 237.43 537.81, -0.4 537.85 M627.22 537.62 C394.58 539.45, 161.17 539.58, 0.16 537.15 M-0.55 537.11 C1.14 370.78, 1.74 202.32, -0.78 -0.26 M0.14 537.27 C0.47 350.59, 0.24 163.71, -0.21 0.08" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(10.5 10) rotate(0 314.375 27.5)"><path d="M-0.69 -0.79 C127.48 -0.76, 255.38 -0.98, 629.49 0.7 M-0.2 -0.18 C126.63 0.32, 253.87 0.26, 628.57 0.36 M630.22 1.63 C628.12 11.75, 630.32 26.32, 628.28 53.54 M628.96 -0.46 C630.15 16.06, 629.64 31.3, 628.68 55.27 M627.95 55.16 C438.86 56.91, 249.65 56.3, -0.66 55.38 M628.6 55.01 C476.39 54.97, 323.43 54.72, 0.08 55.29 M-1.67 56.06 C-0.22 42.88, 0.37 29.96, -0.66 0.81 M-0.87 54.22 C-0.63 33.16, -0.58 12.55, -0.53 -0.4" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(85 169.25) rotate(0 141.25 26.25)"><path d="M1.21 0.41 C68.36 -0.08, 136.37 0.62, 283.47 1.06 M0.21 0.65 C93.87 0.11, 186.6 0.74, 282.54 0.55 M282.46 -1.04 C282.42 15.3, 281.47 35.08, 281.87 53.39 M281.66 -0.58 C283.36 18.87, 281.88 39.9, 282.65 52.09 M282.87 52.04 C184.95 49.55, 87.55 50.33, 0.38 52.12 M282.75 52.39 C209.93 50.11, 138.48 50.34, -0.32 52.46 M1.68 51.38 C1.52 39.89, 0.18 28.67, 1.43 -1.8 M-0.79 51.78 C-0.51 40.69, -0.69 30.09, 0.57 0.22" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(88.75 137) rotate(0 38 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">currency</text></g><g stroke-linecap="round" transform="translate(85 303) rotate(0 151.24999999999994 22.5)"><path d="M0.91 -1.14 C95.79 -1.44, 190.83 -2.21, 302.59 -0.46 M-0.67 -0.65 C98.46 0.86, 196.25 0.51, 303.17 -0.24 M303.21 -0.52 C304.15 10.5, 304.18 22.15, 302.86 44.2 M302.76 -0.89 C301.3 15.99, 301.72 33.25, 301.94 45.83 M303.14 44.41 C194.91 45.12, 86.09 43.91, -0.44 44.54 M303.16 44.91 C192.69 46.89, 83.74 46.68, -0.07 45.58 M1.14 45.24 C0.92 29.4, 1.3 13.59, -0.8 -0.83 M0.16 45.29 C0.93 32.24, 0.27 21.07, -0.38 -0.09" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(311.25 171.75) rotate(0 28.75 25)"><path d="M0.44 -0.77 C14.94 -1.14, 28.74 1, 57.05 -0.33 M0.08 0.59 C16.41 -0.48, 30.76 0.45, 58.18 0.53 M58.91 0.53 C59.04 14.79, 59.13 29.43, 59.12 50.44 M57.26 -0.91 C57.45 13.3, 58.12 25.66, 57.66 50.88 M58.73 49.01 C41.72 51.08, 27.73 49.64, 1.53 49.91 M57.62 49.75 C34.13 49.25, 11.8 50.29, 0.88 49.68 M0.38 51.81 C2.16 34.87, 0.54 24.89, 0.68 0.55 M0.44 49.31 C-1.01 30.58, 0.05 10.84, 0.61 0.3" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(86.25 267) rotate(0 43 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">exchange</text></g><g transform="translate(97.5 314.5) rotate(0 63 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">demo.taler.net</text></g><g transform="translate(113 185.75) rotate(0 21.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">EUR</text></g><g transform="translate(77.5 392) rotate(0 33 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">amount</text></g><g stroke-linecap="round" transform="translate(77.5 423) rotate(0 157.5 25.625)"><path d="M0.98 -1.01 C93.63 -1.56, 187.05 -3.04, 315.73 1.11 M-0.04 -0.04 C73.77 -1.1, 147.37 -0.72, 314.99 0.25 M315.02 0.27 C313.45 15.41, 314.14 29.54, 316.03 53 M315.56 -0.56 C314.38 19.16, 315.73 36.89, 315.62 50.8 M315.01 51.64 C198.27 52.87, 77.9 51.61, -0.42 51.5 M315.63 51.68 C235.57 50.27, 157.37 48.94, -0.59 51.29 M-1.47 51.7 C-2.4 38.22, -1.94 25.61, -0.44 -1.56 M0.68 50.43 C-1.32 35.36, 0.49 21.68, -0.96 -0.5" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round" transform="translate(372.5 520.5) rotate(0 115 26.25)"><path d="M0.51 -0.64 C92.55 2.54, 183 2.13, 229.93 -0.07 M0.15 0.19 C65.69 1.02, 132.13 2.16, 229.76 -0.72 M229.11 1.89 C230.91 18.79, 228.5 38.07, 229.53 53.38 M230.66 0.83 C230.81 16.54, 229.79 34.44, 230.53 52.73 M229 53.36 C174.76 53.18, 121.58 52.1, 1.39 53.28 M230.71 52.74 C155.74 52.99, 81.01 53.08, -0.33 51.67 M0.52 53.55 C-1.36 39.47, -0.09 24.78, 0.87 1.84 M-0.64 51.9 C-0.32 36.64, 0.36 20.89, 0.12 -0.53" stroke="#364fc7" stroke-width="1" fill="none"></path></g><g transform="translate(407.5 538.75) rotate(0 69.5 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#364fc7" text-anchor="start" style="white-space: pre;" direction="ltr">start withdrawal</text></g><g stroke-linecap="round" transform="translate(321.25 299) rotate(0 31.875 23.75)"><path d="M0.91 0.47 C20.99 -1.15, 47.29 1.06, 63.61 1.5 M-0.45 -0.41 C19.5 -0.46, 40.52 -0.3, 64.3 0.46 M65.47 1.81 C63.31 12.54, 65.46 28.51, 63.36 46.29 M63.61 0.61 C62.98 11.81, 62.86 25.37, 63.81 48.33 M62.66 48.86 C43.77 48.27, 24.95 48.59, -0.58 46.58 M64.61 47.11 C49.77 47.35, 34.75 47.13, 0.07 47.88 M-0.29 48.17 C-2.19 29.17, -0.3 11.96, -0.98 1.71 M0.7 47.1 C0.04 33.01, -1.35 17.47, -0.73 -0.83" stroke="#000000" stroke-width="1" fill="none"></path></g><g stroke-linecap="round"><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M-0.49 -0.03 C-0.24 3.78, 0.68 18.59, 1.29 22.17 M1.45 -1.1 C2.08 2.97, 3.2 19.72, 3.42 23.62" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M-1.12 12.29 C-0.05 16.92, 0.53 18.56, 2.44 24.48 M-0.95 13.8 C0.07 16.86, 1.42 19.69, 3.08 23.78" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(352.60947862109833 311.65257480315864) rotate(0 1.4633860506549468 11.261301259659206)"><path d="M6.55 11.8 C5.47 16.54, 3.89 18.32, 2.44 24.48 M6.73 13.31 C5.4 16.51, 4.4 19.5, 3.08 23.78" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g stroke-linecap="round"><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M0.43 -0.55 C0.52 3.3, 1.07 19.06, 1.58 22.93 M-0.8 1.78 C-0.42 5.28, 3.19 17.38, 3.86 21.11" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M-2.85 10.97 C0.59 16.31, 2.72 18.44, 3.1 22.21 M-2.68 11.97 C0.41 15.44, 2.12 18.93, 3.78 21.23" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(337.05473931054917 184.57628740157924) rotate(0 1.5295271914883983 11.188407159857434)"><path d="M4.63 9.18 C5.18 15.24, 4.43 18.06, 3.1 22.21 M4.8 10.19 C5.06 14.27, 3.92 18.44, 3.78 21.23" stroke="#000000" stroke-width="1" fill="none"></path></g></g><g transform="translate(422.5 316.75) rotate(0 64 11.5)"><text x="0" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr">Add exchange</text></g><g stroke-linecap="round" transform="translate(76.25 424.75) rotate(0 50 25.625)"><path d="M0.77 1.74 C24.32 0.96, 44.2 -1.69, 98.65 1.57 M-0.69 0.75 C21.48 -0.76, 43.4 0.32, 100.75 -0.92 M100.71 -0.97 C99.78 15.92, 101.32 30.35, 99.88 50.27 M100.65 0.76 C99.43 14.4, 101.11 27.48, 100.96 50.81 M98.17 49.87 C65.22 52.06, 33 50.69, 1.35 50.22 M99.74 51.11 C68.13 52.11, 37.2 52.49, -0.3 50.26 M-1.09 53.01 C1.03 36.68, -0.88 21.05, -0.53 0.24 M0.79 50.4 C0.83 34.32, -0.05 16.09, 0.79 0.09" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(104.75 438.875) rotate(0 21.5 11.5)"><text x="21.5" y="17" font-family="Helvetica, Segoe UI Emoji" font-size="20px" fill="#000000" text-anchor="middle" style="white-space: pre;" direction="ltr">EUR</text></g></svg> \ No newline at end of file