commit ac92c9b8d0e1b5fda7a76cfaedbb343d44488799
parent 97a691ca5c427513abfa7af3b48c9b4f752e4029
Author: sebasjm+llm <sebasjm@numis.ar>
Date: Sat, 3 Oct 2026 16:05:03 -0300
Infer wallet-core docs based
Diffstat:
24 files changed, 9512 insertions(+), 0 deletions(-)
diff --git a/core/api-wallet-core.rst b/core/api-wallet-core.rst
@@ -0,0 +1,477 @@
+..
+ This file is part of GNU TALER.
+ Copyright (C) 2021-2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 3.0, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Florian Dold
+
+================
+Wallet-Core API
+================
+
+This chapter specifies the API that *wallet-core*, the reference GNU Taler
+wallet implementation, exposes to its clients. Clients of this API are the
+various wallet front-ends (the command-line interface, the WebExtension, the
+mobile applications and other user interfaces embedding wallet-core), as well
+as test harnesses.
+
+Unlike most other APIs in GNU Taler, this is **not** an HTTP REST API:
+wallet-core runs inside (or alongside) the client application and is driven
+via an operation-based request/response message protocol. Each request
+names an *operation* and carries operation-specific JSON arguments; the
+response either carries the operation-specific result or a structured error.
+In the documentation, we use TypeScript syntax to describe the JSON objects,
+as with the REST APIs.
+
+The reference for all operations and their payloads is the TypeScript source
+of wallet-core (``packages/taler-wallet-core/src/wallet-api-types.ts`` and
+``packages/taler-util/src/types-taler-wallet.ts``); a per-operation reference
+generated from these sources is also available in the
+:doc:`wallet-core reference </wallet/wallet-core>`.
+
+The `glossary <https://docs.taler.net/taler-developer-manual.html#developer-glossary>`_
+defines all specific terms used in this section.
+
+
+---------------
+Version History
+---------------
+
+The wallet-core API is versioned using the :ref:`libtool version range
+format <http-common>` (``current[:revision[:age]]``). The currently
+implemented protocol version is **10:0:0**, reported via the
+:ref:`getVersion <wallet-op-getVersion>` operation.
+
+**Version history:**
+
+* ``v4``: first tracked version; adds denomination-loss transactions
+* ``v5``: requests must use canonicalized base URLs
+* ``v6``: bank-integrated withdrawal via prepare/confirm steps
+* ``v7``: introduces the transaction finalizing state
+* ``v8``: removes the v1 ``preparePay`` operations
+* ``v9``: payments can be handed off to another wallet
+ (:ref:`unclaimPayment <wallet-op-unclaimPayment>` and
+ :ref:`reclaimPayment <wallet-op-reclaimPayment>`)
+* ``v10``: privacy-scrubbed diagnostics reports
+ (:ref:`getDiagnostics <wallet-op-getDiagnostics>`)
+
+
+.. _wallet-core-conventions:
+
+---------------------
+Protocol conventions
+---------------------
+
+Operations
+^^^^^^^^^^
+
+Every interaction with wallet-core starts with the client sending a
+`CoreApiRequestEnvelope`. The ``operation`` field selects the operation,
+``id`` is a client-chosen request identifier that is echoed back in the
+response (allowing multiple requests to be in flight), and ``args`` carries
+the operation-specific request payload. Operations that take no arguments
+use an empty object (`EmptyObject`).
+
+.. ts:def:: CoreApiRequestEnvelope
+
+ interface CoreApiRequestEnvelope {
+ // Client-chosen request identifier, echoed in the response.
+ id: string;
+
+ // Name of the operation, e.g. "getBalances".
+ operation: string;
+
+ // Operation-specific request payload.
+ args: unknown;
+ }
+
+.. ts:def:: EmptyObject
+
+ // Placeholder for operations without arguments or without a result.
+ type EmptyObject = Record<string, never>;
+
+Responses and errors
+^^^^^^^^^^^^^^^^^^^^
+
+A request is always answered with exactly one of the following envelopes:
+
+.. ts:def:: CoreApiResponseSuccess
+
+ interface CoreApiResponseSuccess {
+ // Distinguishes the message from errors and notifications.
+ type: "response";
+
+ // Operation that was invoked.
+ operation: string;
+
+ // Request identifier from the corresponding request.
+ id: string;
+
+ // Operation-specific result payload.
+ result: unknown;
+ }
+
+.. ts:def:: CoreApiResponseError
+
+ interface CoreApiResponseError {
+ // Distinguishes the message from responses and notifications.
+ type: "error";
+
+ // Operation that was invoked.
+ operation: string;
+
+ // Request identifier from the corresponding request.
+ id: string;
+
+ // Details about the failure.
+ error: TalerErrorDetail;
+ }
+
+The ``error`` object carries a numeric ``code`` from the
+`error code registry <error-codes>`_ plus optional details. It has the
+same structure as the `ErrorDetail` of the REST APIs (see
+:ref:`http-common`), but allows arbitrary additional fields depending on
+the error code:
+
+.. ts:def:: TalerErrorDetail
+
+ interface TalerErrorDetail {
+ // Numeric error code unique to the condition.
+ code: TalerErrorCode;
+
+ // When did the error occur.
+ when?: AbsoluteTime;
+
+ // Human-readable description of the error. May change without notice!
+ hint?: string;
+
+ // Additional fields specific to the error code.
+ [x: string]: unknown;
+ }
+
+For each operation, this specification lists the *expected* error codes:
+conditions the caller can reasonably react to inline (for example
+``WALLET_TRANSACTION_NOT_FOUND`` or
+``WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE``). Any other failure
+(network problems, protocol violations, internal errors) is also reported
+via the error envelope, but is not listed per operation.
+
+Initialization
+^^^^^^^^^^^^^^
+
+:ref:`initWallet <wallet-op-initWallet>` (or
+:ref:`setWalletRunConfig <wallet-op-setWalletRunConfig>`) must be the first
+request made to wallet-core; every other operation fails until
+initialization has completed. Once the wallet has been shut down via
+:ref:`shutdown <wallet-op-shutdown>`, every operation other than
+``shutdown`` itself fails with ``WALLET_CORE_NOT_AVAILABLE`` until
+wallet-core is restarted and initialized again.
+
+Notifications
+^^^^^^^^^^^^^
+
+In addition to responses, wallet-core spontaneously sends *notifications*
+to connected clients, for example when the balance or the state of a
+transaction changes. Notifications are not correlated with a request
+``id``. Clients should use them as a trigger to re-query state, not as an
+authoritative state transfer. See :ref:`wallet-core-notifications` for the
+list of notification types.
+
+.. ts:def:: CoreApiNotification
+
+ interface CoreApiNotification {
+ // Distinguishes the message from responses.
+ type: "notification";
+
+ // A WalletNotification object.
+ payload: unknown;
+ }
+
+
+.. _wallet-core-transports:
+
+----------
+Transports
+----------
+
+The request/response protocol above is transport-agnostic. The following
+transports are in use:
+
+**In-process.** Front-ends that link against wallet-core as a library
+obtain a ``WalletCoreApiClient`` with two methods: ``call(operation, args)``
+returns the result or throws on error, and ``callForResult(operation,
+args)`` returns expected errors (see above) as a ``Result`` value instead of
+throwing. Notifications are delivered via a registered listener.
+
+**Unix-domain socket.** ``taler-wallet-cli`` can serve the wallet-core API
+over a Unix-domain socket (by default ``~/.wallet-core.sock``), allowing
+separate processes — and thus other programming languages — to act as
+wallet-core clients. The framing protocol is line-based; JSON messages may
+span multiple lines and are wrapped in control lines that start with ``%``:
+
+.. code:: none
+
+ # On connect, both sides greet each other:
+ server> %hello-from-server
+ client> %hello-from-client
+
+ # A request is sent as:
+ client> %request
+ client> {"operation":"getBalances","id":"req-1","args":{}}
+ client> %end
+
+ # Responses and notifications are both sent as messages:
+ server> %message
+ server> {"type":"response","operation":"getBalances","id":"req-1","result":{...}}
+ server> %end
+
+ # Protocol-level errors terminate the connection:
+ server> %error: invalid message
+
+**Browser messaging.** The WebExtension front-end talks to wallet-core
+across the extension's message channel. The same request, response and
+notification envelopes are wrapped in a versioned browser RPC message; see
+:doc:`/wallet/browser-integration` for details.
+
+
+.. _wallet-core-operations:
+
+----------
+Operations
+----------
+
+The operations are grouped by topic. Unless noted otherwise, each
+operation's ``args`` must be an object of the stated request type, and a
+successful ``result`` is an object of the stated response type.
+
+
+Initialization and lifecycle
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. include:: wallet-core/init.rst
+
+Generic request management
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Long-running operations that expose a ``progressToken`` can be cancelled or
+nudged with the following requests.
+
+.. include:: wallet-core/requests.rst
+
+Hints
+^^^^^
+
+Hints inform wallet-core about the state of the host application. They do
+not query information and never return a meaningful result.
+
+.. include:: wallet-core/hints.rst
+
+Balances
+^^^^^^^^
+
+.. include:: wallet-core/balances.rst
+
+Transactions
+^^^^^^^^^^^^
+
+Every longer-running business process of the wallet (withdrawals, payments,
+refreshes, peer-to-peer transfers, deposits, ...) is represented as a
+*transaction* with a state machine. Transaction state changes are reported
+via :ref:`transaction-state-transition
+<wallet-notif-transaction-state-transition>` notifications.
+
+.. include:: wallet-core/transactions.rst
+
+Withdrawals
+^^^^^^^^^^^
+
+Withdrawal operations bring coins from an exchange into the wallet, either
+via a bank-integrated flow (the user authorizes the transfer in their
+banking application) or via a manual wire transfer to the exchange.
+
+.. include:: wallet-core/withdrawals.rst
+
+Merchant payments
+^^^^^^^^^^^^^^^^^
+
+Payment operations handle ``taler://pay/`` and ``taler://pay-template/``
+URIs as well as payments to Paivana-protected resources, and follow-up
+actions such as refund queries and payment hand-off between wallets.
+
+.. include:: wallet-core/payments.rst
+
+Deposits
+^^^^^^^^
+
+Deposit operations send coins from the wallet to a bank account, usually
+the wallet user's own account.
+
+.. include:: wallet-core/deposits.rst
+
+Peer-to-peer payments
+^^^^^^^^^^^^^^^^^^^^^
+
+Peer-to-peer payments move funds directly between two wallets. In a *push*
+payment, the sender initiates the transfer; in a *pull* payment, the
+receiver requests to be paid by the sender.
+
+.. include:: wallet-core/p2p.rst
+
+Exchange management
+^^^^^^^^^^^^^^^^^^^
+
+These operations manage the set of exchanges known to the wallet, their
+terms of service and their key material.
+
+.. include:: wallet-core/exchanges.rst
+
+Bank accounts
+^^^^^^^^^^^^^
+
+Bank accounts known to the wallet are used as targets for deposits and as
+a fallback when entering peer-to-peer payment information manually.
+
+.. include:: wallet-core/bank-accounts.rst
+
+Global currency management
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These operations manage the wallet's configuration for a global currency:
+the auditors that the wallet trusts for a currency and the exchanges
+offering it, as well as the currency specification itself.
+
+.. include:: wallet-core/global-currency.rst
+
+Tokens
+^^^^^^
+
+Token families represent discount tokens and subscription tokens obtained
+during merchant payments.
+
+.. include:: wallet-core/tokens.rst
+
+Donau
+^^^^^
+
+The *donau* (donation authority) collects donation receipts for the wallet
+user. These operations configure the donau and query donation statements.
+
+.. include:: wallet-core/donau.rst
+
+Contacts
+^^^^^^^^
+
+.. include:: wallet-core/contacts.rst
+
+Mailbox
+^^^^^^^
+
+The mailbox is used to receive ``taler://`` URIs (for example payment
+requests) from other wallet users.
+
+.. include:: wallet-core/mailbox.rst
+
+Aliases (taldir)
+^^^^^^^^^^^^^^^^
+
+Aliases map human-readable identifiers to wallet addresses via a *taldir*
+service, allowing senders to pay the wallet user without exchanging URIs
+out of band.
+
+.. include:: wallet-core/taldir.rst
+
+Database management
+^^^^^^^^^^^^^^^^^^^
+
+These operations back up, restore, migrate and clear the wallet database.
+
+.. include:: wallet-core/database.rst
+
+Data validation and conversion
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Utility operations to validate and convert between data formats used by
+the front-ends.
+
+.. include:: wallet-core/validation.rst
+
+Diagnostics
+^^^^^^^^^^^
+
+.. include:: wallet-core/diagnostics.rst
+
+Testing and debugging
+^^^^^^^^^^^^^^^^^^^^^
+
+These operations are only meant for integration tests and developer
+experiments. They are not part of the API surface a production front-end
+should rely on.
+
+.. include:: wallet-core/testing.rst
+
+
+.. _wallet-core-notifications:
+
+-------------
+Notifications
+-------------
+
+Notifications are sent by wallet-core to all connected clients whenever
+relevant state changes. The ``payload`` of a `CoreApiNotification` is a
+`WalletNotification`, a discriminated union on the ``type`` field. Clients
+should treat notifications as hints to re-query the affected state; the
+notification contents are deliberately minimal and must not be relied upon
+as an authoritative state transfer.
+
+.. ts:def:: WalletNotification
+
+ type WalletNotification =
+ | CoinRecoveryProgressNotification
+ | BalanceChangeNotification
+ | BankAccountChangeNotification
+ | BackupOperationErrorNotification
+ | ContactAddedNotification
+ | ContactDeletedNotification
+ | MailboxMessageAddedNotification
+ | MailboxMessageDeletedNotification
+ | ExchangeStateTransitionNotification
+ | TransactionStateTransitionNotification
+ | TaskProgressNotification
+ | RequestObservabilityEventNotification
+ | IdleNotification
+ | RequestProgressNotification
+ | RequestProgressPhaseNotification
+ | DatabaseMaintenanceProgressNotification;
+
+.. ts:def:: NotificationType
+
+ enum NotificationType {
+ CoinRecoveryProgress = "coin-recovery-progress",
+ BalanceChange = "balance-change",
+ BankAccountChange = "bank-account-change",
+ BackupOperationError = "backup-error",
+ ContactAdded = "contact-added",
+ ContactDeleted = "contact-deleted",
+ MailboxMessageAdded = "mailbox-message-added",
+ MailboxMessageDeleted = "mailbox-message-deleted",
+ TransactionStateTransition = "transaction-state-transition",
+ ExchangeStateTransition = "exchange-state-transition",
+ Idle = "idle",
+ TaskObservabilityEvent = "task-observability-event",
+ RequestObservabilityEvent = "request-observability-event",
+ RequestProgressError = "request-progress-error",
+ RequestProgressPhase = "request-progress-phase",
+ DatabaseMaintenanceProgress = "database-maintenance-progress",
+ }
+
+.. include:: wallet-core/notifications.rst
diff --git a/core/index.rst b/core/index.rst
@@ -40,6 +40,7 @@ describe the JSON objects used in our REST APIs.
api-common
api-exchange
api-merchant
+ api-wallet-core
../wallet/wallet-core
api-auditor
api-sync
diff --git a/core/wallet-core/balances.rst b/core/wallet-core/balances.rst
@@ -0,0 +1,292 @@
+.. _wallet-op-getBalances:
+
+**getBalances**
+
+Get current wallet balance.
+
+Balances are reported per *scope* (`ScopeInfo`): funds held globally for
+a currency, at a particular exchange, under a particular auditor or under
+a superseded exchange master key are reported as separate balances.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `BalancesResponse` object.
+
+**Details:**
+
+ Each balance distinguishes three amounts. ``available`` is the
+ balance available for spending from transactions in their final state,
+ plus amounts expected to become available from pending refreshes.
+ ``pendingIncoming`` is the expected positive delta to the available
+ balance once pending operations (such as withdrawals or incoming peer
+ payments) reach the "done" state. ``pendingOutgoing`` is the amount
+ currently allocated to spend operations that could still be aborted,
+ in which case part of the amount may be recovered.
+
+.. ts:def:: BalancesResponse
+
+ interface BalancesResponse {
+ // Electronic cash balances, per currency scope.
+ balances: WalletBalance[];
+
+ // Does the user have money from an exchange other than demo or test?
+ haveProdBalance: boolean;
+
+ // Summary of donations, per donau/year/currency.
+ donauSummary?: DonauSummaryItem[];
+ }
+
+.. ts:def:: WalletBalance
+
+ interface WalletBalance {
+ // DD71 expiry information and conditional
+ // cost of keeping this balance.
+ refreshInfo?: WalletRefreshInfo;
+
+ // Scope of the funds covered by this balance.
+ scopeInfo: ScopeInfo;
+
+ // Balance available for spending, including amounts
+ // expected from pending refreshes.
+ available: AmountString;
+
+ // Expected positive delta to the available balance
+ // from pending operations.
+ pendingIncoming: AmountString;
+
+ // Amount allocated to spend operations that could still be aborted.
+ pendingOutgoing: AmountString;
+
+ // Pending KYC or confirmation steps affecting this balance.
+ flags: BalanceFlag[];
+
+ // Available URLs for pages that list
+ // where money in this scope can be spent.
+ shoppingUrls?: string[];
+
+ // Are p2p payments disabled for this scope?
+ disablePeerPayments?: boolean;
+
+ // Are wallet deposits disabled for this scope?
+ disableDirectDeposits?: boolean;
+ }
+
+.. ts:def:: WalletRefreshInfo
+
+ interface WalletRefreshInfo {
+ risks: CashExpirationRisk[];
+ recoveries: CashRenewalNotice[];
+ annualCostBound: AnnualRefreshCostBound;
+ }
+
+.. ts:def:: CashExpirationRisk
+
+ interface CashExpirationRisk {
+ exchangeBaseUrl: string;
+ exchangeMasterPub: string;
+ amount: AmountString;
+ earliestDepositExpiration: TalerProtocolTimestamp;
+ reason:
+ | "pending"
+ | "connectivity"
+ | "exchange-error"
+ | "no-replacement"
+ | "checking"
+ | "invalid-lifetime";
+ }
+
+.. ts:def:: CashRenewalNotice
+
+ interface CashRenewalNotice {
+ warningId: string;
+ exchangeBaseUrl: string;
+ exchangeMasterPub: string;
+ amount: AmountString;
+ oldDepositExpiration: TalerProtocolTimestamp;
+ newDepositExpiration: TalerProtocolTimestamp;
+ // Earliest emergency threshold of the renewed coins.
+ nextRelevantDate: TalerProtocolTimestamp;
+ }
+
+.. ts:def:: AnnualRefreshCostBound
+
+ // Conditional on stable, continuously available compatible
+ // offerings and timely reveal.
+ type AnnualRefreshCostBound = {
+ horizonDays: 365;
+ projection: "stable-current-offerings";
+ } & (
+ | {
+ status: "available";
+ amount: AmountString;
+ }
+ | {
+ status: "unavailable";
+ reasons: string[];
+ }
+ );
+
+.. ts:def:: ScopeInfo
+
+ // Scope of a balance; identifies the trust domain
+ // the funds belong to.
+ type ScopeInfo =
+ | ScopeInfoGlobal
+ | ScopeInfoExchange
+ | ScopeInfoAuditor
+ | ScopeInfoExchangeLegacyKeys;
+
+.. ts:def:: ScopeInfoGlobal
+
+ // Funds held with an exchange that is globally
+ // trusted for the currency.
+ type ScopeInfoGlobal = {
+ type: "global";
+ currency: string;
+ };
+
+.. ts:def:: ScopeInfoExchange
+
+ // Funds held at one particular exchange.
+ type ScopeInfoExchange = {
+ type: "exchange";
+ currency: string;
+ url: string;
+ };
+
+.. ts:def:: ScopeInfoAuditor
+
+ // Funds whose denominations are audited by a globally
+ // trusted auditor.
+ type ScopeInfoAuditor = {
+ type: "auditor";
+ currency: string;
+ url: string;
+ };
+
+.. ts:def:: ScopeInfoExchangeLegacyKeys
+
+ // Funds issued under a master public key that the exchange has
+ // since replaced; never pooled with funds under the current key.
+ type ScopeInfoExchangeLegacyKeys = {
+ type: "exchange-legacy-keys";
+ currency: string;
+ url: string;
+ // The superseded key the funds were issued under.
+ masterPub: string;
+ };
+
+.. ts:def:: BalanceFlag
+
+ // Flag marking a pending KYC, AML or confirmation step for
+ // the incoming or outgoing funds of a balance.
+ type BalanceFlag =
+ | "incoming-kyc"
+ | "incoming-aml"
+ | "incoming-confirmation"
+ | "outgoing-kyc";
+
+.. ts:def:: DonauSummaryItem
+
+ interface DonauSummaryItem {
+ // Base URL of the donau service.
+ donauBaseUrl: string;
+
+ // Legal domain of the donau service (if available).
+ legalDomain?: string;
+
+ // Year of the donation(s).
+ year: number;
+
+ // Sum of donation receipts received from merchants
+ // in the applicable year.
+ amountReceiptsAvailable: AmountString;
+
+ // Sum of donation receipts already submitted to the
+ // donau in the applicable year.
+ amountReceiptsSubmitted: AmountString;
+
+ // Amount of the latest available statement. Missing
+ // if no statement was requested yet.
+ amountStatement?: AmountString;
+ }
+
+
+.. _wallet-op-getBalanceDetail:
+
+**getBalanceDetail**
+
+Get detailed balance information for one currency.
+
+Unlike :ref:`getBalances <wallet-op-getBalances>`, which reports one
+balance per scope, this operation aggregates the wallet's funds in the
+given currency across all exchanges known to the wallet and reports how
+much of the balance is spendable under progressively stricter criteria.
+
+**Request:**
+
+ The request ``args`` must be a `GetBalanceDetailRequest` object.
+
+**Response:**
+
+ On success, the result is a `PaymentBalanceDetails` object.
+
+**Details:**
+
+ ``balanceAvailable`` covers funds available for spending, including
+ amounts expected from pending refreshes. ``balanceMaterial`` is the
+ balance the wallet believes it could spend right now, without waiting
+ for any operations to complete. The remaining balances are subsets
+ of the material balance: ``balanceAgeAcceptable`` applies an age
+ restriction, the ``balanceReceiver*Acceptable`` balances restrict to
+ funds that a receiver accepts based on exchange URL, exchange public
+ key or auditor URL, and the depositable balances additionally require
+ that the funds can be deposited via a supported wire method.
+
+.. ts:def:: GetBalanceDetailRequest
+
+ interface GetBalanceDetailRequest {
+ // Currency to compute the balance details for.
+ currency: string;
+ }
+
+.. ts:def:: PaymentBalanceDetails
+
+ interface PaymentBalanceDetails {
+ // Balance of type "available" (see details above).
+ balanceAvailable: AmountJson;
+
+ // Balance of type "material" (see details above).
+ balanceMaterial: AmountJson;
+
+ // Balance of type "age-acceptable" (see details above).
+ balanceAgeAcceptable: AmountJson;
+
+ // Balance of type "receiver-acceptable" (see details above).
+ // Deprecated, use the balanceReceiver*Acceptable balances instead.
+ balanceReceiverAcceptable: AmountJson;
+
+ // Balance of type "receiver-exchange-url-acceptable".
+ balanceReceiverExchangeUrlAcceptable: AmountJson;
+
+ // Balance of type "receiver-exchange-pub-acceptable".
+ balanceReceiverExchangePubAcceptable: AmountJson;
+
+ // Balance of type "receiver-auditor-url-acceptable".
+ balanceReceiverAuditorUrlAcceptable: AmountJson;
+
+ // Balance of type "receiver-depositable".
+ balanceReceiverDepositable: AmountJson;
+
+ // Balance that is depositable with the exchange, reduced by the
+ // exchange's debit restrictions and wire fee configuration.
+ balanceExchangeDepositable: AmountJson;
+
+ // Estimated maximum amount that the wallet could pay for,
+ // under the assumption that the merchant pays absolutely no fees.
+ maxMerchantEffectiveDepositAmount: AmountJson;
+ }
diff --git a/core/wallet-core/bank-accounts.rst b/core/wallet-core/bank-accounts.rst
@@ -0,0 +1,162 @@
+.. _wallet-op-listBankAccounts:
+
+**listBankAccounts**
+
+List bank accounts known to the wallet from previous withdrawals.
+
+**Request:**
+
+ The request must be a `ListBankAccountsRequest` object.
+
+**Response:**
+
+ On success, the result is a `ListBankAccountsResponse` object.
+
+**Details:**
+
+ When ``currency`` is specified, only accounts that support the
+ currency are returned; accounts whose supported currencies are
+ unknown are always included.
+
+.. ts:def:: ListBankAccountsRequest
+
+ interface ListBankAccountsRequest {
+ currency?: string;
+ }
+
+.. ts:def:: ListBankAccountsResponse
+
+ interface ListBankAccountsResponse {
+ accounts: WalletBankAccountInfo[];
+ }
+
+.. ts:def:: WalletBankAccountInfo
+
+ interface WalletBankAccountInfo {
+ bankAccountId: string;
+
+ paytoUri: string;
+
+ // Did we previously complete a KYC process for this bank account?
+ // Deprecated: the KYC may have been completed for one exchange
+ // but not for another.
+ kycCompleted: boolean;
+
+ // Currencies supported by the bank, if known.
+ currencies: string[] | undefined;
+
+ label: string | undefined;
+ }
+
+
+.. _wallet-op-getBankAccountById:
+
+**getBankAccountById**
+
+Get a known bank account by its identifier.
+
+**Request:**
+
+ The request must be a `GetBankAccountByIdRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetBankAccountByIdResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_BANK_ACCOUNT_NOT_FOUND``.
+
+.. ts:def:: GetBankAccountByIdRequest
+
+ interface GetBankAccountByIdRequest {
+ bankAccountId: string;
+ }
+
+.. ts:def:: GetBankAccountByIdResponse
+
+ type GetBankAccountByIdResponse = WalletBankAccountInfo;
+
+
+.. _wallet-op-addBankAccount:
+
+**addBankAccount**
+
+Add a bank account to the wallet's list of known bank accounts.
+
+**Request:**
+
+ The request must be an `AddBankAccountRequest` object.
+
+**Response:**
+
+ On success, the result is an `AddBankAccountResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``GENERIC_PAYTO_URI_MALFORMED``, ``WALLET_BANK_ACCOUNT_NOT_FOUND``.
+
+**Details:**
+
+ If an account with the same ``paytoUri`` is already known, it is
+ updated (the supported currencies are merged) and its identifier
+ is returned. When ``replaceBankAccountId`` is specified, the
+ account with that identifier is replaced and keeps its identifier;
+ the request fails with ``WALLET_BANK_ACCOUNT_NOT_FOUND`` when no
+ such account exists. A ``bank-account-change`` notification is
+ emitted.
+
+.. ts:def:: AddBankAccountRequest
+
+ interface AddBankAccountRequest {
+ // Payto URI of the bank account that should be added.
+ paytoUri: string;
+
+ // Human-readable label for the account.
+ label: string;
+
+ // Currencies supported by the bank (if known).
+ currencies?: string[] | undefined;
+
+ // Bank account that this new account should replace.
+ replaceBankAccountId?: string;
+ }
+
+.. ts:def:: AddBankAccountResponse
+
+ interface AddBankAccountResponse {
+ // Identifier of the added bank account.
+ bankAccountId: string;
+ }
+
+
+.. _wallet-op-forgetBankAccount:
+
+**forgetBankAccount**
+
+Remove a known bank account.
+
+**Request:**
+
+ The request must be a `ForgetBankAccountRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object (`EmptyObject`).
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_BANK_ACCOUNT_NOT_FOUND``.
+
+**Details:**
+
+ A ``bank-account-change`` notification is emitted.
+
+.. ts:def:: ForgetBankAccountRequest
+
+ interface ForgetBankAccountRequest {
+ bankAccountId: string;
+ }
diff --git a/core/wallet-core/contacts.rst b/core/wallet-core/contacts.rst
@@ -0,0 +1,96 @@
+.. _wallet-op-addContact:
+
+**addContact**
+
+Add a contact to the wallet's contact list. Contacts are identified by
+the pair of ``alias`` and ``aliasType``; if a contact with the same
+alias and alias type already exists, it is updated. After the contact
+list has changed, wallet-core emits a ``contact-added`` notification.
+
+**Request:**
+
+ The request arguments are an `AddContactRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: AddContactRequest
+
+ interface AddContactRequest {
+ // The contact to add or update.
+ contact: ContactEntry;
+ }
+
+.. ts:def:: ContactEntry
+
+ interface ContactEntry {
+ // Contact alias.
+ alias: string;
+
+ // Alias type.
+ aliasType: string;
+
+ // Mailbox URI.
+ mailboxBaseUri: string;
+
+ // Mailbox identity.
+ mailboxAddress: HashCodeString;
+
+ // The source of this contact, may be a URI.
+ source: string;
+
+ // The local petname of the contact.
+ petname: string;
+ }
+
+
+.. _wallet-op-deleteContact:
+
+**deleteContact**
+
+Delete a contact from the wallet's contact list. After the contact
+list has changed, wallet-core emits a ``contact-deleted`` notification.
+
+**Request:**
+
+ The request arguments are a `DeleteContactRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ Only the ``alias`` and ``aliasType`` fields of the given contact are
+ used to identify the contact to delete; the remaining fields are
+ ignored. Deleting a contact that does not exist is not an error.
+
+.. ts:def:: DeleteContactRequest
+
+ interface DeleteContactRequest {
+ // The contact to delete.
+ contact: ContactEntry;
+ }
+
+
+.. _wallet-op-getContacts:
+
+**getContacts**
+
+Get all contacts from the wallet's contact list.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `ContactListResponse` object.
+
+.. ts:def:: ContactListResponse
+
+ interface ContactListResponse {
+ // All contacts stored in the wallet.
+ contacts: ContactEntry[];
+ }
diff --git a/core/wallet-core/database.rst b/core/wallet-core/database.rst
@@ -0,0 +1,239 @@
+.. _wallet-op-importDb:
+
+**importDb**
+
+Import a wallet database dump, replacing the current database
+contents.
+
+**Request:**
+
+ The request arguments must be an `ImportDbRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_DB_BACKEND_UNSUPPORTED``, ``WALLET_CORE_API_BAD_REQUEST``,
+ ``WALLET_CORE_REQUEST_CANCELLED``.
+
+**Details:**
+
+ The format of ``dump`` (JSON or SQLite) is detected automatically.
+ A ``dump`` that does not look like a valid wallet database dump is
+ rejected with ``WALLET_CORE_API_BAD_REQUEST``; if the active
+ database backend cannot import the dump's format, the request fails
+ with ``WALLET_DB_BACKEND_UNSUPPORTED``. When ``progressToken`` is
+ set, progress is reported via notifications and the import can be
+ cancelled with
+ :ref:`cancelProgressToken <wallet-op-cancelProgressToken>`; a
+ cancelled import fails with ``WALLET_CORE_REQUEST_CANCELLED``.
+
+.. ts:def:: ImportDbRequest
+
+ interface ImportDbRequest {
+ dump?: any;
+
+ // Correlates progress notifications and allows cancellation.
+ progressToken?: string;
+ }
+
+
+.. _wallet-op-exportDb:
+
+**exportDb**
+
+Export the wallet database's contents to JSON.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an unspecified JSON value.
+
+
+.. _wallet-op-exportDbToFile:
+
+**exportDbToFile**
+
+Export the database to a file. The target directory must already
+exist.
+
+**Request:**
+
+ The request arguments must be an `ExportDbToFileRequest` object.
+
+**Response:**
+
+ On success, the result is an `ExportDbToFileResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_DB_BACKEND_UNSUPPORTED``.
+
+**Details:**
+
+ The request fails with ``WALLET_DB_BACKEND_UNSUPPORTED`` if the
+ active database backend cannot export the database to a file.
+
+.. ts:def:: ExportDbToFileRequest
+
+ interface ExportDbToFileRequest {
+ // Directory that the DB should be exported into.
+ directory: string;
+
+ // Stem of the exported DB filename. The final name will be
+ // ${directory}/${stem}.${extension}, where the extension depends
+ // on the used DB backend.
+ stem: string;
+
+ // Force the format of the export. Supported values on
+ // filesystem-capable hosts are "json" and "sqlite3"; if omitted,
+ // the host's default is "sqlite3".
+ forceFormat?: string;
+ }
+
+.. ts:def:: ExportDbToFileResponse
+
+ interface ExportDbToFileResponse {
+ // Full path to the backup.
+ path: string;
+ }
+
+
+.. _wallet-op-importDbFromFile:
+
+**importDbFromFile**
+
+Import the database from a JSON or SQLite file. **Caution:** this
+overrides existing data.
+
+**Request:**
+
+ The request arguments must be an `ImportDbFromFileRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_DB_BACKEND_UNSUPPORTED``, ``WALLET_CORE_API_BAD_REQUEST``,
+ ``WALLET_CORE_REQUEST_CANCELLED``.
+
+**Details:**
+
+ The ``path`` must end in ``.json`` or ``.sqlite3``; other files are
+ rejected with ``WALLET_CORE_API_BAD_REQUEST``, as is a file that
+ cannot be read. The ``progressToken`` behaves as for
+ :ref:`importDb <wallet-op-importDb>`.
+
+.. ts:def:: ImportDbFromFileRequest
+
+ interface ImportDbFromFileRequest {
+ // Full path to a .json or .sqlite3 backup.
+ path: string;
+
+ // Correlates progress notifications and allows cancellation.
+ progressToken?: string;
+ }
+
+
+.. _wallet-op-migrateDatabase:
+
+**migrateDatabase**
+
+Explicitly migrate an IndexedDB-emulation wallet to the native SQLite
+schema. The operation is idempotent when the wallet is already
+native.
+
+**Request:**
+
+ The request arguments must be a `MigrateDatabaseRequest` object.
+
+**Response:**
+
+ On success, the result is a `MigrateDatabaseResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_DB_BACKEND_UNSUPPORTED``, ``WALLET_DB_UNAVAILABLE``,
+ ``WALLET_CORE_REQUEST_CANCELLED``.
+
+**Details:**
+
+ When the wallet already uses the native SQLite schema, the request
+ succeeds with ``migrated`` set to false. The optional
+ ``progressToken`` correlates progress notifications and allows
+ cancellation via
+ :ref:`cancelProgressToken <wallet-op-cancelProgressToken>`.
+
+.. ts:def:: MigrateDatabaseRequest
+
+ interface MigrateDatabaseRequest {
+ // Enables progress correlation and cancellation through
+ // cancelProgressToken.
+ progressToken?: string;
+ }
+
+.. ts:def:: MigrateDatabaseResponse
+
+ interface MigrateDatabaseResponse {
+ // Whether this request changed the active database backend.
+ migrated: boolean;
+
+ // Database backend active after the request.
+ databaseBackend: WalletDatabaseBackend;
+ }
+
+.. ts:def:: WalletDatabaseBackend
+
+ type WalletDatabaseBackend = "indexeddb" | "sqlite";
+
+
+.. _wallet-op-clearDb:
+
+**clearDb**
+
+Dangerously clear the whole wallet database.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ In addition to the database contents, all in-memory caches and the
+ recorded flight records are cleared, and the background task
+ scheduler is reloaded.
+
+
+.. _wallet-op-recycle:
+
+**recycle**
+
+Export a backup, clear the database and re-import it.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ This operation is declared but not implemented yet: every request
+ currently fails with ``GENERIC_FEATURE_NOT_IMPLEMENTED``.
diff --git a/core/wallet-core/deposits.rst b/core/wallet-core/deposits.rst
@@ -0,0 +1,421 @@
+.. _wallet-op-checkDeposit:
+
+**checkDeposit**
+
+Check whether a deposit of the given amount to the given target account
+is possible with the funds in the wallet, and calculate the associated
+fees, without creating a deposit transaction.
+
+**Request:**
+
+ The request body must be a `CheckDepositRequest` object.
+
+**Response:**
+
+ On success, the result is a `CheckDepositResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``GENERIC_PAYTO_URI_MALFORMED``, ``WALLET_NO_SUITABLE_EXCHANGE``,
+ ``WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE``.
+
+**Details:**
+
+ ``totalDepositCost`` is the total cost to the wallet: the
+ contributions of the selected coins plus the cost of refreshing any
+ change. ``effectiveDepositAmount`` is the amount expected to be
+ wired to the destination account (not considering aggregation).
+
+.. ts:def:: CheckDepositRequest
+
+ interface CheckDepositRequest {
+ // Payto URI to identify the (bank) account that the exchange will
+ // wire the money to.
+ depositPaytoUri: string;
+
+ // Instructed amount used for deposit coin selection. The response
+ // reports the resulting wallet cost and destination amount, which
+ // can differ because of fees and refresh change.
+ amount: AmountString;
+
+ // Restrict the deposit to a certain scope.
+ restrictScope?: ScopeInfo;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: CheckDepositResponse
+
+ interface CheckDepositResponse {
+ totalDepositCost: AmountString;
+ effectiveDepositAmount: AmountString;
+ fees: DepositGroupFees;
+
+ kycSoftLimit?: AmountString;
+ kycHardLimit?: AmountString;
+
+ // Base URL of exchanges that would likely require soft KYC.
+ kycExchanges?: string[];
+ }
+
+.. ts:def:: DepositGroupFees
+
+ interface DepositGroupFees {
+ // Deposit fees of the selected coins.
+ coin: AmountString;
+
+ // Wire fees of the involved exchanges.
+ wire: AmountString;
+
+ // Cost of refreshing change from the selected coins.
+ refresh: AmountString;
+ }
+
+
+.. _wallet-op-createDepositGroup:
+
+**createDepositGroup**
+
+Create a new deposit group. Deposit groups are used to deposit multiple
+coins to a bank account, usually the wallet user's own bank account.
+
+The deposit is executed asynchronously as a transaction; the response
+returns the transaction identifier and its initial state. The fees of
+the deposit can be reviewed beforehand with :ref:`checkDeposit
+<wallet-op-checkDeposit>`.
+
+**Request:**
+
+ The request body must be a `CreateDepositGroupRequest` object.
+
+**Response:**
+
+ On success, the result is a `CreateDepositGroupResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``GENERIC_PAYTO_URI_MALFORMED``, ``WALLET_NO_SUITABLE_EXCHANGE``,
+ ``WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE``,
+ ``WALLET_KYC_LIMIT_EXCEEDED``.
+
+.. ts:def:: CreateDepositGroupRequest
+
+ interface CreateDepositGroupRequest {
+ depositPaytoUri: string;
+
+ // Instructed amount used for deposit coin selection.
+ amount: AmountString;
+
+ // Restrict the deposit to a certain scope.
+ restrictScope?: ScopeInfo;
+
+ // Use a fixed merchant private key.
+ testingFixedPriv?: string;
+
+ // Optional wire deadline for the deposits.
+ wireDeadline?: TalerProtocolTimestamp;
+
+ // Pre-allocated transaction ID. Allows clients to easily handle
+ // notifications that occur while the operation has been created
+ // but before the creation request has returned.
+ transactionId?: TransactionIdStr;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: CreateDepositGroupResponse
+
+ // Response to a createDepositGroup request.
+ interface CreateDepositGroupResponse {
+ // Transaction ID of the newly created deposit transaction.
+ transactionId: TransactionIdStr;
+
+ // Current state of the new deposit transaction. Returned as a
+ // performance optimization, so that the UI doesn't have to do a
+ // separate getTransactionById.
+ txState: TransactionState;
+
+ // Deprecated, use transactionId instead.
+ depositGroupId: string;
+ }
+
+
+.. _wallet-op-convertDepositAmount:
+
+**convertDepositAmount**
+
+This operation is **deprecated**. Use :ref:`checkDeposit
+<wallet-op-checkDeposit>` for a concrete instructed amount, or
+:ref:`getMaxDepositAmount <wallet-op-getMaxDepositAmount>` to query
+deposit limits.
+
+Compute the effective and raw amounts for a deposit of the given
+instructed amount, based on the coins currently available in the
+wallet. The ``type`` field (`TransactionAmountMode`) selects whether
+``amount`` is interpreted as an effective or as a raw amount.
+
+**Request:**
+
+ The request body must be a `ConvertAmountRequest` object.
+
+**Response:**
+
+ On success, the result is an `AmountResponse` object.
+
+.. ts:def:: ConvertAmountRequest
+
+ // Deprecated: use CheckDepositRequest for a concrete instructed
+ // amount, or GetMaxDepositAmountRequest to query deposit limits.
+ interface ConvertAmountRequest {
+ amount: AmountString;
+ type: TransactionAmountMode;
+ depositPaytoUri: PaytoString;
+ }
+
+.. ts:def:: AmountResponse
+
+ interface AmountResponse {
+ effectiveAmount: AmountString;
+ rawAmount: AmountString;
+ }
+
+
+.. _wallet-op-getMaxDepositAmount:
+
+**getMaxDepositAmount**
+
+Query the maximum amount that can currently be deposited in the given
+currency.
+
+**Request:**
+
+ The request body must be a `GetMaxDepositAmountRequest` object. When
+ ``depositPaytoUri`` is omitted, wire-method eligibility, account
+ restrictions and wire fees cannot be reflected in the response.
+
+**Response:**
+
+ On success, the result is a `GetMaxDepositAmountResponse` object.
+
+**Details:**
+
+ The result distinguishes the maximum that can be deposited
+ immediately (``material``) from the maximum that also includes the
+ expected outputs of pending refresh operations (``available``).
+ ``exchangeDiagnostics`` gives, for every ready same-currency
+ exchange, the per-exchange maximums and the reasons why the exchange
+ cannot serve the deposit.
+
+.. ts:def:: GetMaxDepositAmountRequest
+
+ interface GetMaxDepositAmountRequest {
+ // Currency to deposit.
+ currency: string;
+
+ // Target bank account to deposit into. When omitted, wire-method
+ // eligibility, account restrictions and wire fees cannot be
+ // reflected in the response.
+ depositPaytoUri?: string;
+
+ // Restrict the deposit to a certain scope.
+ restrictScope?: ScopeInfo;
+ }
+
+.. ts:def:: GetMaxDepositAmountResponse
+
+ interface GetMaxDepositAmountResponse {
+ // Maximum that can be deposited immediately.
+ material: DepositMaximum;
+
+ // Maximum including expected outputs of pending refresh
+ // operations.
+ available: DepositMaximum;
+
+ // Eligibility and maximum amounts for every ready same-currency
+ // exchange.
+ exchangeDiagnostics: Record<string, DepositExchangeDiagnostics>;
+ }
+
+.. ts:def:: DepositMaximum
+
+ // Maximum amounts and fees for one coherent deposit coin selection.
+ interface DepositMaximum {
+ // Gross target amount passed to checkDeposit or
+ // createDepositGroup.
+ instructedAmount: AmountString;
+
+ // Total balance effect on the wallet: instructed amount plus fees
+ // paid by the customer and the cost of refreshing any change.
+ effectiveAmount: AmountString;
+
+ // Amount expected to reach the destination account: instructed
+ // amount minus fees covered by the counterparty.
+ rawAmount: AmountString;
+
+ // Total fees incurred by this deposit selection.
+ fees: DepositGroupFees;
+ }
+
+.. ts:def:: DepositExchangeDiagnostics
+
+ interface DepositExchangeDiagnostics {
+ // Maximum that can be deposited immediately.
+ material: DepositMaximum;
+
+ // Maximum including expected outputs of pending refresh
+ // operations.
+ available: DepositMaximum;
+
+ // Eligibility failures, in deterministic evaluation order.
+ reasons: DepositEligibilityReason[];
+ }
+
+.. ts:def:: DepositEligibilityReason
+
+ // Reason why a ready, same-currency exchange cannot serve a deposit.
+ type DepositEligibilityReason =
+ | { type: "direct-deposit-disabled" }
+ | { type: "scope-restricted"; scopeInfo: ScopeInfo }
+ | { type: "wire-method-unsupported"; wireMethod: string }
+ | { type: "wire-fee-unavailable"; wireMethod: string }
+ | {
+ type: "deposit-account-restricted";
+ wireMethod: string;
+ accountRestrictions: Record<string, AccountRestriction[]>;
+ };
+
+.. ts:def:: DepositEligibilityReasonType
+
+ type DepositEligibilityReasonType =
+ "direct-deposit-disabled"
+ | "scope-restricted"
+ | "wire-method-unsupported"
+ | "wire-fee-unavailable"
+ | "deposit-account-restricted";
+
+.. ts:def:: AccountRestriction
+
+ type AccountRestriction =
+ | RegexAccountRestriction
+ | DenyAllAccountRestriction;
+
+.. ts:def:: RegexAccountRestriction
+
+ // Accounts interacting with this type of account restriction must
+ // have a payto://-URI matching the given regex.
+ interface RegexAccountRestriction {
+ type: "regex";
+
+ // Regular expression that the payto://-URI of the partner account
+ // must follow (posix-egrep, without support for character
+ // classes, GNU extensions, back-references or intervals).
+ payto_regex: string;
+
+ // Hint for a human to understand the restriction.
+ human_hint: string;
+
+ // Map from IETF BCP 47 language tags to localized human hints.
+ human_hint_i18n?: InternationalizedString;
+ }
+
+.. ts:def:: DenyAllAccountRestriction
+
+ interface DenyAllAccountRestriction {
+ type: "deny";
+ }
+
+
+.. _wallet-op-getDepositWireTypes:
+
+**getDepositWireTypes**
+
+Get the wire types that can be used as the target of a deposit,
+together with per-type details. The result is derived from the wire
+accounts of the exchanges known to the wallet; accounts with a
+deny-type debit restriction are excluded. When ``currency`` is given,
+only exchanges for that currency are considered.
+
+**Request:**
+
+ The request body must be a `GetDepositWireTypesRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetDepositWireTypesResponse` object.
+
+.. ts:def:: GetDepositWireTypesRequest
+
+ interface GetDepositWireTypesRequest {
+ currency?: string;
+
+ // Optional scope info to further restrict the result.
+ // Currency must match the currency field.
+ scopeInfo?: ScopeInfo;
+ }
+
+.. ts:def:: GetDepositWireTypesResponse
+
+ interface GetDepositWireTypesResponse {
+ // Details for each wire type.
+ wireTypeDetails: WireTypeDetails[];
+ }
+
+.. ts:def:: WireTypeDetails
+
+ interface WireTypeDetails {
+ paymentTargetType: string;
+
+ // Only applicable for payment target type IBAN. Specifies
+ // whether the user wants to preferably enter their bank account
+ // details as an IBAN or as a BBAN. Mandatory for
+ // paymentTargetType="iban".
+ preferredEntryType?: "iban" | "bban";
+
+ // Allowed hostnames for the deposit payto URI. Only applicable to
+ // x-taler-bank.
+ talerBankHostnames?: string[];
+ }
+
+
+.. _wallet-op-getDepositWireTypesForCurrency:
+
+**getDepositWireTypesForCurrency**
+
+This operation is **deprecated**. Use :ref:`getDepositWireTypes
+<wallet-op-getDepositWireTypes>` instead.
+
+Get wire types that can be used for a deposit operation with the
+provided currency.
+
+**Request:**
+
+ The request body must be a `GetDepositWireTypesForCurrencyRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a `GetDepositWireTypesForCurrencyResponse`
+ object.
+
+.. ts:def:: GetDepositWireTypesForCurrencyRequest
+
+ interface GetDepositWireTypesForCurrencyRequest {
+ currency: string;
+
+ // Optional scope info to further restrict the result.
+ // Currency must match the currency field.
+ scopeInfo?: ScopeInfo;
+ }
+
+.. ts:def:: GetDepositWireTypesForCurrencyResponse
+
+ // Response with wire types that are supported for a deposit.
+ interface GetDepositWireTypesForCurrencyResponse {
+ // Deprecated, use wireTypeDetails instead.
+ wireTypes: string[];
+
+ // Details for each wire type.
+ wireTypeDetails: WireTypeDetails[];
+ }
diff --git a/core/wallet-core/diagnostics.rst b/core/wallet-core/diagnostics.rst
@@ -0,0 +1,102 @@
+.. _wallet-op-getDiagnostics:
+
+**getDiagnostics**
+
+Get a diagnostics report about the state of the wallet, serialized as
+a string in the requested format.
+
+**Request:**
+
+ The request arguments must be a `GetDiagnosticsRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetDiagnosticsResponse`: the
+ diagnostics report serialized as a string.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ The report is privacy-scrubbed: transaction identifiers are replaced
+ by synthetic labels. It contains the wallet-core version, the
+ database backend with record counts, the known exchanges, coins,
+ bank accounts and the newest transactions (at most
+ ``transactionLimit`` of them). The ``WALLET_CORE_API_BAD_REQUEST``
+ error is raised when ``transactionLimit`` is not a non-negative
+ safe integer.
+
+.. ts:def:: GetDiagnosticsRequest
+
+ interface GetDiagnosticsRequest {
+ // Serialized output format. Defaults to JSON.
+ format?: DiagnosticsFormat;
+
+ // Maximum number of newest transactions to include. Defaults to 100.
+ transactionLimit?: number;
+
+ // Information supplied by the wallet frontend invoking wallet-core.
+ frontendInfo?: DiagnosticsFrontendInfo;
+ }
+
+.. ts:def:: DiagnosticsFormat
+
+ type DiagnosticsFormat = "json" | "yaml";
+
+.. ts:def:: DiagnosticsFrontendInfo
+
+ interface DiagnosticsFrontendInfo {
+ name: string;
+ version: string;
+ platform?: string;
+ }
+
+.. ts:def:: GetDiagnosticsResponse
+
+ // A serialized diagnostics report in the requested format, returned
+ // as a string so that clients can save it without depending on the
+ // report's internal schema.
+ type GetDiagnosticsResponse = string;
+
+
+.. _wallet-op-getActiveTasks:
+
+**getActiveTasks**
+
+Get the wallet's currently active background tasks and their retry
+state.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `GetActiveTasksResponse` object.
+
+**Details:**
+
+ Tasks are wallet-core's background retry loops, for example for
+ pending withdrawals or payments. ``firstTry`` and ``nextTry`` give
+ the time of the first attempt and of the next scheduled retry;
+ ``lastError`` is the error of the most recent attempt, if any.
+
+.. ts:def:: GetActiveTasksResponse
+
+ interface GetActiveTasksResponse {
+ tasks: ActiveTask[];
+ }
+
+.. ts:def:: ActiveTask
+
+ interface ActiveTask {
+ taskId: string;
+ transaction?: TransactionIdStr | undefined;
+ firstTry?: AbsoluteTime | undefined;
+ nextTry?: AbsoluteTime | undefined;
+ retryCounter?: number | undefined;
+ lastError?: TalerErrorDetail | undefined;
+ }
diff --git a/core/wallet-core/donau.rst b/core/wallet-core/donau.rst
@@ -0,0 +1,104 @@
+.. _wallet-op-setDonau:
+
+**setDonau**
+
+Set the donation authority for this wallet.
+
+**Request:**
+
+ The request arguments must be a `SetDonauRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The taxpayer ID is stored together with a salted hash of it (as
+ defined in LSD0013) that is later used to submit and query donation
+ receipts. Repeating the request with an unchanged ``donauBaseUrl``
+ and ``taxPayerId`` is idempotent: the existing salt is kept.
+
+.. ts:def:: SetDonauRequest
+
+ interface SetDonauRequest {
+ donauBaseUrl: string;
+ taxPayerId: string;
+ }
+
+
+.. _wallet-op-getDonau:
+
+**getDonau**
+
+Get the currently configured donation authority for this wallet.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `GetDonauResponse` object. The
+ ``currentDonauInfo`` field is undefined if no donation authority
+ has been configured.
+
+.. ts:def:: GetDonauResponse
+
+ interface GetDonauResponse {
+ currentDonauInfo:
+ | {
+ donauBaseUrl: string;
+ taxPayerId: string;
+ }
+ | undefined;
+ }
+
+
+.. _wallet-op-getDonauStatements:
+
+**getDonauStatements**
+
+Get a list of donation statements for this wallet. Both donation
+statements for the currently configured donation authority as well as
+past configurations (if they exist) are returned.
+
+**Request:**
+
+ The request arguments must be a `GetDonauStatementsRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetDonauStatementsResponse` object.
+
+**Details:**
+
+ Before statements are queried, the wallet submits all pending
+ donation receipts to the respective donation authority. A statement
+ is then requested from every donation authority (or only from the
+ one named by ``donauBaseUrl``, if given) for each year with
+ submitted receipts; years for which the authority has not yet issued
+ a statement contribute no entry to the result.
+
+.. ts:def:: GetDonauStatementsRequest
+
+ interface GetDonauStatementsRequest {
+ donauBaseUrl?: string;
+ }
+
+.. ts:def:: GetDonauStatementsResponse
+
+ interface GetDonauStatementsResponse {
+ statements: DonauStatementItem[];
+ }
+
+.. ts:def:: DonauStatementItem
+
+ interface DonauStatementItem {
+ total: AmountString;
+ year: number;
+ legalDomain: string;
+ uri: string;
+ donationStatementSig: EddsaSignatureString;
+ donauPub: EddsaPublicKeyString;
+ }
diff --git a/core/wallet-core/exchanges.rst b/core/wallet-core/exchanges.rst
@@ -0,0 +1,940 @@
+.. _wallet-op-addExchange:
+
+**addExchange**
+
+Add an exchange to the wallet, or force an update of the exchange entry.
+
+**Request:**
+
+ The request arguments must be an `AddExchangeRequest` object.
+
+**Response:**
+
+ On success, the result is an `AddExchangeResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_EXCHANGE_UNAVAILABLE``,
+ ``WALLET_EXCHANGE_SIGNATURE_INVALID``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ The ``uri`` is either an http(s) exchange base URL or a
+ ``taler://add-exchange/`` URI. An http(s) URL must already be
+ canonical, unless ``allowCompletion`` is set; in that case the wallet
+ tries to complete the URL as with
+ :ref:`completeExchangeBaseUrl <wallet-op-completeExchangeBaseUrl>` and
+ fails if completion is not possible. The wallet fetches the
+ exchange's key data before the request succeeds. Unless ``ephemeral``
+ is set, the exchange is marked as explicitly added by the user.
+
+.. ts:def:: AddExchangeRequest
+
+ interface AddExchangeRequest {
+ // Either an http(s) exchange base URL or
+ // a taler://add-exchange/ URI.
+ uri?: string;
+
+ // Only ephemerally add the exchange.
+ ephemeral?: boolean;
+
+ // Allow passing incomplete URLs. The wallet will try to complete
+ // the URL and throw an error if completion is not possible.
+ allowCompletion?: boolean;
+
+ // Deprecated: start a forced exchange update with a separate
+ // updateExchangeEntry call instead.
+ forceUpdate?: boolean;
+
+ // Deprecated: use the uri field instead.
+ exchangeBaseUrl?: string;
+
+ // Correlates progress notifications and allows cancellation.
+ progressToken?: string;
+ }
+
+.. ts:def:: AddExchangeResponse
+
+ interface AddExchangeResponse {
+ // Base URL of the exchange that was added to the wallet.
+ exchangeBaseUrl: string;
+ }
+
+
+.. _wallet-op-listExchanges:
+
+**listExchanges**
+
+List exchanges known to the wallet.
+
+**Request:**
+
+ The request arguments must be a `ListExchangesRequest` object.
+ Omitted filter fields do not restrict the result.
+
+**Response:**
+
+ On success, the result is an `ExchangesListResponse` object.
+
+.. ts:def:: ListExchangesRequest
+
+ interface ListExchangesRequest {
+ // Filter results to only include exchanges in the given scope.
+ filterByScope?: ScopeInfo;
+
+ // Filter results to only include exchanges
+ // with the given status.
+ filterByExchangeEntryStatus?: ExchangeEntryStatus;
+
+ // Filter results to only include exchanges with the
+ // given type.
+ filterByType?: ExchangeType;
+ }
+
+.. ts:def:: ExchangeType
+
+ type ExchangeType = "demo" | "prod";
+
+.. ts:def:: ExchangesListResponse
+
+ interface ExchangesListResponse {
+ exchanges: ExchangeListItem[];
+ }
+
+.. ts:def:: ExchangeListItem
+
+ // Info about an exchange entry in the wallet.
+ interface ExchangeListItem {
+ exchangeBaseUrl: string;
+
+ source?: ExchangeEntrySource;
+
+ masterPub: string | undefined;
+
+ // Master public keys this exchange URL used before its current
+ // key. The array is sorted and does not include masterPub.
+ legacyMasterPubs: string[];
+
+ // Set when the exchange changed its key set and the user has not
+ // confirmed the change yet. Operations that send money to the
+ // exchange or disclose coin authorizations are refused while this
+ // is present.
+ unconfirmedKeyChange?: ExchangeKeyChangeInfo;
+
+ currency: string;
+
+ paytoUris: string[];
+
+ tosStatus: ExchangeTosStatus;
+
+ exchangeEntryStatus: ExchangeEntryStatus;
+
+ exchangeUpdateStatus: ExchangeUpdateStatus;
+
+ ageRestrictionOptions: number[];
+
+ walletKycStatus?: ExchangeWalletKycStatus;
+
+ walletKycReservePub?: string;
+
+ walletKycAccessToken?: string;
+
+ walletKycUrl?: string;
+
+ // Threshold that we've requested to satisfy.
+ walletKycRequestedThreshold?: string;
+
+ // P2P payments are disabled with this exchange
+ // (e.g. because no global fees are configured).
+ peerPaymentsDisabled: boolean;
+
+ directDepositsDisabled: boolean;
+
+ // Set to true if this exchange doesn't charge any fees.
+ noFees: boolean;
+
+ // Most general scope that the exchange is a part of.
+ scopeInfo: ScopeInfo;
+
+ // Instructs wallets to use certain bank-specific language (for
+ // buttons) and/or other UI/UX customization for compliance with
+ // the rules of that bank.
+ bankComplianceLanguage?: string;
+
+ lastUpdateTimestamp: TalerPreciseTimestamp | undefined;
+
+ // Most recent successful withdrawal through this exchange.
+ lastWithdrawal?: TalerPreciseTimestamp;
+
+ // Information about the last error that occurred when trying
+ // to update the exchange info.
+ lastUpdateErrorInfo?: OperationErrorInfo;
+
+ // Currency spec for the currency offered by the exchange.
+ currencySpec: CurrencySpecification;
+ }
+
+.. ts:def:: ExchangeKeyChangeInfo
+
+ // An exchange that changed its key set, pending the user's
+ // confirmation. The wallet has already adopted the new key set, so
+ // the entry works and the older coins stay spendable; withdrawing is
+ // withheld until the change is confirmed.
+ interface ExchangeKeyChangeInfo {
+ // Master public key the exchange now uses, and the wallet now
+ // trusts.
+ currentMasterPub: string;
+
+ currentCurrency: string;
+
+ // Master public key the wallet's older funds were issued under.
+ supersededMasterPub: string;
+
+ supersededCurrency: string;
+
+ // Whether the new key set still advertises denominations the
+ // wallet holds coins of. False means the exchange does not offer
+ // to settle the older coins at all; true is only the exchange's
+ // claim, not proof of continuity.
+ sharesDenominations: boolean;
+
+ firstSeen: TalerPreciseTimestamp;
+ }
+
+.. ts:def:: OperationErrorInfo
+
+ interface OperationErrorInfo {
+ error: TalerErrorDetail;
+ }
+
+.. ts:def:: ExchangeTosStatus
+
+ type ExchangeTosStatus =
+ | "pending"
+ | "proposed"
+ | "accepted"
+ | "missing-tos";
+
+.. ts:def:: ExchangeEntryStatus
+
+ type ExchangeEntryStatus =
+ | "preset"
+ | "ephemeral"
+ | "used";
+
+.. ts:def:: ExchangeEntrySource
+
+ // How an exchange entry became known to the wallet.
+ type ExchangeEntrySource =
+ | "builtin"
+ | "user"
+ | "discovered"
+ | "unknown";
+
+.. ts:def:: ExchangeUpdateStatus
+
+ type ExchangeUpdateStatus =
+ | "initial"
+ | "initial-update"
+ | "suspended"
+ | "unavailable-update"
+ | "ready"
+ | "ready-update"
+ | "outdated-update";
+
+
+.. _wallet-op-listWithdrawalExchangeCandidates:
+
+**listWithdrawalExchangeCandidates**
+
+List exchanges suitable for presentation in a withdrawal chooser.
+
+**Request:**
+
+ The request arguments must be a `ListWithdrawalExchangeCandidatesRequest`
+ object. Omitted fields use their documented defaults.
+
+**Response:**
+
+ On success, the result is a `ListWithdrawalExchangeCandidatesResponse`
+ object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ Candidates are taken from the wallet's exchange entries and from the
+ builtin exchange list; ephemeral entries and entries with unknown
+ currency are excluded. Demo and test exchanges are only included when
+ ``withDemo`` or ``withTest`` are set. Combining ``presetOnly`` with
+ ``withBuiltin: false`` is rejected with
+ ``WALLET_CORE_API_BAD_REQUEST``. Exchanges recently used for a
+ withdrawal are sorted first, and ``recommendationReasons`` states why
+ each candidate is suggested.
+
+.. ts:def:: GetDefaultExchangesRequest
+
+ interface GetDefaultExchangesRequest {
+ // Only return production exchanges from the builtin exchange list.
+ // Cannot be combined with withBuiltin: false.
+ presetOnly?: boolean;
+
+ // Include exchanges from the builtin list. Defaults to true.
+ withBuiltin?: boolean;
+
+ // Include demo exchanges from the builtin list. Defaults to false.
+ withDemo?: boolean;
+
+ // Include test exchanges from the builtin list. Defaults to false.
+ withTest?: boolean;
+ }
+
+.. ts:def:: ListWithdrawalExchangeCandidatesRequest
+
+ type ListWithdrawalExchangeCandidatesRequest =
+ GetDefaultExchangesRequest;
+
+.. ts:def:: ListWithdrawalExchangeCandidatesResponse
+
+ interface ListWithdrawalExchangeCandidatesResponse {
+ candidates: WithdrawalExchangeCandidate[];
+ }
+
+.. ts:def:: WithdrawalExchangeCandidate
+
+ interface WithdrawalExchangeCandidate {
+ // A taler://withdraw-exchange URI for the exchange.
+ talerUri: string;
+
+ exchangeBaseUrl: string;
+
+ currency: string;
+
+ currencySpec: CurrencySpecification;
+
+ exchangeEntryStatus: ExchangeEntryStatus;
+
+ exchangeUpdateStatus: ExchangeUpdateStatus;
+
+ source: ExchangeEntrySource;
+
+ recommendationReasons: ExchangeRecommendationReason[];
+
+ lastWithdrawal?: TalerPreciseTimestamp;
+ }
+
+.. ts:def:: ExchangeRecommendationReason
+
+ type ExchangeRecommendationReason =
+ | "preset"
+ | "user-added"
+ | "previous-withdrawal"
+ | "previously-used";
+
+
+.. _wallet-op-getDefaultExchanges:
+
+**getDefaultExchanges**
+
+This operation is **deprecated**. Use
+:ref:`listWithdrawalExchangeCandidates
+<wallet-op-listWithdrawalExchangeCandidates>` instead.
+
+List the default exchanges offered to the user for withdrawing.
+
+**Request:**
+
+ The request arguments must be a `GetDefaultExchangesRequest` object,
+ as for :ref:`listWithdrawalExchangeCandidates
+ <wallet-op-listWithdrawalExchangeCandidates>`.
+
+**Response:**
+
+ On success, the result is a `GetDefaultExchangesResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ The result is a reduced view of the withdrawal exchange candidates,
+ carrying only the withdrawal URI, currency and currency
+ specification of each candidate.
+
+.. ts:def:: GetDefaultExchangesResponse
+
+ interface GetDefaultExchangesResponse {
+ defaultExchanges: {
+ // A taler://withdraw-exchange URI for the exchange.
+ talerUri: string;
+
+ // Currency offered by the exchange.
+ currency: string;
+
+ // Currency spec for the currency offered by the exchange.
+ currencySpec: CurrencySpecification;
+ }[];
+ }
+
+
+.. _wallet-op-getExchangeEntryByUrl:
+
+**getExchangeEntryByUrl**
+
+Get the wallet's exchange entry for an exchange base URL.
+
+**Request:**
+
+ The request arguments must be a `GetExchangeEntryByUrlRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetExchangeEntryByUrlResponse` object,
+ which is an alias for `ExchangeListItem` (see
+ :ref:`listExchanges <wallet-op-listExchanges>`).
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``.
+
+.. ts:def:: GetExchangeEntryByUrlRequest
+
+ interface GetExchangeEntryByUrlRequest {
+ exchangeBaseUrl: string;
+ }
+
+.. ts:def:: GetExchangeEntryByUrlResponse
+
+ type GetExchangeEntryByUrlResponse = ExchangeListItem;
+
+
+.. _wallet-op-updateExchangeEntry:
+
+**updateExchangeEntry**
+
+Update an exchange entry.
+
+Only starts updating the exchange entry. After this request finishes,
+it is not guaranteed that the exchange entry has been updated. Use
+notifications and the :ref:`listExchanges <wallet-op-listExchanges>`
+request to check the status.
+
+**Request:**
+
+ The request arguments must be an `UpdateExchangeEntryRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``,
+ ``WALLET_EXCHANGE_ENTRY_UPDATE_CONFLICT``,
+ ``WALLET_EXCHANGE_UNAVAILABLE``.
+
+**Details:**
+
+ Setting ``force`` starts the update even when the entry was recently
+ updated or the exchange is currently marked unavailable.
+
+.. ts:def:: UpdateExchangeEntryRequest
+
+ interface UpdateExchangeEntryRequest {
+ exchangeBaseUrl: string;
+
+ // Force the update even if the entry was recently updated or
+ // the exchange is currently marked unavailable.
+ force?: boolean;
+ }
+
+
+.. _wallet-op-getExchangeResources:
+
+**getExchangeResources**
+
+Check whether the wallet still holds resources associated with an
+exchange.
+
+**Request:**
+
+ The request arguments must be a `GetExchangeResourcesRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetExchangeResourcesResponse` object.
+
+**Details:**
+
+ Resources are the coins and withdrawal groups (including internal
+ withdrawals of peer transactions) associated with the exchange;
+ ``hasResources`` is true if any of them exist. Clients can use this
+ to decide whether :ref:`deleteExchange <wallet-op-deleteExchange>`
+ requires explicit user confirmation.
+
+.. ts:def:: GetExchangeResourcesRequest
+
+ interface GetExchangeResourcesRequest {
+ exchangeBaseUrl: string;
+ }
+
+.. ts:def:: GetExchangeResourcesResponse
+
+ interface GetExchangeResourcesResponse {
+ hasResources: boolean;
+ }
+
+
+.. _wallet-op-completeExchangeBaseUrl:
+
+**completeExchangeBaseUrl**
+
+Try to complete a partial URL into the canonical base URL of an
+exchange.
+
+**Request:**
+
+ The request arguments must be a `CompleteBaseUrlRequest` object.
+
+**Response:**
+
+ On success, the result is a `CompleteBaseUrlResult` object.
+
+**Details:**
+
+ The wallet derives candidate base URLs from ``url`` and probes them,
+ returning the first candidate that answers like an exchange. A
+ failure to complete is reported in-band via the ``status`` field of
+ the result, not with an error response: ``bad-syntax`` when the URL
+ is too malformed to be completed, ``bad-network`` when no candidate
+ could be reached and ``bad-exchange`` when the endpoint is reachable
+ but is not an exchange. In the failure case, ``suggestions`` may
+ list base URLs of exchanges known to the wallet that look similar to
+ what was typed.
+
+.. ts:def:: CompleteBaseUrlRequest
+
+ interface CompleteBaseUrlRequest {
+ url: string;
+
+ // Correlates progress notifications and allows cancellation.
+ progressToken?: string;
+ }
+
+.. ts:def:: CompleteBaseUrlResult
+
+ type CompleteBaseUrlResult =
+ | {
+ // ok: completion is a proper exchange
+ status: "ok";
+
+ // Completed exchange base URL, if completion was possible
+ completion: string;
+ }
+ | {
+ // bad-syntax: url is so badly malformed, it can't be completed
+ // bad-network: syntax okay, but exchange can't be reached
+ // bad-exchange: syntax and network okay, but not talking to
+ // an exchange
+ status: "bad-syntax" | "bad-network" | "bad-exchange";
+
+ // Error details in case status is not "ok"
+ error: TalerErrorDetail;
+
+ // Base URLs of exchanges known to the wallet whose host looks
+ // like what the user meant to type, most likely first.
+ // Absent when the wallet does not know anything similar.
+ suggestions?: string[];
+ };
+
+
+.. _wallet-op-deleteExchange:
+
+**deleteExchange**
+
+Delete an exchange and its associated resources.
+
+**Request:**
+
+ The request arguments must be a `DeleteExchangeRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``, ``WALLET_EXCHANGE_ENTRY_USED``.
+
+**Details:**
+
+ The operation fails with ``WALLET_EXCHANGE_ENTRY_USED`` when the
+ exchange still has associated resources (see
+ :ref:`getExchangeResources <wallet-op-getExchangeResources>`), unless
+ ``purge`` is set. Some transactions related to the exchange
+ (payments, peer payments and refreshes) are kept even when purging.
+
+.. ts:def:: DeleteExchangeRequest
+
+ interface DeleteExchangeRequest {
+ exchangeBaseUrl: string;
+
+ // Delete the exchange even if it's in use.
+ purge?: boolean;
+ }
+
+
+.. _wallet-op-purgeExchangeLegacyKeys:
+
+**purgeExchangeLegacyKeys**
+
+Purge every non-current key set retained for an exchange URL.
+
+**Request:**
+
+ The request arguments must be a `PurgeExchangeLegacyKeysRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ Removes the coins, denominations and other stored data issued under
+ superseded master public keys of the exchange; withdrawals of purged
+ coins are marked as legacy. The ``currentMasterPub`` must be the
+ current master key observed by the client before confirming the
+ purge: the operation fails atomically with
+ ``WALLET_CORE_API_BAD_REQUEST`` if the exchange has changed keys
+ since, so a key rotation between review and execution cannot turn
+ the key the user just reviewed into another key that is silently
+ purged.
+
+.. ts:def:: PurgeExchangeLegacyKeysRequest
+
+ interface PurgeExchangeLegacyKeysRequest {
+ exchangeBaseUrl: string;
+
+ // Current master key observed by the client before confirming the
+ // purge. The operation fails atomically if the exchange has
+ // changed keys since.
+ currentMasterPub: string;
+ }
+
+
+.. _wallet-op-confirmExchangeKeyChange:
+
+**confirmExchangeKeyChange**
+
+Confirm that the exchange's changed key set is legitimate.
+
+The wallet has already adopted the changed key set; this releases the
+operations that send money to the exchange, which are withheld until
+the user has had a chance to notice that the key changed.
+
+**Request:**
+
+ The request arguments must be a `ConfirmExchangeKeyChangeRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_NO_KEY_CHANGE_PENDING``,
+ ``WALLET_EXCHANGE_KEY_CHANGE_MISMATCH``.
+
+**Details:**
+
+ ``currentMasterPub`` must be the master public key the exchange
+ currently uses, so that a UI showing a stale key change cannot
+ confirm a different one than the user was looking at.
+
+.. ts:def:: ConfirmExchangeKeyChangeRequest
+
+ interface ConfirmExchangeKeyChangeRequest {
+ exchangeBaseUrl: string;
+
+ // Master public key the exchange now uses. Required, so that a
+ // UI showing a stale key change cannot confirm a different one
+ // than the user was looking at.
+ currentMasterPub: string;
+ }
+
+
+.. _wallet-op-setExchangeTosAccepted:
+
+**setExchangeTosAccepted**
+
+Mark the current version of the exchange's terms of service as accepted
+by the user.
+
+**Request:**
+
+ The request arguments must be an `AcceptExchangeTosRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The accepted version is the current ToS version observed by the
+ wallet, as reported by :ref:`getExchangeTos <wallet-op-getExchangeTos>`.
+
+.. ts:def:: AcceptExchangeTosRequest
+
+ interface AcceptExchangeTosRequest {
+ exchangeBaseUrl: string;
+ }
+
+
+.. _wallet-op-setExchangeTosForgotten:
+
+**setExchangeTosForgotten**
+
+Forget the acceptance of the exchange's terms of service, marking them
+as not accepted.
+
+**Request:**
+
+ The request arguments must be an `AcceptExchangeTosRequest` object
+ (see :ref:`setExchangeTosAccepted <wallet-op-setExchangeTosAccepted>`).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-getExchangeTos:
+
+**getExchangeTos**
+
+Get the current terms of service of an exchange.
+
+**Request:**
+
+ The request arguments must be a `GetExchangeTosRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetExchangeTosResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``.
+
+**Details:**
+
+ The wallet downloads the terms of service from the exchange, using
+ ``acceptedFormat`` and ``acceptLanguage`` for content negotiation,
+ and updates its stored current ToS version tag. If the exchange
+ does not provide terms of service, ``tosStatus`` is ``"missing-tos"``
+ and the content fields carry placeholder values.
+
+.. ts:def:: GetExchangeTosRequest
+
+ interface GetExchangeTosRequest {
+ exchangeBaseUrl: string;
+
+ // Acceptable content types for the ToS text, used for content
+ // negotiation with the exchange.
+ acceptedFormat?: string[];
+
+ // Preferred language for the ToS text.
+ acceptLanguage?: string;
+
+ // Correlates progress notifications and allows cancellation.
+ progressToken?: string;
+ }
+
+.. ts:def:: GetExchangeTosResult
+
+ interface GetExchangeTosResult {
+ // Markdown version of the current ToS.
+ content: string;
+
+ // Version tag of the current ToS.
+ currentEtag: string;
+
+ // Version tag of the last ToS that the user has accepted, if any.
+ acceptedEtag: string | undefined;
+
+ // Accepted content type
+ contentType: string;
+
+ // Language of the returned content. If missing, language is
+ // unknown.
+ contentLanguage: string | undefined;
+
+ // Available languages as advertised by the exchange.
+ tosAvailableLanguages: string[];
+
+ tosStatus: ExchangeTosStatus;
+ }
+
+
+.. _wallet-op-getExchangeDetailedInfo:
+
+**getExchangeDetailedInfo**
+
+Get detailed information about an exchange, including a timeline for
+the fees charged by the exchange.
+
+**Request:**
+
+ The request arguments must be a `GetExchangeDetailedInfoRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an `ExchangeDetailedResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``.
+
+**Details:**
+
+ The ``denomFees``, ``transferFees`` and ``globalFees`` of the result
+ are timelines: each `FeeDescription` covers the time range from
+ ``from`` to ``until`` during which the fee applies.
+
+.. ts:def:: GetExchangeDetailedInfoRequest
+
+ interface GetExchangeDetailedInfoRequest {
+ exchangeBaseUrl: string;
+ }
+
+.. ts:def:: ExchangeDetailedResponse
+
+ interface ExchangeDetailedResponse {
+ exchange: ExchangeFullDetails;
+ }
+
+.. ts:def:: ExchangeFullDetails
+
+ interface ExchangeFullDetails {
+ exchangeBaseUrl: string;
+
+ currency: string;
+
+ paytoUris: string[];
+
+ auditors: ExchangeAuditor[];
+
+ wireInfo: WireInfo;
+
+ denomFees: DenomOperationMap<FeeDescription[]>;
+
+ transferFees: Record<string, FeeDescription[]>;
+
+ globalFees: FeeDescription[];
+ }
+
+.. ts:def:: DenomOperation
+
+ type DenomOperation = "deposit" | "withdraw" | "refresh" | "refund";
+
+.. ts:def:: DenomOperationMap
+
+ type DenomOperationMap<T> = { [op in DenomOperation]: T };
+
+.. ts:def:: FeeDescription
+
+ interface FeeDescription {
+ group: string;
+
+ from: AbsoluteTime;
+
+ until: AbsoluteTime;
+
+ fee?: AmountString;
+ }
+
+.. ts:def:: WireInfo
+
+ interface WireInfo {
+ feesForType: WireFeeMap;
+
+ accounts: ExchangeWireAccount[];
+ }
+
+.. ts:def:: WireFeeMap
+
+ type WireFeeMap = { [wireMethod: string]: WireFee[] };
+
+.. ts:def:: WireFee
+
+ // Wire fee for one wire method
+ interface WireFee {
+ // Fee for wire transfers.
+ wireFee: AmountString;
+
+ // Fees to close and refund a reserve.
+ closingFee: AmountString;
+
+ // Start date of the fee.
+ startStamp: TalerProtocolTimestamp;
+
+ // End date of the fee.
+ endStamp: TalerProtocolTimestamp;
+
+ // Signature made by the exchange master key.
+ sig: string;
+ }
+
+
+.. _wallet-op-startExchangeWalletKyc:
+
+**startExchangeWalletKyc**
+
+Start the wallet KYC process at an exchange, requesting authorization
+to hold funds up to the given amount threshold.
+
+**Request:**
+
+ The request arguments must be a `StartExchangeWalletKycRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The request only initiates the KYC process; the wallet then drives it
+ in the background and reports status changes via notifications. If a
+ KYC threshold of at least ``amount`` was already granted or is
+ already being requested, the operation does nothing. Once started,
+ the exchange entry is marked as used, as the wallet keeps an account
+ at the exchange.
+
+.. ts:def:: StartExchangeWalletKycRequest
+
+ interface StartExchangeWalletKycRequest {
+ exchangeBaseUrl: string;
+
+ // Amount threshold that the KYC process should authorize.
+ amount: AmountString;
+ }
diff --git a/core/wallet-core/global-currency.rst b/core/wallet-core/global-currency.rst
@@ -0,0 +1,292 @@
+.. _wallet-op-listGlobalCurrencyExchanges:
+
+**listGlobalCurrencyExchanges**
+
+List the exchanges registered for global currencies.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `ListGlobalCurrencyExchangesResponse`
+ object.
+
+.. ts:def:: ListGlobalCurrencyExchangesResponse
+
+ interface ListGlobalCurrencyExchangesResponse {
+ exchanges: {
+ currency: string;
+ exchangeBaseUrl: string;
+ exchangeMasterPub: string;
+ }[];
+ }
+
+
+.. _wallet-op-listGlobalCurrencyAuditors:
+
+**listGlobalCurrencyAuditors**
+
+List the auditors the wallet trusts for global currencies.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `ListGlobalCurrencyAuditorsResponse`
+ object.
+
+.. ts:def:: ListGlobalCurrencyAuditorsResponse
+
+ interface ListGlobalCurrencyAuditorsResponse {
+ auditors: {
+ currency: string;
+ auditorBaseUrl: string;
+ auditorPub: string;
+ }[];
+ }
+
+
+.. _wallet-op-addGlobalCurrencyExchange:
+
+**addGlobalCurrencyExchange**
+
+Register an exchange for a global currency.
+
+**Request:**
+
+ The request must be an `AddGlobalCurrencyExchangeRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object (`EmptyObject`).
+
+**Details:**
+
+ Adding an exchange that is already registered is a no-op.
+ Currency information already known under the exchange's own scope
+ is copied to the global scope. The ``exchangeBaseUrl`` must be a
+ canonicalized base URL (see :ref:`canonicalizeBaseUrl
+ <wallet-op-canonicalizeBaseUrl>`). When the configuration
+ changed, a ``balance-change`` notification is emitted.
+
+.. ts:def:: AddGlobalCurrencyExchangeRequest
+
+ interface AddGlobalCurrencyExchangeRequest {
+ currency: string;
+ exchangeBaseUrl: string;
+ exchangeMasterPub: string;
+ }
+
+
+.. _wallet-op-removeGlobalCurrencyExchange:
+
+**removeGlobalCurrencyExchange**
+
+Remove an exchange registered for a global currency.
+
+**Request:**
+
+ The request must be a `RemoveGlobalCurrencyExchangeRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object (`EmptyObject`).
+
+**Details:**
+
+ Removing an exchange that is not registered is a no-op. When the
+ last exchange for a currency is removed, the currency information
+ for the global scope is removed as well. When the configuration
+ changed, a ``balance-change`` notification is emitted.
+
+.. ts:def:: RemoveGlobalCurrencyExchangeRequest
+
+ interface RemoveGlobalCurrencyExchangeRequest {
+ currency: string;
+ exchangeBaseUrl: string;
+ exchangeMasterPub: string;
+ }
+
+
+.. _wallet-op-addGlobalCurrencyAuditor:
+
+**addGlobalCurrencyAuditor**
+
+Register a trusted auditor for a global currency.
+
+**Request:**
+
+ The request must be an `AddGlobalCurrencyAuditorRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object (`EmptyObject`).
+
+**Details:**
+
+ Adding an auditor that is already registered is a no-op. The
+ ``auditorBaseUrl`` must be a canonicalized base URL (see
+ :ref:`canonicalizeBaseUrl <wallet-op-canonicalizeBaseUrl>`). When
+ the configuration changed, a ``balance-change`` notification is
+ emitted.
+
+.. ts:def:: AddGlobalCurrencyAuditorRequest
+
+ interface AddGlobalCurrencyAuditorRequest {
+ currency: string;
+ auditorBaseUrl: string;
+ auditorPub: string;
+ }
+
+
+.. _wallet-op-removeGlobalCurrencyAuditor:
+
+**removeGlobalCurrencyAuditor**
+
+Remove a trusted auditor for a global currency.
+
+**Request:**
+
+ The request must be a `RemoveGlobalCurrencyAuditorRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object (`EmptyObject`).
+
+**Details:**
+
+ Removing an auditor that is not registered is a no-op. When the
+ configuration changed, a ``balance-change`` notification is
+ emitted.
+
+.. ts:def:: RemoveGlobalCurrencyAuditorRequest
+
+ interface RemoveGlobalCurrencyAuditorRequest {
+ currency: string;
+ auditorBaseUrl: string;
+ auditorPub: string;
+ }
+
+
+.. _wallet-op-getCurrencySpecification:
+
+**getCurrencySpecification**
+
+Get the currency specification (input and rendering rules) for a
+currency scope.
+
+**Request:**
+
+ The request must be a `GetCurrencySpecificationRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetCurrencySpecificationResponse`
+ object.
+
+**Details:**
+
+ The specification recorded for the given scope is returned. For
+ the demonstration currencies ``KUDOS`` and ``TESTKUDOS``, a
+ hard-coded specification is returned. When no specification is
+ known for the scope, a default with two fractional digits derived
+ from the currency name is returned.
+
+.. ts:def:: GetCurrencySpecificationRequest
+
+ interface GetCurrencySpecificationRequest {
+ scope: ScopeInfo;
+ }
+
+.. ts:def:: GetCurrencySpecificationResponse
+
+ interface GetCurrencySpecificationResponse {
+ currencySpecification: CurrencySpecification;
+ }
+
+.. ts:def:: ScopeInfo
+
+ // Currency scope, discriminated on the "type" field.
+ type ScopeInfo =
+ | ScopeInfoGlobal
+ | ScopeInfoExchange
+ | ScopeInfoAuditor
+ | ScopeInfoExchangeLegacyKeys;
+
+.. ts:def:: ScopeInfoGlobal
+
+ type ScopeInfoGlobal = {
+ type: ScopeType.Global;
+ currency: string;
+ };
+
+.. ts:def:: ScopeInfoExchange
+
+ type ScopeInfoExchange = {
+ type: ScopeType.Exchange;
+ currency: string;
+ url: string;
+ };
+
+.. ts:def:: ScopeInfoAuditor
+
+ type ScopeInfoAuditor = {
+ type: ScopeType.Auditor;
+ currency: string;
+ url: string;
+ };
+
+.. ts:def:: ScopeInfoExchangeLegacyKeys
+
+ type ScopeInfoExchangeLegacyKeys = {
+ type: ScopeType.ExchangeLegacyKeys;
+ currency: string;
+ url: string;
+
+ // The superseded key the funds were issued under.
+ masterPub: string;
+ };
+
+.. ts:def:: ScopeType
+
+ enum ScopeType {
+ Global = "global",
+ Exchange = "exchange",
+ Auditor = "auditor",
+
+ // Funds issued under a master public key the exchange has
+ // since replaced.
+ ExchangeLegacyKeys = "exchange-legacy-keys",
+ }
+
+.. ts:def:: CurrencySpecification
+
+ // Input and rendering rules for a currency (see DD51).
+ interface CurrencySpecification {
+ // Name of the currency.
+ name: string;
+
+ // How many digits the user may enter after the decimal
+ // separator.
+ num_fractional_input_digits: Integer;
+
+ // Number of fractional digits to render in normal font and
+ // size.
+ num_fractional_normal_digits: Integer;
+
+ // Number of fractional digits to render always, padding with
+ // zeros if needed.
+ num_fractional_trailing_zero_digits: Integer;
+
+ // Map of powers of 10 to alternative currency names / symbols;
+ // always has an entry under "0" with the base name, e.g.
+ // "0 => €" or "3 => k€".
+ alt_unit_names: { [log10: string]: string };
+
+ common_amounts?: AmountString[];
+ }
diff --git a/core/wallet-core/hints.rst b/core/wallet-core/hints.rst
@@ -0,0 +1,130 @@
+.. _wallet-op-hintNetworkAvailability:
+
+**hintNetworkAvailability**
+
+Inform wallet-core about the host system's network connectivity.
+
+**Request:**
+
+ The request ``args`` must be a `HintNetworkAvailabilityRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ When the reported availability changes, wallet-core restarts all
+ running background tasks: tasks that are blocked waiting for the
+ network resume when connectivity is back, and tasks notice the outage
+ and wait when it goes away. Reporting the already known state has no
+ effect.
+
+.. ts:def:: HintNetworkAvailabilityRequest
+
+ interface HintNetworkAvailabilityRequest {
+ // Whether the host system currently has network connectivity.
+ isNetworkAvailable: boolean;
+ }
+
+
+.. _wallet-op-hintPowerState:
+
+**hintPowerState**
+
+Report the host system's current power source.
+
+**Request:**
+
+ The request ``args`` must be a `HintPowerStateRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The power source controls whether wallet-core may refresh coins
+ opportunistically, long before they expire: such early refreshes are
+ only made when they are free of charge, and only on ``external``
+ power. The ``unknown`` power source never qualifies. When the power
+ source changes, wallet-core restarts all running background tasks.
+
+.. ts:def:: HintPowerStateRequest
+
+ interface HintPowerStateRequest {
+ // Currently observed power source of the host system.
+ powerSource: WalletPowerSource;
+ }
+
+.. ts:def:: WalletPowerSource
+
+ // Power source of the host system, as observed by the client.
+ type WalletPowerSource = "external" | "battery" | "unknown";
+
+
+.. _wallet-op-dismissWalletWarning:
+
+**dismissWalletWarning**
+
+Dismiss a completed renewal notice. Active expiration risks cannot be
+dismissed.
+
+**Request:**
+
+ The request ``args`` must be a `DismissWalletWarningRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ When wallet-core automatically refreshed coins that were close to
+ expiration, the renewal is reported as a `CashRenewalNotice` in the
+ ``refreshInfo.recoveries`` of the affected `WalletBalance`. Passing
+ the notice's ``warningId`` to this operation dismisses the notice; a
+ ``balance-change`` notification is emitted, and the notice is no
+ longer reported. Renewal notices of refresh operations that are still
+ active, as well as unknown warning identifiers, are ignored; the
+ operation still succeeds.
+
+.. ts:def:: DismissWalletWarningRequest
+
+ interface DismissWalletWarningRequest {
+ // Identifier of the wallet warning to dismiss, as found in the
+ // warningId field of a CashRenewalNotice.
+ warningId: string;
+ }
+
+
+.. _wallet-op-hintApplicationResumed:
+
+**hintApplicationResumed**
+
+Give wallet-core a kick and restart all pending tasks.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `HintApplicationResumedResponse` object.
+
+**Details:**
+
+ Useful when the host application was suspended and resumed, as active
+ network requests might have stalled. Wallet-core restarts its
+ background tasks and performs a database write and read health check;
+ the outcome of the checks is reported in the response.
+
+.. ts:def:: HintApplicationResumedResponse
+
+ interface HintApplicationResumedResponse {
+ // Did the database write health check succeed?
+ dbWriteHealthy: boolean;
+
+ // Did the database read health check succeed?
+ dbReadHealthy: boolean;
+ }
diff --git a/core/wallet-core/init.rst b/core/wallet-core/init.rst
@@ -0,0 +1,196 @@
+.. _wallet-op-initWallet:
+
+**initWallet**
+
+Initialize wallet-core. This must be the first request made to
+wallet-core; every other operation fails until initialization has
+completed.
+
+**Request:**
+
+ The request must be an `InitRequest` object.
+
+**Response:**
+
+ On success, the result is an `InitResponse` object.
+
+**Details:**
+
+ Initialization opens the wallet database, runs internal data
+ migrations and installs the built-in default exchanges (unless
+ ``config.testing.skipDefaults`` is set). When
+ ``config.features.migrateNativeDb`` is set, the database is first
+ migrated to wallet-core's native sqlite schema. Unless
+ ``config.lazyTaskLoop`` is set, the background task loop is started
+ and begins processing pending transactions.
+
+ Calling ``initWallet`` again after a successful initialization
+ re-initializes the wallet with the new configuration, exactly like
+ :ref:`setWalletRunConfig <wallet-op-setWalletRunConfig>`.
+
+.. ts:def:: InitRequest
+
+ interface InitRequest {
+ // Configuration overrides; omitted fields fall back to defaults.
+ config?: PartialWalletRunConfig;
+ }
+
+.. ts:def:: PartialWalletRunConfig
+
+ interface PartialWalletRunConfig {
+ testing?: Partial<WalletRunConfig["testing"]>;
+ features?: Partial<WalletRunConfig["features"]>;
+ lazyTaskLoop?: Partial<WalletRunConfig["lazyTaskLoop"]>;
+ logLevel?: Partial<WalletRunConfig["logLevel"]>;
+ }
+
+.. ts:def:: WalletRunConfig
+
+ interface WalletRunConfig {
+ // Unsafe options which should only be used to create
+ // testing environments.
+ testing: {
+ devModeActive: boolean;
+ insecureTrustExchange: boolean;
+ preventThrottling: boolean;
+ skipDefaults: boolean;
+ emitObservabilityEvents?: boolean;
+
+ // Coin selection algorithm to use when spending.
+ // Defaults to the TALER_WALLET_COINSEL environment variable,
+ // and to "default" when that is unset.
+ coinSelectionAlgorithm: CoinSelectionAlgorithm;
+ };
+
+ // Configuration values that may be safe to show to the user.
+ features: {
+ allowHttp: boolean;
+
+ // Migrate the wallet database to wallet-core's native sqlite
+ // schema, replacing the IndexedDB emulation. Checked on every
+ // initialization; off by default.
+ migrateNativeDb: boolean;
+
+ // Use the native sqlite schema when initializing a new, empty
+ // database; never converts an existing IndexedDB wallet.
+ useNativeDb: boolean;
+ };
+
+ // Start processing tasks only when explicitly required, even
+ // after init has been called.
+ lazyTaskLoop: boolean;
+
+ // Global log level.
+ logLevel: string;
+ }
+
+.. ts:def:: CoinSelectionAlgorithm
+
+ // Coin selection algorithm the wallet uses when spending.
+ // "legacy-2024" is the algorithm shipped in 2024, kept for external
+ // test suites that pin the coin selections it produces.
+ type CoinSelectionAlgorithm = "default" | "legacy-2024";
+
+.. ts:def:: InitResponse
+
+ interface InitResponse {
+ // Version information about the initialized wallet-core.
+ versionInfo: WalletCoreVersion;
+
+ // Database backend used by the initialized wallet.
+ databaseBackend: WalletDatabaseBackend;
+ }
+
+.. ts:def:: WalletDatabaseBackend
+
+ // Database backends that wallet-core can run on.
+ type WalletDatabaseBackend = "indexeddb" | "sqlite";
+
+
+.. _wallet-op-setWalletRunConfig:
+
+**setWalletRunConfig**
+
+Change the configuration of wallet-core.
+
+**Request:**
+
+ The request must be an `InitRequest` object.
+
+**Response:**
+
+ On success, the result is an `InitResponse` object.
+
+**Details:**
+
+ This operation is currently an alias for :ref:`initWallet
+ <wallet-op-initWallet>`: both operations run the same initialization,
+ so ``setWalletRunConfig`` can also serve as the first request that
+ initializes the wallet. Fields not present in ``config`` are reset
+ to their defaults.
+
+
+.. _wallet-op-getVersion:
+
+**getVersion**
+
+Get version information about wallet-core and the protocol versions
+it supports.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `WalletCoreVersion` object.
+
+.. ts:def:: WalletCoreVersion
+
+ interface WalletCoreVersion {
+ implementationSemver: string;
+ implementationGitHash: string;
+
+ // Wallet-core protocol version supported by this implementation
+ // of the API ("server" version).
+ version: string;
+ exchange: string;
+ merchant: string;
+
+ bankIntegrationApiRange: string;
+ bankConversionApiRange: string;
+ corebankApiRange: string;
+
+ // Deprecated: the bank API was split into multiple APIs with
+ // separate versioning.
+ bank: string;
+
+ // Deprecated.
+ hash: string | undefined;
+
+ // Deprecated, will be removed.
+ devMode: boolean;
+ }
+
+
+.. _wallet-op-shutdown:
+
+**shutdown**
+
+Shut down wallet-core: stop the background task loop, timers and
+cryptographic workers, and close the wallet database.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ After a shutdown, every operation other than ``shutdown`` itself
+ fails with ``WALLET_CORE_NOT_AVAILABLE``. To use the wallet again,
+ wallet-core must be restarted and :ref:`initWallet
+ <wallet-op-initWallet>` called again.
diff --git a/core/wallet-core/mailbox.rst b/core/wallet-core/mailbox.rst
@@ -0,0 +1,260 @@
+.. _wallet-op-getMailbox:
+
+**getMailbox**
+
+Get the mailbox configuration stored locally for a mailbox service.
+This operation only reads the wallet's local database.
+
+**Request:**
+
+ The request arguments are a `MailboxBaseUrl` object.
+
+**Response:**
+
+ On success, the result is a `GetMailboxResponse` object. The
+ ``mailboxConfiguration`` field is absent if the wallet has no
+ mailbox configuration for the given base URL.
+
+.. ts:def:: MailboxBaseUrl
+
+ interface MailboxBaseUrl {
+ // Base URL of the mailbox service.
+ mailboxBaseUrl: string;
+ }
+
+.. ts:def:: GetMailboxResponse
+
+ interface GetMailboxResponse {
+ // Locally stored configuration for the mailbox service, if any.
+ mailboxConfiguration?: MailboxConfiguration;
+ }
+
+.. ts:def:: MailboxConfiguration
+
+ interface MailboxConfiguration {
+ // Base URL of the mailbox service.
+ mailboxBaseUrl: string;
+
+ // Private EdDSA signing key of the mailbox.
+ privateKey: EddsaPrivateKeyString;
+
+ // Private HPKE encryption key of the mailbox.
+ privateEncryptionKey: string;
+
+ // Expiration of the current mailbox registration.
+ expiration: Timestamp;
+
+ // Mailbox address (hash of the public signing key).
+ hAddress: string;
+
+ // Set when the mailbox service requires payment to complete
+ // the registration.
+ payUri?: TalerUri;
+ }
+
+
+.. _wallet-op-initializeMailbox:
+
+**initializeMailbox**
+
+Create a new mailbox at a mailbox service: wallet-core generates fresh
+signing and encryption keys, registers the mailbox with the service and
+stores the resulting configuration locally.
+
+**Request:**
+
+ The request arguments are a `MailboxBaseUrl` object.
+
+**Response:**
+
+ On success, the result is the newly created `MailboxConfiguration`
+ object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_MAILBOX_UNAVAILABLE``, ``GENERIC_FORBIDDEN``.
+
+**Details:**
+
+ A fresh key pair is generated on every call, so each call creates a
+ new mailbox identity at the service. The registration is made with
+ an expiration one year in the future. If the mailbox service
+ requires payment for the registration, the operation still succeeds
+ and the returned configuration has ``payUri`` set, which the client
+ can use to pay for the registration. ``GENERIC_FORBIDDEN`` indicates
+ that the mailbox service refused the registration.
+
+
+.. _wallet-op-getMailboxMessage:
+
+**getMailboxMessage**
+
+Get the mailbox messages stored locally in the wallet.
+
+Note that the TypeScript client library exposes this operation as
+``GetMailboxMessages`` (with a trailing "s"), while the operation name
+sent on the wire is ``getMailboxMessage``.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `MailboxMessagesResponse` object.
+
+**Details:**
+
+ This operation only reads the wallet's local database; use
+ :ref:`refreshMailbox <wallet-op-refreshMailbox>` to download new
+ messages from the mailbox service.
+
+.. ts:def:: MailboxMessagesResponse
+
+ interface MailboxMessagesResponse {
+ // Locally stored mailbox messages.
+ messages: MailboxMessageRecord[];
+ }
+
+.. ts:def:: MailboxMessageRecord
+
+ // Record metadata for mailbox messages.
+ interface MailboxMessageRecord {
+ // Origin mailbox.
+ originMailboxBaseUrl: string;
+
+ // Time of download.
+ downloadedAt: Timestamp;
+
+ // Taler URI in message.
+ talerUri: string;
+ }
+
+
+.. _wallet-op-addMailboxMessage:
+
+**addMailboxMessage**
+
+Store a mailbox message in the wallet's local database. If a message
+with the same ``originMailboxBaseUrl`` and ``talerUri`` already exists,
+its ``downloadedAt`` timestamp is updated. Afterwards, wallet-core
+emits a ``mailbox-message-added`` notification.
+
+**Request:**
+
+ The request arguments are an `AddMailboxMessageRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: AddMailboxMessageRequest
+
+ interface AddMailboxMessageRequest {
+ // The message to store.
+ message: MailboxMessageRecord;
+ }
+
+
+.. _wallet-op-deleteMailboxMessage:
+
+**deleteMailboxMessage**
+
+Delete a mailbox message from the wallet's local database. Afterwards,
+wallet-core emits a ``mailbox-message-deleted`` notification.
+
+**Request:**
+
+ The request arguments are a `DeleteMailboxMessageRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The message is identified by the ``originMailboxBaseUrl`` and
+ ``talerUri`` fields of the given message; the ``downloadedAt`` field
+ is ignored. Deleting a message that does not exist is not an error.
+
+.. ts:def:: DeleteMailboxMessageRequest
+
+ interface DeleteMailboxMessageRequest {
+ // The message to delete.
+ message: MailboxMessageRecord;
+ }
+
+
+.. _wallet-op-sendTalerUriMailboxMessage:
+
+**sendTalerUriMailboxMessage**
+
+Send a ``taler://`` URI to the mailbox of a contact. Wallet-core
+fetches the public keys of the recipient's mailbox from the contact's
+mailbox service, verifies that they match the contact's mailbox
+address, encrypts the URI for the recipient and uploads it to the
+recipient's mailbox.
+
+**Request:**
+
+ The request arguments are a `SendTalerUriMailboxMessageRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_MAILBOX_UNAVAILABLE``.
+
+.. ts:def:: SendTalerUriMailboxMessageRequest
+
+ interface SendTalerUriMailboxMessageRequest {
+ // The recipient contact.
+ contact: ContactEntry;
+
+ // The taler:// URI to send.
+ talerUri: string;
+ }
+
+
+.. _wallet-op-refreshMailbox:
+
+**refreshMailbox**
+
+Download new messages from a mailbox service: messages are fetched in
+batches, decrypted with the mailbox's private encryption key, stored in
+the wallet's local database and deleted on the mailbox service.
+
+**Request:**
+
+ The request arguments are a `MailboxConfiguration` object for the
+ mailbox to refresh.
+
+**Response:**
+
+ On success, the result is a `MailboxMessageRecordsResponse` object
+ with the newly downloaded messages.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_MAILBOX_UNAVAILABLE``.
+
+**Details:**
+
+ Each downloaded message is stored like in
+ :ref:`addMailboxMessage <wallet-op-addMailboxMessage>` and thus
+ triggers a ``mailbox-message-added`` notification. Messages that
+ cannot be decrypted are skipped. At most 100 batches are fetched in
+ one call.
+
+.. ts:def:: MailboxMessageRecordsResponse
+
+ interface MailboxMessageRecordsResponse {
+ // Newly downloaded messages.
+ messages: MailboxMessageRecord[];
+ }
diff --git a/core/wallet-core/notifications.rst b/core/wallet-core/notifications.rst
@@ -0,0 +1,570 @@
+.. _wallet-notif-coin-recovery-progress:
+
+**coin-recovery-progress**
+
+Emitted while the :ref:`testingRecoverCoins <wallet-op-testingRecoverCoins>`
+operation scans an exchange for coins that belong to the wallet and recovers
+them. The ``progressToken`` matches the progress token of the initiating
+request.
+
+The payload is a `CoinRecoveryProgressNotification` object.
+
+.. ts:def:: CoinRecoveryProgressNotification
+
+ interface CoinRecoveryProgressNotification {
+ type: NotificationType.CoinRecoveryProgress;
+ exchangeBaseUrl: string;
+ progressToken: string;
+ phase: CoinRecoveryPhase;
+ numChecked: number;
+ numDiscovered: number;
+ numQueued: number;
+ numRecovered: number;
+ recoveredAmount: AmountString;
+ numIssues: number;
+ }
+
+.. ts:def:: CoinRecoveryPhase
+
+ type CoinRecoveryPhase =
+ | "starting"
+ | "history"
+ | "derive"
+ | "melt"
+ | "reveal"
+ | "refresh"
+ | "complete"
+ | "incomplete"
+ | "failed"
+ | "cancelled";
+
+
+.. _wallet-notif-balance-change:
+
+**balance-change**
+
+Invalidates balance data, including flags and refresh information. The
+monetary amounts need not have changed, for example when a refresh cost
+becomes ready. Clients should re-query
+:ref:`getBalances <wallet-op-getBalances>` in response.
+
+The payload is a `BalanceChangeNotification` object.
+
+.. ts:def:: BalanceChangeNotification
+
+ interface BalanceChangeNotification {
+ type: NotificationType.BalanceChange;
+
+ // If set to true, the balance change is internal to the wallet
+ // and not visible to the user. (For example when the material
+ // balance changes via a refresh, but the available balance
+ // stays the same.)
+ isInternal?: boolean;
+
+ // Transaction ID of the transaction that caused the balance
+ // update. Only used as a hint for debugging, should not be
+ // relied upon by clients.
+ hintTransactionId: string;
+ }
+
+
+.. _wallet-notif-bank-account-change:
+
+**bank-account-change**
+
+Emitted when a bank account known to the wallet was added, changed or
+deleted. Clients should re-query
+:ref:`listBankAccounts <wallet-op-listBankAccounts>`.
+
+The payload is a `BankAccountChangeNotification` object.
+
+.. ts:def:: BankAccountChangeNotification
+
+ interface BankAccountChangeNotification {
+ type: NotificationType.BankAccountChange;
+
+ // ID of the affected bank account.
+ bankAccountId: string;
+ }
+
+
+.. _wallet-notif-backup-error:
+
+**backup-error**
+
+Signals the failure of a backup operation. The current wallet-core
+implementation does not emit this notification.
+
+The payload is a `BackupOperationErrorNotification` object.
+
+.. ts:def:: BackupOperationErrorNotification
+
+ interface BackupOperationErrorNotification {
+ type: NotificationType.BackupOperationError;
+ error: TalerErrorDetail;
+ }
+
+
+.. _wallet-notif-contact-added:
+
+**contact-added**
+
+Emitted when a contact was added to the wallet's address book. Clients
+should re-query :ref:`getContacts <wallet-op-getContacts>`.
+
+The payload is a `ContactAddedNotification` object.
+
+.. ts:def:: ContactAddedNotification
+
+ interface ContactAddedNotification {
+ type: NotificationType.ContactAdded;
+
+ // The contact that was added.
+ contact: ContactEntry;
+ }
+
+
+.. _wallet-notif-contact-deleted:
+
+**contact-deleted**
+
+Emitted when a contact was deleted from the wallet's address book. Clients
+should re-query :ref:`getContacts <wallet-op-getContacts>`.
+
+The payload is a `ContactDeletedNotification` object.
+
+.. ts:def:: ContactDeletedNotification
+
+ interface ContactDeletedNotification {
+ type: NotificationType.ContactDeleted;
+
+ // The contact that was deleted.
+ contact: ContactEntry;
+ }
+
+
+.. _wallet-notif-mailbox-message-added:
+
+**mailbox-message-added**
+
+Emitted when a message was added to the wallet's mailbox, for example a
+payment request received from another wallet user.
+
+The payload is a `MailboxMessageAddedNotification` object.
+
+.. ts:def:: MailboxMessageAddedNotification
+
+ interface MailboxMessageAddedNotification {
+ type: NotificationType.MailboxMessageAdded;
+
+ // The message that was added.
+ message: MailboxMessageRecord;
+ }
+
+
+.. _wallet-notif-mailbox-message-deleted:
+
+**mailbox-message-deleted**
+
+Emitted when a message was deleted from the wallet's mailbox.
+
+The payload is a `MailboxMessageDeletedNotification` object.
+
+.. ts:def:: MailboxMessageDeletedNotification
+
+ interface MailboxMessageDeletedNotification {
+ type: NotificationType.MailboxMessageDeleted;
+
+ // The message that was deleted.
+ message: MailboxMessageRecord;
+ }
+
+
+.. _wallet-notif-transaction-state-transition:
+
+**transaction-state-transition**
+
+Emitted when a transaction moves from one state to another. Clients should
+re-query the affected transaction; the ``causeHint`` is only a debugging
+aid and must not be relied upon.
+
+The payload is a `TransactionStateTransitionNotification` object.
+
+.. ts:def:: TransactionStateTransitionNotification
+
+ interface TransactionStateTransitionNotification {
+ type: NotificationType.TransactionStateTransition;
+
+ // Identifier of the affected transaction.
+ transactionId: string;
+
+ // A hint as to why the transition happened.
+ // Should not be relied upon by clients.
+ causeHint: string | undefined;
+
+ // State before the transition.
+ oldTxState: TransactionState;
+
+ // State after the transition.
+ newTxState: TransactionState;
+
+ // Internal ID of the new state. Must not be used by the UI,
+ // only used for testing.
+ newStId: number;
+
+ // Short summary of the error for an error transition.
+ errorInfo?: ErrorInfoSummary;
+ }
+
+.. ts:def:: ErrorInfoSummary
+
+ interface ErrorInfoSummary {
+ code: number;
+ hint?: string;
+ message?: string;
+ }
+
+
+.. _wallet-notif-exchange-state-transition:
+
+**exchange-state-transition**
+
+Emitted when the state of an exchange entry changes. If
+``oldExchangeState`` is missing, the entry was newly created; if
+``newExchangeState`` is missing, the entry was deleted.
+
+The payload is an `ExchangeStateTransitionNotification` object.
+
+.. ts:def:: ExchangeStateTransitionNotification
+
+ interface ExchangeStateTransitionNotification {
+ type: NotificationType.ExchangeStateTransition;
+
+ // Identification of the exchange entry that this
+ // notification is about.
+ exchangeBaseUrl: string;
+
+ // A hint as to why the transition happened.
+ // Should not be relied upon by clients.
+ causeHint: string | undefined;
+
+ // If missing, the notification means that
+ // the exchange entry is newly created.
+ oldExchangeState?: ExchangeEntryState;
+
+ // New state of the exchange.
+ // If missing, the exchange entry got deleted.
+ newExchangeState?: ExchangeEntryState;
+
+ // Summary of the error that occurred when trying to update
+ // the exchange entry, if applicable.
+ errorInfo?: ErrorInfoSummary;
+ }
+
+.. ts:def:: ExchangeEntryState
+
+ interface ExchangeEntryState {
+ // Status of the exchange's terms of service.
+ tosStatus: ExchangeTosStatus;
+
+ // Lifecycle status of the exchange entry.
+ exchangeEntryStatus: ExchangeEntryStatus;
+
+ // Status of the last update of the exchange's key material.
+ exchangeUpdateStatus: ExchangeUpdateStatus;
+ }
+
+
+.. _wallet-notif-idle:
+
+**idle**
+
+Emitted when wallet-core becomes idle, that is when no background task
+that keeps the wallet active is running anymore. Mainly useful for test
+harnesses that wait for the wallet to quiesce.
+
+The payload is an `IdleNotification` object.
+
+.. ts:def:: IdleNotification
+
+ interface IdleNotification {
+ type: NotificationType.Idle;
+ }
+
+
+.. _wallet-notif-task-observability-event:
+
+**task-observability-event**
+
+Reports a single observability event of a background task. These
+notifications are only emitted when the testing option
+``emitObservabilityEvents`` is enabled in the wallet run configuration.
+
+The payload is a `TaskProgressNotification` object.
+
+.. ts:def:: TaskProgressNotification
+
+ interface TaskProgressNotification {
+ type: NotificationType.TaskObservabilityEvent;
+ taskId: string;
+ event: ObservabilityEvent;
+ }
+
+.. ts:def:: ObservabilityEvent
+
+ type ObservabilityEvent =
+ | {
+ id: string;
+ when: AbsoluteTime;
+ type: ObservabilityEventType.HttpFetchStart;
+ url: string;
+ longPolling: boolean;
+ }
+ | {
+ id: string;
+ when: AbsoluteTime;
+ type: ObservabilityEventType.HttpFetchFinishSuccess;
+ url: string;
+ status: number;
+ durationMs: number;
+ longPolling: boolean;
+ }
+ | {
+ id: string;
+ when: AbsoluteTime;
+ type: ObservabilityEventType.HttpFetchFinishError;
+ url: string;
+ error: TalerErrorDetail;
+ durationMs: number;
+ longPolling: boolean;
+ }
+ | {
+ type: ObservabilityEventType.DbQueryStart;
+ name: string;
+ location: string;
+ }
+ | {
+ type: ObservabilityEventType.DbQueryFinishSuccess;
+ name: string;
+ location: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.DbQueryFinishError;
+ name: string;
+ location: string;
+ error: TalerErrorDetail;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.RequestStart;
+ name: string;
+ }
+ | {
+ type: ObservabilityEventType.RequestFinishSuccess;
+ operation: string;
+ requestId: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.RequestFinishError;
+ operation: string;
+ requestId: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.TaskStart;
+ taskId: string;
+ }
+ | {
+ type: ObservabilityEventType.TaskStop;
+ taskId: string;
+ }
+ | {
+ type: ObservabilityEventType.TaskReset;
+ taskId: string;
+ }
+ | {
+ type: ObservabilityEventType.DeclareTaskDependency;
+ taskId: string;
+ }
+ | {
+ type: ObservabilityEventType.CryptoStart;
+ operation: string;
+ }
+ | {
+ type: ObservabilityEventType.CryptoFinishSuccess;
+ operation: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.CryptoFinishError;
+ operation: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.ShepherdTaskResult;
+ taskId: string;
+ resultType: string;
+ durationMs: number;
+ }
+ | {
+ type: ObservabilityEventType.Message;
+ contents: string;
+ }
+ | {
+ type: ObservabilityEventType.DeclareConcernsTransaction;
+ transactionId: TransactionIdStr;
+ };
+
+.. ts:def:: ObservabilityEventType
+
+ enum ObservabilityEventType {
+ HttpFetchStart = "http-fetch-start",
+ HttpFetchFinishError = "http-fetch-finish-error",
+ HttpFetchFinishSuccess = "http-fetch-finish-success",
+ DbQueryStart = "db-query-start",
+ DbQueryFinishSuccess = "db-query-finish-success",
+ DbQueryFinishError = "db-query-finish-error",
+ RequestStart = "request-start",
+ RequestFinishSuccess = "request-finish-success",
+ RequestFinishError = "request-finish-error",
+ TaskStart = "task-start",
+ TaskStop = "task-stop",
+ TaskReset = "task-reset",
+ ShepherdTaskResult = "shepherd-task-result",
+ DeclareTaskDependency = "declare-task-dependency",
+ CryptoStart = "crypto-start",
+ CryptoFinishSuccess = "crypto-finish-success",
+ CryptoFinishError = "crypto-finish-error",
+ Message = "message",
+
+ // Declares that an observability event is relevant to a particular
+ // transaction. If emitted from a request/task, all past/future
+ // events for that request/task should be shown for the
+ // transaction as well.
+ DeclareConcernsTransaction = "declare-concerns-transaction",
+ }
+
+
+.. _wallet-notif-request-observability-event:
+
+**request-observability-event**
+
+Reports a single observability event of an API request; ``requestId``
+matches the ``id`` of the corresponding request envelope. These
+notifications are only emitted when the testing option
+``emitObservabilityEvents`` is enabled in the wallet run configuration.
+
+The payload is a `RequestObservabilityEventNotification` object.
+
+.. ts:def:: RequestObservabilityEventNotification
+
+ interface RequestObservabilityEventNotification {
+ type: NotificationType.RequestObservabilityEvent;
+ requestId: string;
+ operation: string;
+ event: ObservabilityEvent;
+ }
+
+
+.. _wallet-notif-request-progress-error:
+
+**request-progress-error**
+
+Emitted when an attempt of a long-running request with a ``progressToken``
+failed and will be retried automatically after ``nextRetryDelay``. The
+client can cancel the request with
+:ref:`cancelProgressToken <wallet-op-cancelProgressToken>` or trigger an
+immediate retry with
+:ref:`retryProgressTokenNow <wallet-op-retryProgressTokenNow>`.
+
+The payload is a `RequestProgressNotification` object.
+
+.. ts:def:: RequestProgressNotification
+
+ interface RequestProgressNotification {
+ type: NotificationType.RequestProgressError;
+ progressToken: string;
+ operation: string;
+ error: TalerErrorDetail;
+ nextRetryDelay: TalerProtocolDuration;
+ retryCounter: number;
+ }
+
+
+.. _wallet-notif-request-progress-phase:
+
+**request-progress-phase**
+
+Emitted when a long-running request with a ``progressToken`` takes longer
+than expected: ``delayed`` after about five seconds, ``stalled`` after
+about ten seconds (at which point the user may want to retry), and
+``done`` when the request finished and no further progress notifications
+will be sent.
+
+The payload is a `RequestProgressPhaseNotification` object.
+
+.. ts:def:: RequestProgressPhaseNotification
+
+ interface RequestProgressPhaseNotification {
+ type: NotificationType.RequestProgressPhase;
+ progressToken: string;
+ operation: string;
+
+ // delayed: request is taking longer than expected (usually
+ // after 5s)
+ // stalled: request is taking *very* long, user can retry
+ // (usually after 10s)
+ // done: no further progress notifications will be sent
+ phase: "delayed" | "stalled" | "done";
+ }
+
+
+.. _wallet-notif-database-maintenance-progress:
+
+**database-maintenance-progress**
+
+Emitted while startup fixups or a database migration hold the database
+gate, reporting progress of the maintenance operation. Intermediate
+updates may be coalesced by wallet-core, but the first and the terminal
+(``complete`` or ``failed``) events are always delivered.
+
+The payload is a `DatabaseMaintenanceProgressNotification` object.
+
+.. ts:def:: DatabaseMaintenanceProgressNotification
+
+ interface DatabaseMaintenanceProgressNotification {
+ type: NotificationType.DatabaseMaintenanceProgress;
+ operation:
+ | "indexeddb-fixup"
+ | "indexeddb-to-native-migration"
+ | "cross-backend-import";
+
+ // Token of the API request that initiated this operation,
+ // when available.
+ progressToken?: string;
+
+ phase: "fixup" | "copy" | "verify" | "complete" | "failed";
+
+ // Current fixup or backend-neutral store, when one is active.
+ step?: string;
+
+ completedSteps: number;
+ totalSteps: number;
+
+ // Records completed in the current phase across the
+ // whole migration.
+ processedRecords?: number;
+
+ // Total records in the whole migration, known before
+ // copying starts.
+ totalRecords?: number;
+
+ // Rough overall migration completion, from 0 through 100.
+ completionPercent?: number;
+
+ // Why the maintenance operation failed. Present when
+ // phase is "failed".
+ error?: TalerErrorDetail;
+ }
diff --git a/core/wallet-core/p2p.rst b/core/wallet-core/p2p.rst
@@ -0,0 +1,548 @@
+.. _wallet-op-preparePeerPushCredit:
+
+**preparePeerPushCredit**
+
+Check an incoming peer push payment. The payment is identified either
+by a ``taler://pay-push/`` URI received from the sender or by the
+``transactionId`` of an existing peer-push-credit transaction.
+
+**Request:**
+
+ The request body must be a `PreparePeerPushCreditRequest` object.
+ Either ``talerUri`` or ``transactionId`` must be specified.
+
+**Response:**
+
+ On success, the result is a `PreparePeerPushCreditResponse` object,
+ carrying the contract terms and the amounts the wallet would receive,
+ so that the user can review the payment before accepting it with
+ :ref:`confirmPeerPushCredit <wallet-op-confirmPeerPushCredit>`.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_CONTRACT_NOT_FOUND``,
+ ``WALLET_PEER_PUSH_CREDIT_PURSE_GONE``, ``WALLET_TALER_URI_MALFORMED``,
+ ``WALLET_TRANSACTION_NOT_FOUND``.
+
+**Details:**
+
+ When called with a ``talerUri`` for a payment that is not yet known
+ to the wallet, wallet-core downloads the contract from the exchange,
+ checks the status of the purse and creates the peer-push-credit
+ transaction. Calling the operation again with the same URI or with
+ the resulting ``transactionId`` returns the existing state.
+
+.. ts:def:: PreparePeerPushCreditRequest
+
+ // Either talerUri or transactionId must be specified.
+ interface PreparePeerPushCreditRequest {
+ talerUri?: string;
+ transactionId?: string;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: PreparePeerPushCreditResponse
+
+ interface PreparePeerPushCreditResponse {
+ contractTerms: PeerContractTerms;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+
+ transactionId: TransactionIdStr;
+
+ // State of the existing or newly created transaction.
+ txState: TransactionState;
+
+ exchangeBaseUrl: string;
+
+ scopeInfo: ScopeInfo;
+
+ // Deprecated.
+ amount: AmountString;
+ }
+
+.. ts:def:: PeerContractTerms
+
+ // Contract terms between two wallets (as opposed to a merchant and
+ // wallet).
+ interface PeerContractTerms {
+ amount: AmountString;
+ summary: string;
+ icon_id?: string;
+ purse_expiration: TalerProtocolTimestamp;
+ }
+
+
+.. _wallet-op-checkPeerPushDebit:
+
+**checkPeerPushDebit**
+
+This operation is **deprecated**. Use :ref:`checkPeerPushDebitV2
+<wallet-op-checkPeerPushDebitV2>` instead.
+
+Check if initiating a peer push payment is possible based on the funds
+in the wallet. Unlike the V2 operation, an insufficient balance is
+reported via the ``WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE``
+error instead of a typed result.
+
+**Request:**
+
+ The request body must be a `CheckPeerPushDebitRequest` object.
+
+**Response:**
+
+ On success, the result is a `CheckPeerPushDebitOkResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE``,
+ ``WALLET_NO_SUITABLE_EXCHANGE``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+.. ts:def:: CheckPeerPushDebitRequest
+
+ interface CheckPeerPushDebitRequest {
+ // Preferred exchange to use for the p2p payment.
+ exchangeBaseUrl?: string;
+
+ // Instructed amount.
+ amount: AmountString;
+
+ // Restrict the scope of funds that can be spent via the given
+ // scope info.
+ restrictScope?: ScopeInfo;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: CheckPeerPushDebitOkResponse
+
+ interface CheckPeerPushDebitOkResponse {
+ type: "ok";
+
+ amountRaw: AmountString;
+
+ amountEffective: AmountString;
+
+ // Exchange base URL.
+ exchangeBaseUrl: string;
+
+ // Maximum expiration date, based on how close the coins used for
+ // the payment are to expiry. The value is based on when the
+ // wallet would typically refresh the coins on its own, leaving
+ // enough time to get a refund for the push payment and refresh
+ // the coin.
+ maxExpirationDate: TalerProtocolTimestamp;
+
+ // Default expiration, as given by the exchange
+ // (or 1 week if the exchange does not specify it).
+ defaultExpiration: TalerProtocolDuration;
+
+ // Opaque description of the values reviewed by the caller.
+ // Passing this back to initiatePeerPushDebit makes wallet-core
+ // reject the operation when coin selection, fees or the selected
+ // exchange changed in the meantime. Optional for compatibility
+ // with older wallet-core implementations.
+ peerPushDebitQuote?: string;
+ }
+
+
+.. _wallet-op-checkPeerPushDebitV2:
+
+**checkPeerPushDebitV2**
+
+Check if initiating a peer push payment is possible based on the funds
+in the wallet.
+
+**Request:**
+
+ The request body must be a `CheckPeerPushDebitRequest` object.
+
+**Response:**
+
+ On success, the result is a `CheckPeerPushDebitResponse` object, a
+ discriminated union on the ``type`` field: ``"ok"``
+ (`CheckPeerPushDebitOkResponse`) carries the reviewed amounts and a
+ quote for :ref:`initiatePeerPushDebit <wallet-op-initiatePeerPushDebit>`;
+ ``"insufficient-balance"``
+ (`CheckPeerPushDebitInsufficientBalanceResponse`) explains why the
+ wallet cannot cover the payment.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE``,
+ ``WALLET_NO_SUITABLE_EXCHANGE``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+.. ts:def:: CheckPeerPushDebitResponse
+
+ type CheckPeerPushDebitResponse =
+ | CheckPeerPushDebitOkResponse
+ | CheckPeerPushDebitInsufficientBalanceResponse;
+
+.. ts:def:: CheckPeerPushDebitInsufficientBalanceResponse
+
+ interface CheckPeerPushDebitInsufficientBalanceResponse {
+ type: "insufficient-balance";
+
+ insufficientBalanceDetails: PaymentInsufficientBalanceDetails;
+ }
+
+
+.. _wallet-op-initiatePeerPushDebit:
+
+**initiatePeerPushDebit**
+
+Initiate an outgoing peer push payment.
+
+**Request:**
+
+ The request body must be an `InitiatePeerPushDebitRequest` object.
+
+**Response:**
+
+ On success, the result is an `InitiatePeerPushDebitResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE``,
+ ``WALLET_NO_SUITABLE_EXCHANGE``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ ``exchangeBaseUrl`` and ``restrictScope`` are mutually exclusive.
+ The ``purse_expiration`` in ``partialContractTerms`` must be finite
+ and in the future. When a ``peerPushDebitQuote`` obtained from
+ :ref:`checkPeerPushDebitV2 <wallet-op-checkPeerPushDebitV2>` is
+ passed, an explicit expiration is required, and wallet-core rejects
+ the request when it does not match the quote or when the coin
+ selection, fees or the selected exchange changed since the quote was
+ made.
+
+ The ``taler://pay-push/`` URI to hand to the receiver is not part of
+ the response; it is available from the transaction (see
+ :ref:`getTransactionById <wallet-op-getTransactionById>`) once the
+ transaction is pending.
+
+.. ts:def:: InitiatePeerPushDebitRequest
+
+ interface InitiatePeerPushDebitRequest {
+ exchangeBaseUrl?: string;
+
+ // Restrict the scope of funds that can be spent via the given
+ // scope info.
+ restrictScope?: ScopeInfo;
+
+ // Quote returned by checkPeerPushDebitV2.
+ peerPushDebitQuote?: string;
+
+ partialContractTerms: PartialPeerContractTerms;
+ }
+
+.. ts:def:: PartialPeerContractTerms
+
+ interface PartialPeerContractTerms {
+ amount: AmountString;
+ summary: string;
+ icon_id?: string;
+ purse_expiration?: TalerProtocolTimestamp;
+ }
+
+.. ts:def:: InitiatePeerPushDebitResponse
+
+ interface InitiatePeerPushDebitResponse {
+ exchangeBaseUrl: string;
+ pursePub: string;
+ mergePriv: string;
+ contractPriv: string;
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-confirmPeerPushCredit:
+
+**confirmPeerPushCredit**
+
+Accept an incoming peer push payment, after reviewing it with
+:ref:`preparePeerPushCredit <wallet-op-preparePeerPushCredit>`.
+
+**Request:**
+
+ The request body must be a `ConfirmPeerPushCreditRequest` object.
+
+**Response:**
+
+ On success, the result is an `AcceptPeerPushPaymentResponse` object.
+ Confirmation is idempotent: confirming a transaction that already
+ left the review state returns the same ``transactionId``.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_KYC_LIMIT_EXCEEDED``, ``WALLET_EXCHANGE_TOS_NOT_ACCEPTED``,
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_EXCHANGE_KEYS_NOT_ACCEPTED``.
+
+.. ts:def:: ConfirmPeerPushCreditRequest
+
+ interface ConfirmPeerPushCreditRequest {
+ transactionId: string;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: AcceptPeerPushPaymentResponse
+
+ interface AcceptPeerPushPaymentResponse {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-checkPeerPullCredit:
+
+**checkPeerPullCredit**
+
+Check fees for an outgoing peer pull payment, i.e. a payment request
+that this wallet creates for another wallet to pay.
+
+**Request:**
+
+ The request body must be a `CheckPeerPullCreditRequest` object.
+ Either ``exchangeBaseUrl`` or ``restrictScope`` must be specified.
+
+**Response:**
+
+ On success, the result is a `CheckPeerPullCreditResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_NO_SUITABLE_EXCHANGE``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+.. ts:def:: CheckPeerPullCreditRequest
+
+ interface CheckPeerPullCreditRequest {
+ // Require using this particular exchange for this operation.
+ exchangeBaseUrl?: string;
+
+ restrictScope?: ScopeInfo;
+
+ amount: AmountString;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: CheckPeerPullCreditResponse
+
+ interface CheckPeerPullCreditResponse {
+ exchangeBaseUrl: string;
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+
+ // Wallet-selected default expiration for the request.
+ defaultExpiration: TalerProtocolDuration;
+
+ // Number of coins that will be used; can be used by the UI to
+ // warn if excessively large.
+ numCoins: number;
+ }
+
+
+.. _wallet-op-initiatePeerPullCredit:
+
+**initiatePeerPullCredit**
+
+Initiate an outgoing peer pull payment: create a payment request
+(invoice) that another wallet can pay.
+
+**Request:**
+
+ The request body must be an `InitiatePeerPullCreditRequest` object.
+
+**Response:**
+
+ On success, the result is an `InitiatePeerPullCreditResponse`
+ object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_NO_SUITABLE_EXCHANGE``, ``WALLET_KYC_LIMIT_EXCEEDED``,
+ ``WALLET_EXCHANGE_TOS_NOT_ACCEPTED``, ``WALLET_CORE_API_BAD_REQUEST``,
+ ``WALLET_EXCHANGE_KEYS_NOT_ACCEPTED``.
+
+**Details:**
+
+ The ``taler://pay-pull/`` URI to hand to the payer is available from
+ the transaction (see :ref:`getTransactionById
+ <wallet-op-getTransactionById>`) once the transaction is in the
+ appropriate state; the ``talerUri`` field of the response is
+ deprecated because it is not necessarily valid yet.
+
+.. ts:def:: InitiatePeerPullCreditRequest
+
+ interface InitiatePeerPullCreditRequest {
+ exchangeBaseUrl?: string;
+ partialContractTerms: PeerContractTerms;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: InitiatePeerPullCreditResponse
+
+ interface InitiatePeerPullCreditResponse {
+ // Taler URI for the other party to make the requested payment.
+ // Deprecated: not necessarily valid yet until the transaction is
+ // in the right state.
+ talerUri?: string;
+
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-preparePeerPullDebit:
+
+**preparePeerPullDebit**
+
+Prepare for an incoming peer pull payment: another wallet requests to
+be paid by this wallet. The request is identified either by a
+``taler://pay-pull/`` URI received from the other party or by the
+``transactionId`` of an existing peer-pull-debit transaction.
+
+**Request:**
+
+ The request body must be a `PreparePeerPullDebitRequest` object.
+ Either ``talerUri`` or ``transactionId`` must be specified.
+
+**Response:**
+
+ On success, the result is a `PreparePeerPullDebitResponse` object,
+ carrying the contract terms and the amounts, so that the user can
+ review the request before paying with :ref:`confirmPeerPullDebit
+ <wallet-op-confirmPeerPullDebit>`.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_CONTRACT_NOT_FOUND``,
+ ``WALLET_PEER_PULL_DEBIT_PURSE_GONE``,
+ ``WALLET_PEER_PULL_DEBIT_ALREADY_PAID``,
+ ``WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE``,
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_TRANSACTION_NOT_FOUND``.
+
+.. ts:def:: PreparePeerPullDebitRequest
+
+ // Either talerUri or transactionId must be specified.
+ interface PreparePeerPullDebitRequest {
+ talerUri?: string;
+ transactionId?: string;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: PreparePeerPullDebitResponse
+
+ interface PreparePeerPullDebitResponse {
+ contractTerms: PeerContractTerms;
+
+ amountRaw: AmountString;
+ amountEffective: AmountString;
+
+ transactionId: TransactionIdStr;
+
+ // State of the existing or newly created transaction.
+ txState: TransactionState;
+
+ exchangeBaseUrl: string;
+
+ scopeInfo: ScopeInfo;
+
+ // Deprecated: redundant field with bad name, will be removed soon.
+ amount: AmountString;
+ }
+
+
+.. _wallet-op-confirmPeerPullDebit:
+
+**confirmPeerPullDebit**
+
+Accept an incoming peer pull payment (i.e. pay the other party), after
+reviewing the request with :ref:`preparePeerPullDebit
+<wallet-op-preparePeerPullDebit>`.
+
+**Request:**
+
+ The request body must be a `ConfirmPeerPullDebitRequest` object.
+
+**Response:**
+
+ On success, the result is an `AcceptPeerPullPaymentResponse` object.
+ Confirmation is idempotent: confirming a transaction that already
+ left the review state returns the same ``transactionId``.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE``,
+ ``WALLET_TRANSACTION_NOT_FOUND``.
+
+.. ts:def:: ConfirmPeerPullDebitRequest
+
+ interface ConfirmPeerPullDebitRequest {
+ transactionId: TransactionIdStr;
+ }
+
+.. ts:def:: AcceptPeerPullPaymentResponse
+
+ interface AcceptPeerPullPaymentResponse {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-getMaxPeerPushDebitAmount:
+
+**getMaxPeerPushDebitAmount**
+
+Query the maximum amount that can currently be sent via a peer push
+payment in the given currency.
+
+**Request:**
+
+ The request body must be a `GetMaxPeerPushDebitAmountRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetMaxPeerPushDebitAmountResponse`
+ object.
+
+**Details:**
+
+ The result is the best per-exchange maximum across the exchanges
+ known to the wallet for the currency (restricted to ``restrictScope``
+ when given, and including the expected outputs of pending refresh
+ operations). ``effectiveAmount`` is the total balance effect on the
+ wallet and ``rawAmount`` the amount the receiver would get after
+ fees. When no exchange can serve the payment, zero amounts are
+ returned and ``exchangeBaseUrl`` is omitted.
+
+.. ts:def:: GetMaxPeerPushDebitAmountRequest
+
+ interface GetMaxPeerPushDebitAmountRequest {
+ currency: string;
+
+ // Preferred exchange to use for the p2p payment.
+ exchangeBaseUrl?: string;
+
+ restrictScope?: ScopeInfo;
+ }
+
+.. ts:def:: GetMaxPeerPushDebitAmountResponse
+
+ interface GetMaxPeerPushDebitAmountResponse {
+ effectiveAmount: AmountString;
+ rawAmount: AmountString;
+ exchangeBaseUrl?: string;
+ }
diff --git a/core/wallet-core/payments.rst b/core/wallet-core/payments.rst
@@ -0,0 +1,985 @@
+.. _wallet-op-getChoicesForPayment:
+
+**getChoicesForPayment**
+
+Get the list of contract choices for a payment transaction in the dialog
+(confirmation) state, together with information on whether each choice
+can be paid with the funds available in the wallet, and whether a
+specific choice should be paid automatically without user confirmation,
+based on the user's configuration or the type of payment requested.
+
+For a contract v1 order, the ``choices`` array of the result mirrors the
+choices of the contract. For a contract v0 order, which has no choices,
+it contains a single choice with no inputs/outputs.
+
+**Request:**
+
+ The ``args`` must be a `GetChoicesForPaymentRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetChoicesForPaymentResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``,
+ ``WALLET_TRANSACTION_NOT_FOUND``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+ The ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED`` error is returned
+ while the contract terms of the payment have not been downloaded yet;
+ the caller should wait for the corresponding transaction state
+ transition and try again.
+
+.. ts:def:: GetChoicesForPaymentRequest
+
+ interface GetChoicesForPaymentRequest {
+ // Transaction identifier of the payment.
+ transactionId: string;
+
+ // Force a particular coin selection when evaluating
+ // whether the choices are payable.
+ forcedCoinSel?: ForcedCoinSel;
+ }
+
+.. ts:def:: ForcedCoinSel
+
+ interface ForcedCoinSel {
+ coins: {
+ value: AmountString;
+ contribution: AmountString;
+ }[];
+ }
+
+.. ts:def:: GetChoicesForPaymentResult
+
+ type GetChoicesForPaymentResult = {
+ // Details for all choices in the contract.
+ //
+ // The index in this array corresponds to the choice
+ // index in the original contract v1. For contract v0
+ // orders, it will only contain a single choice with no
+ // inputs/outputs.
+ choices: ChoiceSelectionDetail[];
+
+ // Index of the choice in the `choices` array to present
+ // to the user as default.
+ //
+ // Won't be set if no default selection is configured
+ // or no choice is payable; otherwise it will always
+ // be 0 for v0 orders.
+ defaultChoiceIndex?: number;
+
+ // Whether the choice referenced by `automaticExecutableIndex`
+ // should be confirmed automatically without user interaction.
+ //
+ // If true, the wallet should call `confirmPay` immediately
+ // afterwards; if false, the user should first be prompted
+ // to select and confirm a choice. Undefined when no
+ // choices are payable.
+ automaticExecution?: boolean;
+
+ // Index of the choice that would be set to automatically
+ // execute if the choice was payable. When `automaticExecution`
+ // is set to true, the payment should be confirmed with this
+ // choice index without user interaction.
+ automaticExecutableIndex?: number;
+
+ // Data extracted from the contract terms that
+ // is relevant for payment processing in the wallet.
+ contractTerms: MerchantContractTerms;
+ };
+
+.. ts:def:: ChoiceSelectionDetail
+
+ type ChoiceSelectionDetail =
+ | ChoiceSelectionDetailPaymentPossible
+ | ChoiceSelectionDetailInsufficientBalance;
+
+.. ts:def:: ChoiceSelectionDetailPaymentPossible
+
+ interface ChoiceSelectionDetailPaymentPossible {
+ status: ChoiceSelectionDetailType.PaymentPossible;
+
+ // Amount requested by the contract for this choice.
+ amountRaw: AmountString;
+
+ // Total cost of this choice for the wallet, including fees.
+ amountEffective: AmountString;
+
+ // Scope of the coins that would be spent, if known.
+ scopeInfo: ScopeInfo | undefined;
+
+ tokenDetails?: PaymentTokenAvailabilityDetails;
+ }
+
+.. ts:def:: ChoiceSelectionDetailInsufficientBalance
+
+ interface ChoiceSelectionDetailInsufficientBalance {
+ status: ChoiceSelectionDetailType.InsufficientBalance;
+
+ // Amount requested by the contract for this choice.
+ amountRaw: AmountString;
+
+ balanceDetails?: PaymentInsufficientBalanceDetails;
+ tokenDetails?: PaymentTokenAvailabilityDetails;
+ }
+
+.. ts:def:: ChoiceSelectionDetailType
+
+ type ChoiceSelectionDetailType =
+ | "payment-possible"
+ | "insufficient-balance";
+
+.. ts:def:: PaymentTokenAvailabilityDetails
+
+ interface PaymentTokenAvailabilityDetails {
+ // Number of tokens requested by the merchant.
+ tokensRequested: number;
+
+ // Number of tokens available to use.
+ tokensAvailable: number;
+
+ // Legacy compatibility field. Always zero: tokens from another
+ // merchant are counted as untrusted and cannot be used.
+ tokensUnexpected: number;
+
+ // Number of tokens not issued by the receiving merchant.
+ //
+ // Cannot be used to pay, so an error should be displayed.
+ tokensUntrusted: number;
+
+ perTokenFamily: {
+ [slug: string]: {
+ causeHint?: TokenAvailabilityHint;
+ requested: number;
+ available: number;
+ unexpected: number;
+ untrusted: number;
+ };
+ };
+ }
+
+.. ts:def:: TokenAvailabilityHint
+
+ type TokenAvailabilityHint =
+ | "wallet-tokens-available-insufficient"
+ | "merchant-unexpected"
+ | "merchant-untrusted";
+
+.. ts:def:: PaymentInsufficientBalanceDetails
+
+ // Detailed reason for why the wallet's balance is insufficient.
+ //
+ // Current wallet-core versions emit all structured fields. The
+ // legacy-only alternative lets clients continue decoding responses
+ // from older cores without allowing partially populated structured
+ // diagnostics.
+ type PaymentInsufficientBalanceDetails =
+ PaymentInsufficientBalanceCompatibilityDetails &
+ (PaymentInsufficientBalanceStructuredDetails
+ | PaymentInsufficientBalanceLegacyOnly);
+
+.. ts:def:: PaymentInsufficientBalanceStructuredDetails
+
+ // Structured explanation emitted by current wallet-core versions.
+ interface PaymentInsufficientBalanceStructuredDetails {
+ // Balance in the requested sender scope before payment
+ // restrictions.
+ balance: CoinSelectionBalanceSnapshot;
+
+ // Maximum contribution attainable under the failed request's
+ // actual restrictions and fee policy. For peer payments this is
+ // the maximum at one exchange, since peer payments cannot combine
+ // exchanges.
+ maximumPayableAmount: AmountString;
+
+ // Operation-wide reasons, in deterministic evaluation order.
+ reasons: CoinSelectionFailureReason[];
+
+ // Detailed analysis for every same-currency exchange known to
+ // the wallet.
+ exchanges: Record<string, CoinSelectionExchangeFailureDiagnostics>;
+ }
+
+.. ts:def:: CoinSelectionBalanceSnapshot
+
+ // Balance amounts before age, receiver, wire and fee restrictions.
+ interface CoinSelectionBalanceSnapshot {
+ // Balance that the wallet believes it can spend immediately.
+ material: AmountString;
+
+ // Expected effective output of unfinished refresh operations.
+ pendingRefresh: AmountString;
+
+ // Material balance plus pending refresh output.
+ available: AmountString;
+ }
+
+.. ts:def:: CoinSelectionExchangeFailureDiagnostics
+
+ interface CoinSelectionExchangeFailureDiagnostics {
+ // Balance held at this exchange before payment restrictions.
+ balance: CoinSelectionBalanceSnapshot;
+
+ // Maximum contribution selectable from this exchange for
+ // this request.
+ maximumPayableAmount: AmountString;
+
+ // Exchange-local reasons, in deterministic evaluation order.
+ reasons: CoinSelectionFailureReason[];
+ }
+
+.. ts:def:: PaymentInsufficientBalanceCompatibilityDetails
+
+ // Request context and compatibility fields shared by old and current
+ // insufficient-balance responses. Fields marked as deprecated are
+ // compatibility-only and planned for removal after consumers migrate.
+ interface PaymentInsufficientBalanceCompatibilityDetails {
+ // Amount requested by the merchant.
+ amountRequested: AmountString;
+
+ // Wire method for the requested payment, only applicable
+ // for merchant payments.
+ wireMethod?: string | undefined;
+
+ // Hint as to why the balance is insufficient.
+ //
+ // If this hint is not provided, the balance hints of the
+ // individual exchanges should be shown, as the overall reason
+ // might be a combination of the reasons for different exchanges.
+ //
+ // Deprecated: use `reasons`.
+ causeHint?: InsufficientBalanceHint;
+
+ // Balance of type "available".
+ // Deprecated: use `balance.available`.
+ balanceAvailable: AmountString;
+
+ // Balance of type "material".
+ // Deprecated: use `balance.material`.
+ balanceMaterial: AmountString;
+
+ // Balance of type "age-acceptable".
+ // Deprecated: use `reasons` and `exchanges`.
+ balanceAgeAcceptable: AmountString;
+
+ // Balance of type "receiver-acceptable".
+ // Deprecated: use `reasons` and `exchanges`.
+ balanceReceiverAcceptable: AmountString;
+
+ // Balance of type "receiver-exchange-url-acceptable".
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverExchangeUrlAcceptable: AmountString;
+
+ // Balance of type "receiver-exchange-pub-acceptable".
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverExchangePubAcceptable: AmountString;
+
+ // Balance of type "receiver-auditor-url-acceptable".
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverAuditorUrlAcceptable: AmountString;
+
+ // Balance of type "merchant-depositable".
+ // Deprecated: use `maximumPayableAmount` and `reasons`.
+ balanceReceiverDepositable: AmountString;
+
+ // Deprecated: use `maximumPayableAmount` and `reasons`.
+ balanceExchangeDepositable: AmountString;
+
+ // Maximum effective amount that the wallet can spend,
+ // when all fees are paid by the wallet.
+ // Deprecated: use `maximumPayableAmount`.
+ maxEffectiveSpendAmount: AmountString;
+
+ // Deprecated: use `exchanges`.
+ perExchange: {
+ [url: string]: {
+ // Deprecated: use `exchanges[url].balance.available`.
+ balanceAvailable: AmountString;
+
+ // Deprecated: use `exchanges[url].balance.material`.
+ balanceMaterial: AmountString;
+
+ // Deprecated: use `exchanges[url].maximumPayableAmount`
+ // and `exchanges[url].reasons`.
+ balanceExchangeDepositable: AmountString;
+
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceAgeAcceptable: AmountString;
+
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverAcceptable: AmountString;
+
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverExchangeUrlAcceptable: AmountString;
+
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverExchangePubAcceptable: AmountString;
+
+ // Deprecated: use `exchanges[url].reasons`.
+ balanceReceiverAuditorUrlAcceptable: AmountString;
+
+ // Deprecated: use `exchanges[url].maximumPayableAmount`.
+ balanceReceiverDepositable: AmountString;
+
+ // Deprecated: use `exchanges[url].maximumPayableAmount`.
+ maxEffectiveSpendAmount: AmountString;
+
+ // The exchange master public key configured by the merchant
+ // backend differs from the one of the coins stored in
+ // the wallet.
+ // Deprecated: use the receiver-exchange-master-pub-mismatch
+ // reason.
+ exchangeMasterPubMismatch: boolean;
+
+ // Exchange doesn't have global fees configured for the
+ // relevant year, p2p payments aren't possible.
+ // Deprecated: use the exchange-global-fees-unavailable reason.
+ missingGlobalFees: boolean;
+
+ // Hint that UIs should show to explain the insufficient
+ // balance.
+ // Deprecated: use `exchanges[url].reasons`.
+ causeHint?: InsufficientBalanceHint | undefined;
+ };
+ };
+ }
+
+.. ts:def:: PaymentInsufficientBalanceLegacyOnly
+
+ // Alternative emitted by older cores: none of the structured
+ // fields are present.
+ interface PaymentInsufficientBalanceLegacyOnly {
+ balance?: undefined;
+ maximumPayableAmount?: undefined;
+ reasons?: undefined;
+ exchanges?: undefined;
+ }
+
+.. ts:def:: CoinSelectionFailureReason
+
+ // Machine-readable reasons that prevented a requested coin
+ // selection. Unlike `InsufficientBalanceHint`, these values are
+ // exhaustive and can be reported together. Consumers must branch
+ // on the discriminator instead of assuming that the first entry
+ // is the only cause.
+ type CoinSelectionFailureReason =
+ | {
+ type: CoinSelectionFailureReasonType.AvailableBalanceInsufficient;
+ amountAvailable: AmountString;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.PendingRefresh;
+ amountPendingRefresh: AmountString;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.MinimumAge;
+ requiredMinimumAge: number;
+ amountAgeAcceptable: AmountString;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.ScopeRestricted;
+ scopeInfo: ScopeInfo;
+ }
+ | { type: CoinSelectionFailureReasonType.ReceiverNotAccepted }
+ | {
+ type: CoinSelectionFailureReasonType.ReceiverExchangeMasterPubMismatch;
+ walletMasterPub: string;
+ receiverMasterPubs: string[];
+ }
+ | {
+ type: CoinSelectionFailureReasonType.WireMethodUnsupported;
+ wireMethod: string;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.WireFeeUnavailable;
+ wireMethod: string;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.DepositAccountRestricted;
+ wireMethod: string;
+ accountRestrictions: Record<string, AccountRestriction[]>;
+ }
+ | { type: CoinSelectionFailureReasonType.ExchangeGlobalFeesUnavailable }
+ | {
+ type: CoinSelectionFailureReasonType.FeesNotCovered;
+ maximumPayableAmount: AmountString;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.BalanceFragmented;
+ combinedMaximumPayableAmount: AmountString;
+ }
+ | {
+ type: CoinSelectionFailureReasonType.SupersededExchangeMasterPub;
+ amountAffected: AmountString;
+ }
+ | { type: CoinSelectionFailureReasonType.SelectionFailed };
+
+.. ts:def:: CoinSelectionFailureReasonType
+
+ type CoinSelectionFailureReasonType =
+ | "available-balance-insufficient"
+ | "pending-refresh"
+ | "minimum-age"
+ | "scope-restricted"
+ | "receiver-not-accepted"
+ | "receiver-exchange-master-pub-mismatch"
+ | "wire-method-unsupported"
+ | "wire-fee-unavailable"
+ | "deposit-account-restricted"
+ | "exchange-global-fees-unavailable"
+ | "fees-not-covered"
+ | "balance-fragmented"
+ | "superseded-exchange-master-pub"
+ | "selection-failed";
+
+.. ts:def:: InsufficientBalanceHint
+
+ // Deprecated: use `CoinSelectionFailureReason` instead.
+ type InsufficientBalanceHint =
+ // Merchant doesn't accept money from exchange(s) that the
+ // wallet supports.
+ | "merchant-accept-insufficient"
+ // Merchant accepts funds from a matching exchange, but the funds
+ // can't be deposited with the wire method.
+ | "merchant-deposit-insufficient"
+ // While in principle the balance is sufficient, the age
+ // restriction on coins causes the spendable balance to be
+ // insufficient.
+ | "age-restricted"
+ // Wallet has enough available funds, but the material funds are
+ // insufficient. Usually because there is a pending refresh
+ // operation.
+ | "wallet-balance-material-insufficient"
+ // The wallet simply doesn't have enough available funds.
+ | "wallet-balance-available-insufficient"
+ // Exchange is missing the global fee configuration, thus fees are
+ // unknown and funds from this exchange can't be used for p2p
+ // payments.
+ | "exchange-missing-global-fees"
+ // Even though the balance looks sufficient for the instructed
+ // amount, the fees can be covered by neither the merchant nor
+ // the remaining wallet balance.
+ | "fees-not-covered";
+
+
+.. _wallet-op-preparePayForUriV2:
+
+**preparePayForUriV2**
+
+Prepare to make a payment based on a ``taler://pay/`` URI.
+
+Creates a payment transaction for the order identified by the URI, or
+reuses the existing transaction if the wallet already knows the order.
+The contract terms are then downloaded and processed by the transaction.
+Use :ref:`getChoicesForPayment <wallet-op-getChoicesForPayment>` to
+inspect the payable choices and :ref:`confirmPay <wallet-op-confirmPay>`
+to confirm the payment.
+
+**Request:**
+
+ The ``args`` must be a `PreparePayRequest` object.
+
+**Response:**
+
+ On success, the result is a `PreparePayV2Result` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_MERCHANT_ORDER_NOT_FOUND``,
+ ``WALLET_ORDER_ALREADY_CLAIMED``, ``WALLET_ORDER_ALREADY_PAID``,
+ ``WALLET_CONTRACT_TERMS_MALFORMED``,
+ ``WALLET_CONTRACT_TERMS_UNSUPPORTED``.
+
+.. ts:def:: PreparePayRequest
+
+ interface PreparePayRequest {
+ // The taler://pay/ URI to pay.
+ talerPayUri: string;
+ }
+
+.. ts:def:: PreparePayV2Result
+
+ interface PreparePayV2Result {
+ // Transaction identifier of the payment transaction.
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-preparePayForTemplateV2:
+
+**preparePayForTemplateV2**
+
+Prepare to make a payment based on a ``taler://pay-template/`` URI.
+
+Instantiates the referenced order template at the merchant, honoring
+``templateParams`` for template fields that are editable by the
+customer, and creates (or reuses) a payment transaction for the
+resulting order. As with
+:ref:`preparePayForUriV2 <wallet-op-preparePayForUriV2>`, the payment is
+then inspected with
+:ref:`getChoicesForPayment <wallet-op-getChoicesForPayment>` and
+confirmed with :ref:`confirmPay <wallet-op-confirmPay>`.
+
+**Request:**
+
+ The ``args`` must be a `PreparePayTemplateRequest` object.
+
+**Response:**
+
+ On success, the result is a `PreparePayV2Result` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_CONTRACT_TERMS_UNSUPPORTED``.
+
+.. ts:def:: PreparePayTemplateRequest
+
+ interface PreparePayTemplateRequest {
+ // The taler://pay-template/ URI to pay.
+ talerPayTemplateUri: string;
+
+ // Values for editable template fields.
+ templateParams?: TemplateParams;
+
+ // Enables progress correlation and cancellation through
+ // cancelProgressToken.
+ progressToken?: string;
+ }
+
+.. ts:def:: TemplateParams
+
+ type TemplateParams = {
+ amount?: AmountString;
+ summary?: string;
+ };
+
+
+.. _wallet-op-preparePayForPaivana:
+
+**preparePayForPaivana**
+
+Prepare a payment for an HTTP(S) resource protected by a Paivana
+paywall.
+
+The wallet requests the given ``url``, expects an HTTP 402 response that
+advertises a payment template in the ``Paivana`` header, instantiates
+that template into an order and creates (or reuses) a payment
+transaction for it. The returned ``paivana`` redemption metadata must
+be kept by the client; once the payment has succeeded, it is passed to
+:ref:`getPaivanaCookie <wallet-op-getPaivanaCookie>` to obtain the
+access cookie for the resource.
+
+**Request:**
+
+ The ``args`` must be a `PreparePayForPaivanaRequest` object.
+
+**Response:**
+
+ On success, the result is a `PreparePayForPaivanaResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_CORE_API_BAD_REQUEST``, ``WALLET_RECEIVED_MALFORMED_RESPONSE``,
+ ``WALLET_NETWORK_ERROR``, ``WALLET_HTTP_REQUEST_THROTTLED``,
+ ``WALLET_HTTP_REQUEST_GENERIC_TIMEOUT``,
+ ``WALLET_UNEXPECTED_REQUEST_ERROR``,
+ ``WALLET_CONTRACT_TERMS_UNSUPPORTED``.
+
+.. ts:def:: PreparePayForPaivanaRequest
+
+ interface PreparePayForPaivanaRequest {
+ // URL of the Paivana-protected resource to pay for.
+ url: string;
+
+ // Enables progress correlation and cancellation through
+ // cancelProgressToken.
+ progressToken?: string;
+ }
+
+.. ts:def:: PreparePayForPaivanaResult
+
+ interface PreparePayForPaivanaResult {
+ // Transaction identifier of the payment transaction.
+ transactionId: TransactionIdStr;
+
+ paivana: PaivanaRedemption;
+ }
+
+.. ts:def:: PaivanaRedemption
+
+ // Information needed to redeem a paid Paivana order for an
+ // access cookie.
+ interface PaivanaRedemption {
+ // Canonical HTTP(S) URL of the protected resource.
+ url: string;
+
+ // Crockford-base32 encoded 16-byte client nonce.
+ nonce: string;
+
+ // End of the access period used to derive the Paivana
+ // session ID.
+ expiration: TalerProtocolTimestamp;
+ }
+
+
+.. _wallet-op-getPaivanaCookie:
+
+**getPaivanaCookie**
+
+Redeem a successfully paid Paivana transaction for an access cookie.
+
+The payment transaction referenced by ``transactionId`` must have
+completed successfully, and the ``paivana`` redemption metadata must
+match the one returned by
+:ref:`preparePayForPaivana <wallet-op-preparePayForPaivana>` for that
+transaction. The wallet redeems the paid order at the Paivana endpoint
+of the protected resource and returns the access cookie; the client can
+then request the resource with this cookie. Error codes with the
+``PAIVANA_`` prefix are reported by the Paivana server and forwarded by
+the wallet.
+
+**Request:**
+
+ The ``args`` must be a `GetPaivanaCookieRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetPaivanaCookieResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_CORE_API_BAD_REQUEST``, ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``,
+ ``WALLET_RECEIVED_MALFORMED_RESPONSE``, ``WALLET_NETWORK_ERROR``,
+ ``WALLET_HTTP_REQUEST_THROTTLED``,
+ ``WALLET_HTTP_REQUEST_GENERIC_TIMEOUT``, ``PAIVANA_PAYMENT_MISSING``,
+ ``PAIVANA_BACKEND_REFUSED``, ``PAIVANA_ORDER_UNKNOWN``,
+ ``PAIVANA_BACKEND_ERROR``, ``PAIVANA_GET_ORDER_FAILED``,
+ ``PAIVANA_WRONG_ORDER``, ``PAIVANA_TOO_LATE``,
+ ``PAIVANA_INVALID_TARGET``.
+
+.. ts:def:: GetPaivanaCookieRequest
+
+ interface GetPaivanaCookieRequest {
+ // Transaction identifier of the paid Paivana payment.
+ transactionId: TransactionIdStr;
+
+ paivana: PaivanaRedemption;
+ }
+
+.. ts:def:: GetPaivanaCookieResult
+
+ interface GetPaivanaCookieResult {
+ // Plain Cookie request-header value, without Set-Cookie
+ // attributes.
+ cookie: string;
+ }
+
+
+.. _wallet-op-unclaimPayment:
+
+**unclaimPayment**
+
+Release this wallet's claim on an unpaid order, so that the payment can
+be handed off to another wallet.
+
+The merchant is asked to release the order, and the operation returns a
+public ``taler://pay/`` URI for the order that contains no private
+nonce. Another wallet can use this URI with
+:ref:`preparePayForUriV2 <wallet-op-preparePayForUriV2>` to claim and
+pay the order instead. The payment can only be released before it is
+paid; use :ref:`reclaimPayment <wallet-op-reclaimPayment>` to claim the
+order again for this wallet.
+
+**Request:**
+
+ The ``args`` must be an `UnclaimPaymentRequest` object.
+
+**Response:**
+
+ On success, the result is an `UnclaimPaymentResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``,
+ ``WALLET_CORE_REQUEST_CANCELLED``,
+ ``WALLET_UNEXPECTED_REQUEST_ERROR``.
+
+.. ts:def:: UnclaimPaymentRequest
+
+ interface UnclaimPaymentRequest {
+ // Transaction identifier of the payment to release.
+ transactionId: TransactionIdStr;
+
+ // Enables progress correlation and cancellation through
+ // cancelProgressToken.
+ progressToken?: string;
+ }
+
+.. ts:def:: UnclaimPaymentResult
+
+ interface UnclaimPaymentResult {
+ // Public taler://pay/ URI that another wallet can claim.
+ talerPayUri: TalerUriString;
+ }
+
+
+.. _wallet-op-reclaimPayment:
+
+**reclaimPayment**
+
+Claim an order again for this wallet after it was released for handoff
+with :ref:`unclaimPayment <wallet-op-unclaimPayment>`. Claiming again
+with the same nonce is idempotent.
+
+**Request:**
+
+ The ``args`` must be a `ReclaimPaymentRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+.. ts:def:: ReclaimPaymentRequest
+
+ interface ReclaimPaymentRequest {
+ // Transaction identifier of the payment to claim again.
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-checkPayForTemplate:
+
+**checkPayForTemplate**
+
+Check a ``taler://pay-template/`` URI without creating a payment
+transaction.
+
+Fetches the template details from the merchant, applies the overrides
+encoded in the URI (such as amount or summary), and returns the template
+details together with the currencies supported by the merchant. This
+allows the client to present the editable template fields to the user
+before calling
+:ref:`preparePayForTemplateV2 <wallet-op-preparePayForTemplateV2>`.
+
+**Request:**
+
+ The ``args`` must be a `CheckPayTemplateRequest` object.
+
+**Response:**
+
+ On success, the result is a `CheckPayTemplateReponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_CONTRACT_TERMS_UNSUPPORTED``.
+
+**Details:**
+
+ The ``templateDetails`` field is the template information served by
+ the merchant backend (a `WalletTemplateDetailsResponse` of the
+ merchant API), with the URI overrides already applied.
+
+.. ts:def:: CheckPayTemplateRequest
+
+ interface CheckPayTemplateRequest {
+ // The taler://pay-template/ URI to check.
+ talerPayTemplateUri: string;
+
+ // Enables progress correlation and cancellation through
+ // cancelProgressToken.
+ progressToken?: string;
+ }
+
+.. ts:def:: CheckPayTemplateReponse
+
+ type CheckPayTemplateReponse = {
+ // Template details served by the merchant backend.
+ templateDetails: WalletTemplateDetailsResponse;
+
+ // Currencies supported by the merchant, sorted.
+ supportedCurrencies: string[];
+ };
+
+
+.. _wallet-op-startRefundQueryForUri:
+
+**startRefundQueryForUri**
+
+Check for a refund based on a ``taler://refund`` URI.
+
+Locates the payment for the order identified by the URI and starts a
+refund query on it; the refund processing then continues as part of the
+payment transaction. The
+``WALLET_PURCHASE_NOT_FOUND`` error is returned when the wallet has no
+purchase for the order, for example because it was paid with a
+different wallet.
+
+**Request:**
+
+ The ``args`` must be a `PrepareRefundRequest` object.
+
+**Response:**
+
+ On success, the result is a `StartRefundQueryForUriResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``WALLET_PURCHASE_NOT_FOUND``.
+
+.. ts:def:: PrepareRefundRequest
+
+ interface PrepareRefundRequest {
+ // The taler://refund URI to check for refunds.
+ talerRefundUri: string;
+ }
+
+.. ts:def:: StartRefundQueryForUriResponse
+
+ interface StartRefundQueryForUriResponse {
+ // Transaction id of the *payment* where the refund query
+ // was started.
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-startRefundQuery:
+
+**startRefundQuery**
+
+Start a refund query for an existing payment transaction, referenced by
+its transaction identifier. Only payments that completed successfully
+are queried; for payments in any other state, the request has no
+effect.
+
+**Request:**
+
+ The ``args`` must be a `StartRefundQueryRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: StartRefundQueryRequest
+
+ interface StartRefundQueryRequest {
+ // Transaction identifier of the payment to query refunds for.
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-confirmPay:
+
+**confirmPay**
+
+Confirm a payment that was previously prepared with
+:ref:`preparePayForUriV2 <wallet-op-preparePayForUriV2>`,
+:ref:`preparePayForTemplateV2 <wallet-op-preparePayForTemplateV2>` or
+:ref:`preparePayForPaivana <wallet-op-preparePayForPaivana>`.
+
+The wallet selects the coins (and, for a contract v1 order, the tokens)
+for the payment and submits it to the merchant. For a contract v1
+order, ``choiceIndex`` must refer to one of the choices returned by
+:ref:`getChoicesForPayment <wallet-op-getChoicesForPayment>`.
+
+**Request:**
+
+ The ``args`` must be a `ConfirmPayRequest` object.
+
+**Response:**
+
+ On success, the result is a `ConfirmPayResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE``,
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``,
+ ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ Unless ``noWait`` is set, the operation waits for the first payment
+ result: when the payment succeeded, the result has type ``done``; when
+ the payment could not be completed (yet), the result has type
+ ``pending`` and carries the last error of the payment. With
+ ``noWait``, the operation returns a ``pending`` result immediately
+ and the payment status is communicated via notifications.
+
+ The ``WALLET_PAY_MERCHANT_INSUFFICIENT_BALANCE`` error carries
+ ``insufficientBalanceDetails`` of type
+ `PaymentInsufficientBalanceDetails` in its error detail.
+
+.. ts:def:: ConfirmPayRequest
+
+ interface ConfirmPayRequest {
+ // Transaction identifier of the prepared payment.
+ transactionId: TransactionIdStr;
+
+ // Request that a donation receipt for this payment is collected
+ // via the configured donau, if the selected choice offers a
+ // matching tax-receipt output.
+ useDonau?: boolean;
+
+ // Session ID override for the payment.
+ sessionId?: string;
+
+ // Currently ignored by wallet-core.
+ forcedCoinSel?: ForcedCoinSel;
+
+ // Legacy compatibility option for v1 orders. Ignored: tokens can
+ // only be spent at their issuing merchant, even when this is true.
+ forcedTokenSel?: boolean;
+
+ // Only applies to v1 orders.
+ choiceIndex?: number;
+
+ // Do not wait for the first payment success or error
+ // before returning a response. Instead, status will
+ // be communicated via notifications.
+ //
+ // Will become the default in future versions.
+ noWait?: boolean;
+ }
+
+.. ts:def:: ConfirmPayResult
+
+ // Result for confirmPay.
+ type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
+
+.. ts:def:: ConfirmPayResultDone
+
+ interface ConfirmPayResultDone {
+ type: ConfirmPayResultType.Done;
+ contractTerms: MerchantContractTermsV0;
+ transactionId: TransactionIdStr;
+ }
+
+.. ts:def:: ConfirmPayResultPending
+
+ interface ConfirmPayResultPending {
+ type: ConfirmPayResultType.Pending;
+ transactionId: TransactionIdStr;
+ lastError?: TalerErrorDetail | undefined;
+ }
+
+.. ts:def:: ConfirmPayResultType
+
+ type ConfirmPayResultType = "done" | "pending";
diff --git a/core/wallet-core/requests.rst b/core/wallet-core/requests.rst
@@ -0,0 +1,65 @@
+.. _wallet-op-retryProgressTokenNow:
+
+**retryProgressTokenNow**
+
+Retry a long-running request now instead of waiting for its scheduled
+retry delay.
+
+**Request:**
+
+ The request ``args`` must be a `RetryProgressTokenNowRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ Long-running operations accept an optional ``progressToken`` in their
+ request. While such a request keeps failing, wallet-core retries it
+ with exponential backoff and reports each failed attempt via a
+ ``request-progress-error`` notification, including the delay until the
+ next retry. This operation abandons the remaining delay, so that the
+ next attempt starts immediately. If the progress token is unknown or
+ the request has no retry logic, the operation has no effect.
+
+.. ts:def:: RetryProgressTokenNowRequest
+
+ interface RetryProgressTokenNowRequest {
+ // Name of the operation that the progress token belongs to.
+ operation: string;
+
+ // Client-chosen progress token passed to the original request.
+ progressToken: string;
+ }
+
+
+.. _wallet-op-cancelProgressToken:
+
+**cancelProgressToken**
+
+Cancel the running request associated with a progress token.
+
+**Request:**
+
+ The request ``args`` must be a `CancelProgressTokenRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The affected request is aborted; its response is an error with error
+ code ``WALLET_CORE_REQUEST_CANCELLED``. If the progress token is
+ unknown, the operation has no effect.
+
+.. ts:def:: CancelProgressTokenRequest
+
+ interface CancelProgressTokenRequest {
+ // Name of the operation that the progress token belongs to.
+ operation: string;
+
+ // Client-chosen progress token passed to the original request.
+ progressToken: string;
+ }
diff --git a/core/wallet-core/taldir.rst b/core/wallet-core/taldir.rst
@@ -0,0 +1,152 @@
+.. _wallet-op-registerAlias:
+
+**registerAlias**
+
+Initiate the registration of an alias with a taldir (directory)
+service. The alias (for example an e-mail address or a phone number)
+is associated with a target URI, typically the wallet's mailbox URI.
+Unless the registration is already paid for, the service sends a
+challenge to the alias; the registration is completed with
+:ref:`completeRegisterAlias <wallet-op-completeRegisterAlias>` once the
+user has received the challenge.
+
+**Request:**
+
+ The request arguments are a `TaldirRegistrationRequest` object.
+
+**Response:**
+
+ On success, the result is a `TaldirRegistrationResponse`: an empty
+ object if a challenge was sent to the alias, or a
+ `TaldirAlreadyPaidResponse` object if the registration already
+ exists and is still paid for.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_ALIAS_REGISTRATION_FAILED``.
+
+.. ts:def:: TaldirRegistrationRequest
+
+ interface TaldirRegistrationRequest {
+ // Alias to register, in alias-type-specific format.
+ alias: string;
+
+ // Type of the alias, e.g. "email" or "sms".
+ aliasType: string;
+
+ // Target URI to associate with the alias.
+ targetUri: string;
+
+ // Base URL of the taldir service.
+ taldirBaseUrl: string;
+
+ // For how long the registration should last or be extended.
+ duration: RelativeTime;
+ }
+
+.. ts:def:: TaldirRegistrationResponse
+
+ type TaldirRegistrationResponse =
+ | TaldirAlreadyPaidResponse
+ | EmptyObject;
+
+.. ts:def:: TaldirAlreadyPaidResponse
+
+ interface TaldirAlreadyPaidResponse {
+ // The remaining duration for which this registration is still
+ // paid for.
+ valid_for: RelativeTime;
+ }
+
+
+.. _wallet-op-completeRegisterAlias:
+
+**completeRegisterAlias**
+
+Complete an alias registration started with
+:ref:`registerAlias <wallet-op-registerAlias>` by answering the
+challenge that the taldir service sent to the alias.
+
+**Request:**
+
+ The request arguments are a `TaldirRegistrationCompletionRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_ALIAS_REGISTRATION_FAILED``, ``GENERIC_FORBIDDEN``.
+
+**Details:**
+
+ Wallet-core derives the answer sent to the service as the hash of the
+ ``challenge`` together with the ``targetUri`` from the registration
+ request, which protects the user against authorizing a concurrent
+ registration of a different target URI. ``GENERIC_FORBIDDEN``
+ indicates that the service rejected the answer to the challenge.
+
+.. ts:def:: TaldirRegistrationCompletionRequest
+
+ interface TaldirRegistrationCompletionRequest {
+ // Alias being registered.
+ alias: string;
+
+ // Type of the alias.
+ aliasType: string;
+
+ // Challenge received at the alias.
+ challenge: string;
+
+ // Base URL of the taldir service.
+ taldirBaseUrl: string;
+
+ // Target URI from the registration request.
+ targetUri: string;
+ }
+
+
+.. _wallet-op-lookupAlias:
+
+**lookupAlias**
+
+Look up the target URI registered for an alias at a taldir service.
+
+**Request:**
+
+ The request arguments are a `TaldirLookupRequest` object.
+
+**Response:**
+
+ On success, the result is a `TaldirLookupResponse` object. The
+ ``targetUri`` field is absent if the alias is not registered, or if
+ the service does not support the requested alias type.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_ALIAS_REGISTRATION_FAILED``.
+
+.. ts:def:: TaldirLookupRequest
+
+ interface TaldirLookupRequest {
+ // Alias to look up.
+ alias: string;
+
+ // Type of the alias.
+ aliasType: string;
+
+ // Base URL of the taldir service.
+ taldirBaseUrl: string;
+ }
+
+.. ts:def:: TaldirLookupResponse
+
+ interface TaldirLookupResponse {
+ // Target URI registered for the alias, if any.
+ targetUri?: string;
+ }
diff --git a/core/wallet-core/testing.rst b/core/wallet-core/testing.rst
@@ -0,0 +1,1060 @@
+.. _wallet-op-applyDevExperiment:
+
+**applyDevExperiment**
+
+Apply a developer experiment, specified as a ``taler://dev-experiment/``
+URI, to the current wallet state. This allows UI developers and testers
+to play around without an elaborate test environment. Dev mode must be
+active in the wallet.
+
+**Request:**
+
+ The request must be an `ApplyDevExperimentRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: ApplyDevExperimentRequest
+
+ interface ApplyDevExperimentRequest {
+ devExperimentUri: string;
+ }
+
+
+.. _wallet-op-testingGetSampleTransactions:
+
+**testingGetSampleTransactions**
+
+Get sample transactions for UI development. Currently always returns
+an empty transaction list.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `TransactionsResponse` object.
+
+
+.. _wallet-op-withdrawTestkudos:
+
+**withdrawTestkudos**
+
+Make a withdrawal of ``TESTKUDOS:10`` from the test deployment at
+test.taler.net (exchange ``https://exchange.test.taler.net/`` via the
+corebank API at ``https://bank.test.taler.net/``).
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `WithdrawTestBalanceResult` object.
+
+.. ts:def:: WithdrawTestBalanceResult
+
+ interface WithdrawTestBalanceResult {
+ // Transaction ID of the newly created withdrawal transaction.
+ transactionId: TransactionIdStr;
+
+ // Account of the user registered for the withdrawal.
+ accountPaytoUri: string;
+ }
+
+
+.. _wallet-op-withdrawTestBalance:
+
+**withdrawTestBalance**
+
+Make a withdrawal on a test deployment of the exchange and corebank.
+
+**Request:**
+
+ The request must be a `WithdrawTestBalanceRequest` object.
+
+**Response:**
+
+ On success, the result is a `WithdrawTestBalanceResult` object.
+
+**Details:**
+
+ The operation registers a random bank user via the corebank API,
+ creates a withdrawal operation for the requested amount, accepts the
+ resulting withdrawal URI as a bank-integrated withdrawal at the given
+ exchange, and finally confirms the withdrawal operation at the bank.
+
+.. ts:def:: WithdrawTestBalanceRequest
+
+ interface WithdrawTestBalanceRequest {
+ // Amount to withdraw.
+ amount: AmountString;
+
+ // Corebank API base URL.
+ corebankApiBaseUrl: string;
+
+ // Exchange to use for withdrawal.
+ exchangeBaseUrl: string;
+
+ // Force the usage of a particular denomination selection.
+ // Only useful for testing.
+ forcedDenomSel?: ForcedDenomSel;
+
+ // If set to true, treat the account created during
+ // the withdrawal as a foreign withdrawal account.
+ useForeignAccount?: boolean;
+ }
+
+.. ts:def:: ForcedDenomSel
+
+ interface ForcedDenomSel {
+ denoms: {
+ value: AmountString;
+ count: number;
+ }[];
+ }
+
+
+.. _wallet-op-runIntegrationTest:
+
+**runIntegrationTest**
+
+Run a simple integration test on a test deployment of the exchange and
+merchant: withdraw ``amountToWithdraw`` via the corebank API and then
+spend ``amountToSpend`` at the merchant.
+
+**Request:**
+
+ The request must be an `IntegrationTestArgs` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: IntegrationTestArgs
+
+ interface IntegrationTestArgs {
+ exchangeBaseUrl: string;
+ corebankApiBaseUrl: string;
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amountToWithdraw: AmountString;
+ amountToSpend: AmountString;
+ }
+
+
+.. _wallet-op-runIntegrationTestV2:
+
+**runIntegrationTestV2**
+
+Run an integration test on a test deployment of the exchange and
+merchant. The test withdraws a fixed amount in the exchange's currency
+and then exercises payments, a refund, peer-to-peer push and pull
+payments, and a deposit to a bank account.
+
+**Request:**
+
+ The request must be an `IntegrationTestV2Args` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: IntegrationTestV2Args
+
+ interface IntegrationTestV2Args {
+ exchangeBaseUrl: string;
+ corebankApiBaseUrl: string;
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ }
+
+
+.. _wallet-op-dumpCoins:
+
+**dumpCoins**
+
+Dump all coins of the wallet in a simple JSON format.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `CoinDumpJson` object.
+
+.. ts:def:: CoinDumpJson
+
+ interface CoinDumpJson {
+ coins: Array<{
+ // The coin's denomination's public key.
+ denomPub: DenominationPubKey;
+
+ // Hash of denom_pub.
+ denomPubHash: string;
+
+ // Value of the denomination (without any fees).
+ denomValue: string;
+
+ // Public key of the coin.
+ coinPub: string;
+
+ // Base URL of the exchange for the coin.
+ exchangeBaseUrl: string;
+
+ // Public key of the parent coin.
+ // Only present if this coin was obtained via refreshing.
+ refreshParentCoinPub: string | undefined;
+
+ // Public key of the reserve for this coin.
+ // Only present if this coin was obtained via withdrawal.
+ withdrawalReservePub: string | undefined;
+
+ // Status of the coin.
+ coinStatus: CoinStatus;
+
+ // Information about the age restriction.
+ ageCommitmentProof: AgeCommitmentProof | undefined;
+
+ history: WalletCoinHistoryItem[];
+ }>;
+ }
+
+.. ts:def:: CoinStatus
+
+ type CoinStatus =
+ // Withdrawn and never shown to anybody.
+ | "fresh"
+ // Coin was lost as the denomination is not usable anymore.
+ | "denom-loss"
+ // Fresh, but marked as suspended, thus won't be used for spending.
+ | "fresh-suspended"
+ // A coin that has been spent and refreshed.
+ | "dormant";
+
+.. ts:def:: WalletCoinHistoryItem
+
+ type WalletCoinHistoryItem =
+ | {
+ type: "withdraw";
+ transactionId: TransactionIdStr;
+ }
+ | {
+ type: "spend";
+ transactionId: TransactionIdStr;
+ amount: AmountString;
+ }
+ | {
+ type: "refresh";
+ transactionId: TransactionIdStr;
+ amount: AmountString;
+ }
+ | {
+ type: "recoup";
+ transactionId: TransactionIdStr;
+ amount: AmountString;
+ }
+ | {
+ type: "refund";
+ transactionId: TransactionIdStr;
+ amount: AmountString;
+ };
+
+
+.. _wallet-op-testCrypto:
+
+**testCrypto**
+
+Test the crypto worker by hashing a fixed test string.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an unspecified JSON value.
+
+
+.. _wallet-op-testPay:
+
+**testPay**
+
+Make a test payment using a test deployment of the exchange and
+merchant. Creates an order for ``amount`` with the given ``summary``
+at the merchant and pays it.
+
+**Request:**
+
+ The request must be a `TestPayArgs` object.
+
+**Response:**
+
+ On success, the result is a `TestPayResult` object.
+
+.. ts:def:: TestPayArgs
+
+ interface TestPayArgs {
+ merchantBaseUrl: string;
+ merchantAuthToken?: string;
+ amount: AmountString;
+ summary: string;
+ forcedCoinSel?: ForcedCoinSel;
+ }
+
+.. ts:def:: ForcedCoinSel
+
+ // Forced coin selection for deposits/payments.
+ interface ForcedCoinSel {
+ coins: {
+ value: AmountString;
+ contribution: AmountString;
+ }[];
+ }
+
+.. ts:def:: TestPayResult
+
+ interface TestPayResult {
+ // Number of coins used for the payment.
+ numCoins: number;
+ }
+
+
+.. _wallet-op-setCoinSuspended:
+
+**setCoinSuspended**
+
+Set a coin as (un-)suspended. Suspended coins won't be used for
+payments.
+
+**Request:**
+
+ The request must be a `SetCoinSuspendedRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: SetCoinSuspendedRequest
+
+ interface SetCoinSuspendedRequest {
+ coinPub: string;
+ suspended: boolean;
+ }
+
+
+.. _wallet-op-forceRefresh:
+
+**forceRefresh**
+
+Force a refresh on coins where it would not be necessary. Creates a
+manual refresh group for the given coins.
+
+**Request:**
+
+ The request must be a `ForceRefreshRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The request fails if ``refreshCoinSpecs`` is empty or names a coin
+ that is unknown to the wallet. Each coin is refreshed for its full
+ denomination value, unless a smaller ``amount`` is given.
+
+.. ts:def:: ForceRefreshRequest
+
+ interface ForceRefreshRequest {
+ refreshCoinSpecs: RefreshCoinSpec[];
+ }
+
+.. ts:def:: RefreshCoinSpec
+
+ interface RefreshCoinSpec {
+ coinPub: string;
+
+ // Amount to refresh; defaults to the coin's denomination value.
+ amount?: AmountString;
+ }
+
+
+.. _wallet-op-testingWaitTransactionsFinal:
+
+**testingWaitTransactionsFinal**
+
+Wait until all transactions are in a final state.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-testingWaitRefreshesFinal:
+
+**testingWaitRefreshesFinal**
+
+Wait until all refresh transactions are in a final state.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-testingWaitTransactionState:
+
+**testingWaitTransactionState**
+
+This operation is a legacy alias of
+:ref:`waitTransactionState <wallet-op-waitTransactionState>`. It waits
+until a transaction is in a particular state and has the same behavior
+and payloads; the legacy type names `TestingWaitTransactionRequest` and
+`TestingWaitTransactionStateResponse` are aliases of
+`WaitTransactionStateRequest` and `WaitTransactionStateResponse`,
+respectively.
+
+**Request:**
+
+ The request must be a `WaitTransactionStateRequest` object.
+
+**Response:**
+
+ On success, the result is a `WaitTransactionStateResponse` object.
+
+
+.. _wallet-op-testingWaitExchangeState:
+
+**testingWaitExchangeState**
+
+Wait until an exchange entry is in a particular state. Currently, only
+the wallet KYC status of the exchange entry can be waited on.
+
+**Request:**
+
+ The request must be a `TestingWaitExchangeStateRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingWaitExchangeStateRequest
+
+ interface TestingWaitExchangeStateRequest {
+ exchangeBaseUrl: string;
+ walletKycStatus?: ExchangeWalletKycStatus;
+ }
+
+.. ts:def:: ExchangeWalletKycStatus
+
+ type ExchangeWalletKycStatus =
+ | "done"
+ // Wallet needs to request KYC status.
+ | "legi-init"
+ // User requires KYC or AML.
+ | "legi";
+
+
+.. _wallet-op-testingWaitExchangeReady:
+
+**testingWaitExchangeReady**
+
+Wait until an exchange entry is ready. Returns an error if updating
+the exchange failed.
+
+**Request:**
+
+ The request must be a `TestingWaitExchangeReadyRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingWaitExchangeReadyRequest
+
+ interface TestingWaitExchangeReadyRequest {
+ exchangeBaseUrl: string;
+
+ // Do not stop waiting even when the exchange is
+ // in an error state.
+ noBail?: boolean;
+
+ // Force waiting until an update really happened.
+ forceUpdate?: boolean;
+
+ // Only consider the exchange as ready if the
+ // next auto-refresh is scheduled for the future.
+ waitAutoRefresh?: boolean;
+ }
+
+
+.. _wallet-op-testingWaitTasksDone:
+
+**testingWaitTasksDone**
+
+Wait until all pending tasks of the wallet are done.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-testingWaitBalance:
+
+**testingWaitBalance**
+
+Wait until a balance has reached the desired value. Waits until the
+material balance in the currency of ``amount`` equals ``amount``; only
+``type`` ``"material"`` is currently supported.
+
+**Request:**
+
+ The request must be a `TestingWaitBalanceRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingWaitBalanceRequest
+
+ interface TestingWaitBalanceRequest {
+ type: "material" | "available";
+ amount: AmountString;
+ }
+
+
+.. _wallet-op-testingGetDbStats:
+
+**testingGetDbStats**
+
+Get database statistics. The returned statistics are specific to the
+database backend in use.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an unspecified JSON value.
+
+
+.. _wallet-op-testingSetTimetravel:
+
+**testingSetTimetravel**
+
+Add an offset to the wallet's internal time.
+
+**Request:**
+
+ The request must be a `TestingSetTimetravelRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingSetTimetravelRequest
+
+ interface TestingSetTimetravelRequest {
+ // Offset added to the wallet's internal time, in milliseconds.
+ offsetMs: number;
+ }
+
+
+.. _wallet-op-testingGetDenomStats:
+
+**testingGetDenomStats**
+
+Get statistics about the denominations of an exchange known to the
+wallet.
+
+**Request:**
+
+ The request must be a `TestingGetDenomStatsRequest` object.
+
+**Response:**
+
+ On success, the result is a `TestingGetDenomStatsResponse` object.
+
+.. ts:def:: TestingGetDenomStatsRequest
+
+ interface TestingGetDenomStatsRequest {
+ exchangeBaseUrl: string;
+ }
+
+.. ts:def:: TestingGetDenomStatsResponse
+
+ interface TestingGetDenomStatsResponse {
+ // Denominations known to the wallet for the exchange.
+ numKnown: number;
+
+ // Denominations currently offered by the exchange.
+ numOffered: number;
+
+ // Denominations that are not usable anymore.
+ numLost: number;
+ }
+
+
+.. _wallet-op-testingRecoverCoins:
+
+**testingRecoverCoins**
+
+Follow the refresh/link recovery chain for coins at one exchange.
+
+**Request:**
+
+ The request must be a `TestingRecoverCoinsRequest` object.
+
+**Response:**
+
+ On success, the result is a `TestingRecoverCoinsResponse` object.
+
+.. ts:def:: TestingRecoverCoinsRequest
+
+ interface TestingRecoverCoinsRequest {
+ exchangeBaseUrl: string;
+
+ // Start with fresh coins by default.
+ // Unfinished recovery always resumes.
+ onlyFresh?: boolean;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: TestingRecoverCoinsResponse
+
+ interface TestingRecoverCoinsResponse {
+ exchangeBaseUrl: string;
+ progressToken: string;
+
+ // False when a coin or residual refresh remains unfinished.
+ complete: boolean;
+
+ // Histories processed during this invocation.
+ numChecked: number;
+
+ numDiscovered: number;
+
+ numQueued: number;
+
+ // Newly imported coins that are now spendable,
+ // including residual refreshes.
+ numRecovered: number;
+
+ // Cumulative for a resumed recovery; excludes existing coins,
+ // spent ancestors, fees and pending refreshes.
+ recoveredAmount: AmountString;
+
+ issues: CoinRecoveryIssue[];
+ }
+
+.. ts:def:: CoinRecoveryIssue
+
+ interface CoinRecoveryIssue {
+ coinPub: string;
+ refreshCommitment?: string;
+ reason:
+ | "missing-recovery-data"
+ | "missing-denomination"
+ | "invalid-history"
+ | "commitment-mismatch"
+ | "request-failed"
+ | "local-data-changed"
+ | "refresh-incomplete"
+ | "coin-unavailable";
+ description: string;
+ }
+
+
+.. _wallet-op-testingCheckCoins:
+
+**testingCheckCoins**
+
+Validate exchange coin histories and compare balances of unspent coins
+against the exchange's view.
+
+**Request:**
+
+ The request must be a `TestingCheckCoinsRequest` object.
+
+**Response:**
+
+ On success, the result is a `TestingCheckCoinsResponse` object.
+
+.. ts:def:: TestingCheckCoinsRequest
+
+ interface TestingCheckCoinsRequest {
+ // Canonicalized before selecting coins.
+ // Only this exchange is contacted.
+ exchangeBaseUrl: string;
+
+ // Default true: check only fresh coins. False validates every coin
+ // status, comparing denomination value only for fresh or
+ // suspended-fresh coins.
+ onlyFresh?: boolean;
+ }
+
+.. ts:def:: TestingCheckCoinsResponse
+
+ interface TestingCheckCoinsResponse {
+ exchangeBaseUrl: string;
+
+ // Denomination value of fresh coins under the exchange's current
+ // master key in the initial snapshot. Null if local data is
+ // unavailable.
+ expectedMaterialBalance: AmountString | null;
+
+ // Verified remaining exchange balance of those same coins. Null
+ // if any relevant history could not be verified or the material
+ // balance changed during the check; never a partial total.
+ actualMaterialBalance: AmountString | null;
+
+ // Coins selected by the exchange and onlyFresh filter
+ // in the initial snapshot.
+ numCoins: number;
+
+ // Validated histories with stable coin data,
+ // including balance mismatches.
+ numChecked: number;
+
+ // Balance differences, invalid exchange histories,
+ // and unavailable coin data.
+ issues: TestingCheckCoinsIssue[];
+ }
+
+.. ts:def:: TestingCheckCoinsIssue
+
+ interface TestingCheckCoinsIssue {
+ coinPub: string;
+ denomPubHash: string;
+ category: "mismatch" | "error" | "incomplete";
+ reason:
+ | "balance-difference"
+ | "invalid-history"
+ | "request-failed"
+ | "missing-local-data"
+ | "local-data-changed";
+ description: string;
+ expected?: Record<string, string | number | boolean>;
+ actual?: Record<string, string | number | boolean>;
+
+ // On balance differences: exchange history in offset order,
+ // including credits.
+ exchangeOperations?: Array<
+ [operation: CoinSpendHistoryItem["type"], amount: AmountString]
+ >;
+ }
+
+
+.. _wallet-op-testingPing:
+
+**testingPing**
+
+Do nothing. Can be used to check that wallet-core is alive and
+responding to requests.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-testingGetReserveHistory:
+
+**testingGetReserveHistory**
+
+Fetch the history of a reserve from the exchange. The reserve must be
+known to the wallet, as the request to the exchange is signed with the
+reserve's private key.
+
+**Request:**
+
+ The request must be a `TestingGetReserveHistoryRequest` object.
+
+**Response:**
+
+ On success, the result is an unspecified JSON value with the reserve
+ history as returned by the exchange.
+
+.. ts:def:: TestingGetReserveHistoryRequest
+
+ interface TestingGetReserveHistoryRequest {
+ reservePub: string;
+ exchangeBaseUrl: string;
+ }
+
+
+.. _wallet-op-testingResetAllRetries:
+
+**testingResetAllRetries**
+
+Reset all task/transaction retries, resulting in an immediate re-try of
+all pending operations.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is an empty object.
+
+
+.. _wallet-op-testingWaitWalletKyc:
+
+**testingWaitWalletKyc**
+
+Wait for the wallet KYC process at an exchange. With ``passed`` set to
+true, waits until KYC has been passed for at least the given ``amount``;
+otherwise already returns when legitimization is required.
+
+**Request:**
+
+ The request must be a `TestingWaitWalletKycRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingWaitWalletKycRequest
+
+ interface TestingWaitWalletKycRequest {
+ exchangeBaseUrl: string;
+ amount: AmountString;
+
+ // Do we wait for the KYC to be passed (true),
+ // or do we already return if legitimization is
+ // required (false).
+ passed: boolean;
+ }
+
+
+.. _wallet-op-testingPlanMigrateExchangeBaseUrl:
+
+**testingPlanMigrateExchangeBaseUrl**
+
+Enable migration from an old exchange base URL to a new exchange base
+URL. The actual migration is only applied once the exchange returns
+the new base URL.
+
+**Request:**
+
+ The request must be a `TestingPlanMigrateExchangeBaseUrlRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: TestingPlanMigrateExchangeBaseUrlRequest
+
+ interface TestingPlanMigrateExchangeBaseUrlRequest {
+ oldExchangeBaseUrl: string;
+ newExchangeBaseUrl: string;
+ }
+
+
+.. _wallet-op-testingRunFixup:
+
+**testingRunFixup**
+
+Run a named database fixup that repairs records in the wallet database.
+Fails if no fixup with the given ``id`` exists.
+
+**Request:**
+
+ The request must be a `RunFixupRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+.. ts:def:: RunFixupRequest
+
+ interface RunFixupRequest {
+ // Name of the fixup to run.
+ id: string;
+ }
+
+
+.. _wallet-op-testingGetFlightRecords:
+
+**testingGetFlightRecords**
+
+Get the wallet's flight records: records of exceptional events observed
+while communicating with an exchange (such as a coin reported as gone
+during a refresh melt, or a withdrawal requiring redenomination), kept
+for post-mortem debugging.
+
+**Request:**
+
+ This operation takes no arguments (an empty object).
+
+**Response:**
+
+ On success, the result is a `TestingGetFlightRecordsResponse` object.
+
+.. ts:def:: TestingGetFlightRecordsResponse
+
+ interface TestingGetFlightRecordsResponse {
+ flightRecords: FlightRecordEntry[];
+ }
+
+.. ts:def:: FlightRecordEntry
+
+ interface FlightRecordEntry {
+ timestamp: TalerPreciseTimestamp;
+ target: string;
+ event: FlightRecordEvent;
+ }
+
+.. ts:def:: FlightRecordEvent
+
+ type FlightRecordEvent = "melt-gone" | "withdrawal-redenominate";
+
+
+.. _wallet-op-testingGetPerformanceStats:
+
+**testingGetPerformanceStats**
+
+Get a list of performance stats for diagnostics. Requires
+observability events to be enabled; performance tables for the current
+running wallet instance are generated from observability events and
+stored in memory. Under each table, different types of duration for
+each operation (e.g. a ``getBalances`` wallet request) are included.
+
+**Request:**
+
+ The request must be a `GetPerformanceStatsRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetPerformanceStatsResponse` object.
+
+.. ts:def:: GetPerformanceStatsRequest
+
+ interface GetPerformanceStatsRequest {
+ // Limit to N largest average performance stats of each table.
+ // When undefined, all performance stats will be returned.
+ limit?: number;
+ }
+
+.. ts:def:: GetPerformanceStatsResponse
+
+ interface GetPerformanceStatsResponse {
+ stats: PerformanceTable;
+ }
+
+.. ts:def:: PerformanceTable
+
+ type PerformanceTable = {
+ [key in PerformanceStatType]?: PerformanceStat[];
+ };
+
+.. ts:def:: PerformanceStatType
+
+ type PerformanceStatType =
+ | "http-fetch"
+ | "db-query"
+ | "crypto"
+ | "wallet-request"
+ | "wallet-task";
+
+.. ts:def:: PerformanceStat
+
+ type PerformanceStat =
+ | {
+ type: "http-fetch";
+ url: string;
+ avgDurationMs: number;
+ maxDurationMs: number;
+ minDurationMs: number;
+ totalDurationMs: number;
+ count: number;
+ }
+ | {
+ type: "db-query";
+ name: string;
+ location: string;
+ avgDurationMs: number;
+ maxDurationMs: number;
+ minDurationMs: number;
+ totalDurationMs: number;
+ count: number;
+ }
+ | {
+ type: "crypto";
+ operation: string;
+ avgDurationMs: number;
+ maxDurationMs: number;
+ minDurationMs: number;
+ totalDurationMs: number;
+ count: number;
+ }
+ | {
+ type: "wallet-request";
+ operation: string;
+ avgDurationMs: number;
+ maxDurationMs: number;
+ minDurationMs: number;
+ totalDurationMs: number;
+ count: number;
+ }
+ | {
+ type: "wallet-task";
+ taskId: string;
+ avgDurationMs: number;
+ maxDurationMs: number;
+ minDurationMs: number;
+ totalDurationMs: number;
+ count: number;
+ };
+
+
+.. _wallet-op-testingCorruptWithdrawalCoinSel:
+
+**testingCorruptWithdrawalCoinSel**
+
+Corrupt the denomination selection of a withdrawal transaction by
+replacing the first selected denomination's public key hash with a
+random value, in order to test the wallet's error handling.
+
+**Request:**
+
+ The request must be a `TestingCorruptWithdrawalCoinSelRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Details:**
+
+ The ``transactionId`` must identify a withdrawal transaction.
+ Withdrawal groups without a denomination selection are left
+ unchanged.
+
+.. ts:def:: TestingCorruptWithdrawalCoinSelRequest
+
+ interface TestingCorruptWithdrawalCoinSelRequest {
+ transactionId: TransactionIdStr;
+ }
diff --git a/core/wallet-core/tokens.rst b/core/wallet-core/tokens.rst
@@ -0,0 +1,185 @@
+.. _wallet-op-listDiscounts:
+
+**listDiscounts**
+
+List discount tokens stored in the wallet. Listed tokens are grouped
+by token family. Only tokens that are within their validity period
+and not currently in use by a transaction are listed.
+
+**Request:**
+
+ The request arguments must be a `ListDiscountsRequest` object.
+ Omitted filter fields do not restrict the result.
+
+**Response:**
+
+ On success, the result is a `ListDiscountsResponse` object.
+
+.. ts:def:: ListDiscountsRequest
+
+ interface ListDiscountsRequest {
+ // Filter by hash of token issue public key.
+ tokenIssuePubHash?: string;
+
+ // Filter by merchant base URL.
+ merchantBaseUrl?: string;
+ }
+
+.. ts:def:: ListDiscountsResponse
+
+ interface ListDiscountsResponse {
+ discounts: DiscountListDetail[];
+ }
+
+.. ts:def:: DiscountListDetail
+
+ interface DiscountListDetail {
+ // Hash of token family info.
+ tokenFamilyHash: string;
+
+ // Hash of token issue public key.
+ tokenIssuePubHash: string;
+
+ // URL of the merchant issuing the token.
+ merchantBaseUrl: string;
+
+ // Information about the merchant issuing the token.
+ merchantInfo?: MerchantInfo;
+
+ // Human-readable name for the token family.
+ name: string;
+
+ // Human-readable description for the token family.
+ description: string;
+
+ // Optional map from IETF BCP 47 language tags to localized descriptions.
+ descriptionI18n: any | undefined;
+
+ // Start time of the token's validity period.
+ validityStart: Timestamp;
+
+ // End time of the token's validity period.
+ validityEnd: Timestamp;
+
+ // Number of tokens available to use.
+ tokensAvailable: number;
+ }
+
+.. ts:def:: MerchantInfo
+
+ interface MerchantInfo {
+ // The merchant's legal name of business.
+ name: string;
+
+ // Contact email address of the merchant.
+ email?: string;
+
+ // Website of the merchant.
+ website?: string;
+
+ // An optional base64-encoded product image.
+ logo?: ImageDataUrl;
+
+ // Business address of the merchant.
+ address?: Location;
+
+ // Jurisdiction for disputes; some typical location fields may be absent.
+ jurisdiction?: Location;
+ }
+
+
+.. _wallet-op-deleteDiscount:
+
+**deleteDiscount**
+
+Delete all discount tokens of one token family from the wallet.
+
+**Request:**
+
+ The request arguments must be a `DeleteDiscountRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TOKENS_IN_USE``.
+
+**Details:**
+
+ The deletion fails with ``WALLET_TOKENS_IN_USE`` if any token of the
+ family is currently in use by a transaction.
+
+.. ts:def:: DeleteDiscountRequest
+
+ interface DeleteDiscountRequest {
+ // Hash of token family info.
+ tokenFamilyHash: string;
+ }
+
+
+.. _wallet-op-listSubscriptions:
+
+**listSubscriptions**
+
+List subscription tokens stored in the wallet. Listed tokens are
+grouped by token family. Only tokens that are within their validity
+period and not currently in use by a transaction are listed.
+
+**Request:**
+
+ The request arguments must be a `ListSubscriptionsRequest` object,
+ which has the same fields as `ListDiscountsRequest`.
+
+**Response:**
+
+ On success, the result is a `ListSubscriptionsResponse` object.
+
+.. ts:def:: ListSubscriptionsRequest
+
+ type ListSubscriptionsRequest = ListDiscountsRequest;
+
+.. ts:def:: ListSubscriptionsResponse
+
+ interface ListSubscriptionsResponse {
+ subscriptions: SubscriptionListDetail[];
+ }
+
+.. ts:def:: SubscriptionListDetail
+
+ // Same as DiscountListDetail, but without a token count.
+ type SubscriptionListDetail = Omit<DiscountListDetail, "tokensAvailable">;
+
+
+.. _wallet-op-deleteSubscription:
+
+**deleteSubscription**
+
+Delete all subscription tokens of one token family from the wallet.
+
+**Request:**
+
+ The request arguments must be a `DeleteSubscriptionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TOKENS_IN_USE``.
+
+**Details:**
+
+ The deletion fails with ``WALLET_TOKENS_IN_USE`` if any token of the
+ family is currently in use by a transaction.
+
+.. ts:def:: DeleteSubscriptionRequest
+
+ interface DeleteSubscriptionRequest {
+ // Hash of token family info.
+ tokenFamilyHash: string;
+ }
diff --git a/core/wallet-core/transactions.rst b/core/wallet-core/transactions.rst
@@ -0,0 +1,1380 @@
+.. _wallet-op-getTransactions:
+
+**getTransactions**
+
+Get the wallet's transaction list, containing past and pending
+transactions.
+
+**Request:**
+
+ The request ``args`` must be a `TransactionsRequest` object.
+
+**Response:**
+
+ On success, the result is a `TransactionsResponse` object.
+
+**Details:**
+
+ Refresh transactions are excluded from the result unless
+ ``includeRefreshes`` is set.
+
+ With the default sort orders (``ascending`` and ``descending``),
+ pending transactions (major states ``pending``, ``aborting`` and
+ ``dialog``) are listed before all other transactions; within each
+ group, transactions are sorted by their timestamp, and ties are
+ broken by a fixed transaction-type order. With
+ ``stable-ascending``, all transactions are sorted purely by
+ ascending timestamp, so pending transactions do not jump around.
+
+ Filtering by an auditor scope (``scopeInfo`` of type ``auditor``)
+ is not supported and fails with ``WALLET_CORE_API_BAD_REQUEST``.
+
+.. ts:def:: TransactionsRequest
+
+ interface TransactionsRequest {
+ // Return only transactions in the given currency.
+ // Deprecated: use ``scopeInfo`` instead.
+ currency?: string;
+
+ // Return only transactions in the given scope.
+ scopeInfo?: ScopeInfo;
+
+ // Limit results to transactions related to the given search
+ // string. Currently accepted but not applied by wallet-core.
+ search?: string;
+
+ // Sort order of the transaction items. "ascending" (the
+ // default) and "descending" sort by timestamp but list pending
+ // transactions first; "stable-ascending" sorts purely by
+ // ascending timestamp, with pending transactions in between.
+ sort?: "ascending" | "descending" | "stable-ascending";
+
+ // If true, include all refreshes in the transaction list.
+ includeRefreshes?: boolean;
+
+ // If set, only return transactions matching the state filter.
+ filterByState?: TransactionStateFilter;
+ }
+
+
+.. ts:def:: TransactionStateFilter
+
+ // State filter for the transaction list: only transactions in a
+ // non-final state.
+ type TransactionStateFilter = "nonfinal";
+
+
+.. ts:def:: TransactionsResponse
+
+ interface TransactionsResponse {
+ // List of past and pending transactions matching the request;
+ // see the respective operation for the sort order.
+ transactions: Transaction[];
+ }
+
+
+.. ts:def:: Transaction
+
+ // A transaction in the wallet's transaction history. The
+ // ``type`` field discriminates the union; all members share the
+ // fields of `TransactionCommon`.
+ type Transaction =
+ | TransactionWithdrawal
+ | TransactionPayment
+ | TransactionRefund
+ | TransactionRefresh
+ | TransactionDeposit
+ | TransactionPeerPullCredit
+ | TransactionPeerPullDebit
+ | TransactionPeerPushCredit
+ | TransactionPeerPushDebit
+ | TransactionInternalWithdrawal
+ | TransactionRecoup
+ | TransactionDenomLoss;
+
+
+.. ts:def:: TransactionIdStr
+
+ // Opaque, stable identifier of a transaction, of the form
+ // ``txn:<type>:<id>`` where ``<type>`` is a `TransactionType`.
+ // (The TypeScript source additionally brands this string type;
+ // the brand only exists at compile time.)
+ type TransactionIdStr = `txn:${string}:${string}`;
+
+
+.. ts:def:: TransactionType
+
+ type TransactionType =
+ | "withdrawal"
+ | "internal-withdrawal"
+ | "payment"
+ | "refund"
+ | "refresh"
+ | "deposit"
+ | "peer-push-debit"
+ | "peer-push-credit"
+ | "peer-pull-debit"
+ | "peer-pull-credit"
+ | "recoup"
+ | "denom-loss";
+
+
+.. ts:def:: TransactionCommon
+
+ interface TransactionCommon {
+ // Opaque unique ID for the transaction, used as a starting
+ // point for paginating queries and for invoking actions on the
+ // transaction (e.g. deleting it from the history).
+ transactionId: TransactionIdStr;
+
+ // The transaction produced funds under an exchange key set that
+ // the user subsequently purged from the wallet.
+ legacy?: boolean;
+
+ // Short identifier assigned by this wallet for local,
+ // human-facing use, of the form ``#<type>:<localIdent>``.
+ // Intentionally not portable: importing or merging a wallet can
+ // assign different local identifiers. Clients must use
+ // ``transactionId`` when they need a stable ID. Undefined when
+ // the wallet backend does not support local IDs.
+ localTransactionId?: string;
+
+ // Type of the transaction; discriminates the `Transaction`
+ // union.
+ type: TransactionType;
+
+ // Main timestamp of the transaction.
+ timestamp: TalerPreciseTimestamp;
+
+ // Scopes of this transaction.
+ scopes: ScopeInfo[];
+
+ // Transaction state, as per DD37.
+ txState: TransactionState;
+
+ // Wallet-internal state ID, only used for debugging and
+ // testing.
+ stId: number;
+
+ // Possible transitions based on the current state.
+ txActions: TransactionAction[];
+
+ // Raw amount of the transaction (exclusive of fees or other
+ // extra costs).
+ amountRaw: AmountString;
+
+ // Amount shown when the transaction was confirmed, including
+ // estimated fees. Preserved when execution fails, expires or
+ // is aborted.
+ amountEffective: AmountString;
+
+ // Settled wallet balance effect, including fees and abort
+ // recovery. Nonnegative; the transaction type determines
+ // whether this is a debit or a credit. Absent until the
+ // transaction and its recovery have settled, or when the amount
+ // cannot be established for historical records. Ordinary
+ // merchant refunds remain separate credits. Associated
+ // refreshes have zero effect.
+ amountEffectiveFinal?: AmountString;
+
+ error?: TalerErrorDetail;
+
+ abortReason?: TalerErrorDetail;
+
+ failReason?: TalerErrorDetail;
+
+ // Location where the user must go to complete KYC; present
+ // when the transaction's minor state is ``kyc``.
+ kycUrl?: string;
+
+ // KYC payto hash. Useful for testing, not so useful for UIs.
+ kycPaytoHash?: string;
+
+ // KYC access token. Useful for testing, not so useful for UIs.
+ kycAccessToken?: string;
+
+ kycAuthTransferInfo?: KycAuthTransferInfo;
+ }
+
+
+.. ts:def:: TransactionState
+
+ interface TransactionState {
+ // Major state component of the transaction state.
+ major: TransactionMajorState;
+
+ // Minor state component of the transaction.
+ minor?: TransactionMinorState;
+
+ // Whether the wallet is currently actively processing the
+ // transaction or waiting for a counterparty. Will eventually
+ // be folded into a new major state.
+ working?: boolean;
+ }
+
+
+.. ts:def:: TransactionMajorState
+
+ type TransactionMajorState =
+ // No state, only used when reporting transitions into the
+ // initial state.
+ | "none"
+ | "pending"
+ | "done"
+ | "aborting"
+ | "aborted"
+ | "dialog"
+ | "finalizing"
+ // A suspended pending state.
+ | "suspended"
+ | "suspended-finalizing"
+ | "suspended-aborting"
+ | "failed"
+ | "expired"
+ // Only used for notifications, never in the transaction
+ // history.
+ | "deleted";
+
+
+.. ts:def:: TransactionMinorState
+
+ type TransactionMinorState =
+ | "aborting-bank"
+ | "accept-refund"
+ | "auto-refund"
+ | "balance-kyc"
+ | "bank"
+ | "bank-confirm-transfer"
+ | "bank-register-reserve"
+ | "check-refund"
+ | "claim-proposal"
+ | "completed-by-other-wallet"
+ | "continued-with-other-wallet"
+ | "create-purse"
+ | "delete-purse"
+ | "deposit"
+ | "deposit-abort-partial"
+ | "deposit-abort-recovered"
+ | "deposit-abort-recovery-failed"
+ | "deposit-abort-refund-failed"
+ | "deposit-abort-too-late"
+ | "exchange"
+ | "exchange-wait-reserve"
+ | "kyc-auth"
+ | "kyc-hard-limit"
+ | "kyc-init"
+ | "kyc"
+ | "merge"
+ | "paid-by-other"
+ | "proposed"
+ | "ready"
+ | "rebind-session"
+ | "refresh"
+ | "refused"
+ | "repurchase"
+ | "submit-payment"
+ | "track"
+ | "unknown"
+ | "withdraw"
+ | "waiting-for-other-wallet"
+ | "abort";
+
+
+.. ts:def:: TransactionAction
+
+ // Actions the user can request on a transaction in its current
+ // state; each action corresponds to one of the transaction
+ // operations.
+ type TransactionAction =
+ | "delete"
+ | "suspend"
+ | "resume"
+ | "abort"
+ | "fail"
+ | "retry";
+
+
+.. ts:def:: KycAuthTransferInfo
+
+ interface KycAuthTransferInfo {
+ // Payto URI of the account that must make the transfer. The
+ // KYC auth transfer will *not* work if it originates from a
+ // different account.
+ debitPaytoUri: string;
+
+ // Account public key. Included in the transfer subject for
+ // some of the transfer options.
+ accountPub: string;
+
+ // Options for making the KYC auth transfer, grouped by exchange
+ // credit account in the same format used for withdrawals.
+ transferOptionsExt: WithdrawalExchangeAccountDetails[];
+
+ // Options for making the KYC auth transfer payment to the
+ // exchange. Deprecated: use ``transferOptionsExt`` instead.
+ transferOptions: TransferOption[];
+
+ // Validity of the transferOptions, or undefined if they do not
+ // expire. Deprecated: use the per-account expiry in
+ // ``transferOptionsExt`` instead.
+ transferExpiry: TalerProtocolTimestamp | undefined;
+
+ // Amount that the exchange expects to be deposited. Usually
+ // the smallest amount that can be transferred via a bank
+ // transfer. Deprecated: use ``transferOptions`` instead.
+ amount: AmountString;
+
+ // Possible target payto URIs. Deprecated: use
+ // ``transferOptions`` instead.
+ creditPaytoUris: string[];
+ }
+
+
+.. ts:def:: TransactionWithdrawal
+
+ // A withdrawal transaction (either bank-integrated or manual).
+ interface TransactionWithdrawal extends TransactionCommon {
+ type: "withdrawal";
+
+ // Exchange of the withdrawal.
+ exchangeBaseUrl: string | undefined;
+
+ // Amount that got subtracted from the reserve balance.
+ amountRaw: AmountString;
+
+ // Amount that actually was (or will be) added to the wallet's
+ // balance.
+ amountEffective: AmountString;
+
+ withdrawalDetails: WithdrawalDetails;
+ }
+
+
+.. ts:def:: TransactionInternalWithdrawal
+
+ // Internal withdrawal operation, only reported on request. Some
+ // transactions (peer-*-credit) internally do a withdrawal, but
+ // only the peer-*-credit transaction is reported. The internal
+ // withdrawal transaction gives access to the details of the
+ // underlying withdrawal for testing/debugging. It is usually not
+ // reported, so that the amounts of transactions properly add up.
+ interface TransactionInternalWithdrawal extends TransactionCommon {
+ type: "internal-withdrawal";
+
+ // Exchange of the withdrawal.
+ exchangeBaseUrl: string;
+
+ // Amount that got subtracted from the reserve balance.
+ amountRaw: AmountString;
+
+ // Amount that actually was (or will be) added to the wallet's
+ // balance.
+ amountEffective: AmountString;
+
+ withdrawalDetails: WithdrawalDetails;
+ }
+
+
+.. ts:def:: WithdrawalDetails
+
+ type WithdrawalDetails =
+ | WithdrawalDetailsForManualTransfer
+ | WithdrawalDetailsForTalerBankIntegrationApi;
+
+
+.. ts:def:: WithdrawalType
+
+ type WithdrawalType =
+ | "taler-bank-integration-api"
+ | "manual-transfer";
+
+
+.. ts:def:: WithdrawalDetailsForManualTransfer
+
+ interface WithdrawalDetailsForManualTransfer {
+ type: "manual-transfer";
+
+ // Payto URIs that the exchange supports. Already contains the
+ // amount and message. Deprecated: in favor of
+ // ``exchangeCreditAccountDetails``.
+ exchangePaytoUris: string[];
+
+ exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[];
+
+ // Public key of the reserve.
+ reservePub: string;
+
+ // Is the reserve ready for withdrawal?
+ reserveIsReady: boolean;
+
+ // How long the exchange waits to transfer back funds from a
+ // reserve.
+ reserveClosingDelay: TalerProtocolDuration;
+ }
+
+
+.. ts:def:: WithdrawalDetailsForTalerBankIntegrationApi
+
+ interface WithdrawalDetailsForTalerBankIntegrationApi {
+ type: "taler-bank-integration-api";
+
+ // True if the bank has confirmed the withdrawal. An
+ // unconfirmed withdrawal usually requires user input and should
+ // be highlighted in the UI; see ``bankConfirmationUrl``.
+ confirmed: boolean;
+
+ // If the withdrawal is unconfirmed, this can include a URL for
+ // user-initiated confirmation.
+ bankConfirmationUrl?: string;
+
+ // Public key of the reserve.
+ reservePub: string;
+
+ // Is the reserve ready for withdrawal?
+ reserveIsReady: boolean;
+
+ // Is the bank transfer for the withdrawal externally
+ // confirmed?
+ externalConfirmation?: boolean;
+
+ exchangeCreditAccountDetails?: WithdrawalExchangeAccountDetails[];
+ }
+
+
+.. ts:def:: TransactionPayment
+
+ interface TransactionPayment extends TransactionCommon {
+ type: "payment";
+
+ // Merchant instance base URL used to claim the order.
+ // Available even before the contract terms have been
+ // downloaded.
+ merchantBaseUrl: string;
+
+ // Public payment URI shown while this wallet waits for another
+ // wallet to claim an order that it released.
+ unclaimedPayUri?: TalerUriString;
+
+ // Additional information about the payment. Only present if
+ // the information about the order is already available.
+ info: OrderShortInfo | undefined;
+
+ // Full contract terms. Only included if explicitly requested
+ // via the ``includeContractTerms`` flag of
+ // ``getTransactionById``.
+ contractTerms?: MerchantContractTerms;
+
+ // Amount that must be paid for the contract.
+ amountRaw: AmountString;
+
+ // Amount that was paid, including deposit, wire and refresh
+ // fees.
+ amountEffective: AmountString;
+
+ // Amount that has been refunded by the merchant.
+ totalRefundRaw: AmountString;
+
+ // Amount that will be added to the wallet's balance after fees
+ // and refreshing.
+ totalRefundEffective: AmountString;
+
+ // Amount pending to be picked up.
+ refundPending: AmountString | undefined;
+
+ // Reference to applied refunds.
+ refunds: RefundInfoShort[];
+
+ // Is the wallet currently checking for a refund?
+ refundQueryActive: boolean;
+
+ // PoS confirmation codes, separated by newlines. Only present
+ // for purchases that support PoS confirmation.
+ posConfirmation: string | undefined;
+
+ // Until when the ``posConfirmation`` is valid.
+ posConfirmationDeadline?: TalerProtocolTimestamp;
+
+ // Did we receive the payment via a taler://pay-template/ URI
+ // and did the URI contain a nfc=1 flag?
+ posConfirmationViaNfc?: boolean;
+
+ // In case this payment transaction was detected as a
+ // repurchase, the transaction ID of the original payment.
+ repurchaseTransactionId?: TransactionIdStr;
+
+ // If applicable, the choice that the user selected.
+ choiceIndex?: number;
+ }
+
+
+.. ts:def:: OrderShortInfo
+
+ interface OrderShortInfo {
+ // Order ID, uniquely identifies the order within a merchant
+ // instance.
+ orderId: string;
+
+ // Hash of the contract terms.
+ contractTermsHash: string;
+
+ // More information about the merchant.
+ merchant: MerchantInfo;
+
+ // Summary of the order, given by the merchant.
+ summary: string;
+
+ // Map from IETF BCP 47 language tags to localized summaries.
+ summary_i18n?: InternationalizedString;
+
+ // URL of the fulfillment, given by the merchant.
+ fulfillmentUrl?: string;
+
+ // Plain text message that should be shown to the user when the
+ // payment is complete.
+ fulfillmentMessage?: string;
+
+ // Translations of ``fulfillmentMessage``.
+ fulfillmentMessage_i18n?: InternationalizedString;
+ }
+
+
+.. ts:def:: RefundInfoShort
+
+ interface RefundInfoShort {
+ transactionId: string;
+
+ timestamp: TalerProtocolTimestamp;
+
+ amountEffective: AmountString;
+
+ amountRaw: AmountString;
+ }
+
+
+.. ts:def:: TransactionRefund
+
+ interface TransactionRefund extends TransactionCommon {
+ // Recovery already included in the unsuccessful payment's
+ // final cost.
+ isAbortRecovery?: boolean;
+
+ type: "refund";
+
+ // Amount that has been refunded by the merchant.
+ amountRaw: AmountString;
+
+ // Amount that will be added to the wallet's balance after fees
+ // and refreshing.
+ amountEffective: AmountString;
+
+ // ID of the transaction that is refunded.
+ refundedTransactionId: string;
+
+ paymentInfo: RefundPaymentInfo | undefined;
+ }
+
+
+.. ts:def:: RefundPaymentInfo
+
+ // Summary information about the payment that we got a refund
+ // for.
+ interface RefundPaymentInfo {
+ summary: string;
+
+ summary_i18n?: InternationalizedString;
+
+ // More information about the merchant.
+ merchant: MerchantInfo;
+ }
+
+
+.. ts:def:: TransactionRefresh
+
+ // A transaction shown for refreshes. Only shown for (1)
+ // refreshes not associated with other transactions and (2)
+ // refreshes in an error state.
+ interface TransactionRefresh extends TransactionCommon {
+ type: "refresh";
+
+ refreshReason: RefreshReason;
+
+ // Transaction ID that caused this refresh.
+ originatingTransactionId?: string;
+
+ // Always zero for refreshes.
+ amountRaw: AmountString;
+
+ // Fees, i.e. the effective, negative effect of the refresh on
+ // the balance. Only applicable for stand-alone refreshes, and
+ // zero for other refreshes where the transaction itself
+ // accounts for the refresh fee.
+ amountEffective: AmountString;
+
+ refreshInputAmount: AmountString;
+
+ refreshOutputAmount: AmountString;
+ }
+
+
+.. ts:def:: RefreshReason
+
+ // Reason why a coin is being refreshed.
+ type RefreshReason =
+ | "manual"
+ | "pay-merchant"
+ | "pay-deposit"
+ | "pay-peer-push"
+ | "pay-peer-pull"
+ | "refund"
+ | "abort-pay"
+ | "abort-deposit"
+ | "abort-peer-push-debit"
+ | "abort-peer-pull-debit"
+ | "recoup"
+ | "backup-restored"
+ | "scheduled";
+
+
+.. ts:def:: TransactionDeposit
+
+ // Deposit transaction, which effectively sends money from this
+ // wallet somewhere else.
+ interface TransactionDeposit extends TransactionCommon {
+ type: "deposit";
+
+ depositGroupId: string;
+
+ // Target for the deposit.
+ targetPaytoUri: string;
+
+ // Raw amount that is being deposited.
+ amountRaw: AmountString;
+
+ // Deposit account public key.
+ accountPub: string;
+
+ // Effective amount that is being deposited.
+ amountEffective: AmountString;
+
+ wireTransferDeadline: TalerProtocolTimestamp;
+
+ wireTransferProgress: number;
+
+ // Did all the deposit requests succeed?
+ deposited: boolean;
+
+ trackingState: Array<DepositTransactionTrackingState>;
+ }
+
+
+.. ts:def:: DepositTransactionTrackingState
+
+ interface DepositTransactionTrackingState {
+ // Raw wire transfer identifier of the deposit.
+ wireTransferId: string;
+
+ // When the wire transfer was given to the bank.
+ timestampExecuted: TalerProtocolTimestamp;
+
+ // Total amount transferred for this wtid (including fees).
+ amountRaw: AmountString;
+
+ // Wire fee amount for this exchange.
+ wireFee: AmountString;
+ }
+
+
+.. ts:def:: TransactionPeerPullCredit
+
+ // Credit because we were paid for a P2P invoice we created.
+ interface TransactionPeerPullCredit extends TransactionCommon {
+ type: "peer-pull-credit";
+
+ info: PeerInfoShort;
+
+ // Exchange used.
+ exchangeBaseUrl: string;
+
+ // Amount that got subtracted from the reserve balance.
+ amountRaw: AmountString;
+
+ // Amount that actually was (or will be) added to the wallet's
+ // balance.
+ amountEffective: AmountString;
+
+ // URI to send to the other party. Only available in the right
+ // state.
+ talerUri: string | undefined;
+ }
+
+
+.. ts:def:: TransactionPeerPullDebit
+
+ // Debit because we paid someone's invoice.
+ interface TransactionPeerPullDebit extends TransactionCommon {
+ type: "peer-pull-debit";
+
+ info: PeerInfoShort;
+
+ // Exchange used.
+ exchangeBaseUrl: string;
+
+ amountRaw: AmountString;
+
+ amountEffective: AmountString;
+ }
+
+
+.. ts:def:: TransactionPeerPushDebit
+
+ // We sent money via a P2P payment.
+ interface TransactionPeerPushDebit extends TransactionCommon {
+ type: "peer-push-debit";
+
+ info: PeerInfoShort;
+
+ // Exchange used.
+ exchangeBaseUrl: string;
+
+ // Amount that got subtracted from the reserve balance.
+ amountRaw: AmountString;
+
+ // Amount that actually was (or will be) added to the wallet's
+ // balance.
+ amountEffective: AmountString;
+
+ // URI to accept the payment. Only present if the transaction
+ // is in a state where the other party can accept the payment.
+ talerUri?: string;
+ }
+
+
+.. ts:def:: TransactionPeerPushCredit
+
+ // We received money via a P2P payment.
+ interface TransactionPeerPushCredit extends TransactionCommon {
+ type: "peer-push-credit";
+
+ info: PeerInfoShort;
+
+ // Exchange used.
+ exchangeBaseUrl: string;
+
+ // Amount that got subtracted from the reserve balance.
+ amountRaw: AmountString;
+
+ // Amount that actually was (or will be) added to the wallet's
+ // balance.
+ amountEffective: AmountString;
+ }
+
+
+.. ts:def:: PeerInfoShort
+
+ interface PeerInfoShort {
+ expiration: TalerProtocolTimestamp | undefined;
+
+ summary: string | undefined;
+
+ iconId: string | undefined;
+ }
+
+
+.. ts:def:: TransactionRecoup
+
+ // The exchange revoked a key and the wallet recoups funds.
+ interface TransactionRecoup extends TransactionCommon {
+ type: "recoup";
+ }
+
+
+.. ts:def:: TransactionDenomLoss
+
+ // A transaction to indicate financial loss due to denominations
+ // that became unusable for deposits.
+ interface TransactionDenomLoss extends TransactionCommon {
+ type: "denom-loss";
+
+ lossEventType: DenomLossEventType;
+
+ exchangeBaseUrl: string;
+ }
+
+
+.. ts:def:: DenomLossEventType
+
+ type DenomLossEventType =
+ | "denom-expired"
+ | "denom-vanished"
+ | "denom-unoffered"
+ // The exchange revoked the denomination. A revoked
+ // denomination also stops being offered, so this must be
+ // checked before "denom-unoffered" to say what actually
+ // happened.
+ | "denom-revoked";
+
+
+.. _wallet-op-getTransactionsV2:
+
+**getTransactionsV2**
+
+Get the wallet's transaction list, with support for paginated
+queries.
+
+**Request:**
+
+ The request ``args`` must be a `GetTransactionsV2Request` object.
+
+**Response:**
+
+ On success, the result is a `TransactionsResponse` object.
+
+**Details:**
+
+ Without a ``limit`` and without an offset, all matching
+ transactions are returned in ascending timestamp order.
+
+ With a positive ``limit``, at most ``limit`` transactions are
+ returned in ascending order: without an offset starting at the
+ first transaction, with an offset starting after the offset. With
+ a negative ``limit``, at most ``-limit`` transactions are returned
+ in descending order: without an offset starting at the last
+ transaction, with an offset starting before the offset.
+ Transactions with equal timestamps are ordered by their
+ ``transactionId``.
+
+ If the ``offsetTransactionId`` no longer exists (for example
+ because the transaction was deleted), ``offsetTimestamp`` is used
+ as a fallback anchor. If the offset transaction does not exist
+ and no ``offsetTimestamp`` is given, the request fails with
+ ``WALLET_TRANSACTION_NOT_FOUND``.
+
+ Refresh transactions are excluded unless ``includeRefreshes`` (or
+ ``includeAll``) is set; payments that were superseded by a
+ repurchase are excluded unless ``includeAll`` is set.
+
+.. ts:def:: GetTransactionsV2Request
+
+ interface GetTransactionsV2Request {
+ // Return only transactions in the given currency.
+ currency?: string;
+
+ // Return only transactions in the given scope.
+ scopeInfo?: ScopeInfo;
+
+ // If true, include all refreshes in the transaction list.
+ includeRefreshes?: boolean;
+
+ // If true, include transactions that would usually be filtered
+ // out. Implies ``includeRefreshes``.
+ includeAll?: boolean;
+
+ // Only return transactions before/after this offset.
+ offsetTransactionId?: TransactionIdStr;
+
+ // Only return transactions before/after the transaction with
+ // this timestamp. Used as a fallback if the
+ // ``offsetTransactionId`` was deleted.
+ offsetTimestamp?: TalerPreciseTimestamp;
+
+ // Number of transactions to return. When positive, results are
+ // returned in ascending timestamp order (starting at the first
+ // transaction or after the offset). When negative, results
+ // are returned in descending timestamp order (starting at the
+ // last transaction or before the offset).
+ limit?: number;
+
+ // Filter transactions by their state / state category.
+ // If not specified, all transactions are returned.
+ // "final": transactions in any final state;
+ // "nonfinal": transactions in any state but the final states;
+ // "nonfinal-dialog": nonfinal transactions that require
+ // confirmation / some choice by the user;
+ // "nonfinal-approved": nonfinal transactions that need no
+ // further user approval;
+ // "done": transactions in the "done" major state.
+ filterByState?:
+ | "final"
+ | "nonfinal"
+ | "done"
+ | "nonfinal-approved"
+ | "nonfinal-dialog";
+ }
+
+
+.. _wallet-op-getTransactionById:
+
+**getTransactionById**
+
+Get a single transaction by its identifier.
+
+**Request:**
+
+ The request ``args`` must be a `TransactionByIdRequest` object.
+
+**Response:**
+
+ On success, the result is a `Transaction` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ The ``transactionId`` must have the form of a `TransactionIdStr`;
+ malformed identifiers fail with ``WALLET_CORE_API_BAD_REQUEST``,
+ well-formed but unknown identifiers with
+ ``WALLET_TRANSACTION_NOT_FOUND``.
+
+ The full contract terms (``contractTerms``) are only reported for
+ payment transactions and only when ``includeContractTerms`` is
+ set.
+
+.. ts:def:: TransactionByIdRequest
+
+ interface TransactionByIdRequest {
+ transactionId: string;
+
+ // If set to true, report the full contract terms in the
+ // response if the transaction has them.
+ includeContractTerms?: boolean;
+ }
+
+
+.. _wallet-op-resolveTransactionReference:
+
+**resolveTransactionReference**
+
+Resolve a stable transaction ID, a wallet-local identifier, or an
+external withdrawal reference to a stable transaction ID.
+
+**Request:**
+
+ The request ``args`` must be a `ResolveTransactionReferenceRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a `ResolveTransactionReferenceResponse`
+ object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``, ``WALLET_CORE_API_BAD_REQUEST``.
+
+**Details:**
+
+ Three kinds of references are accepted:
+
+ * a stable transaction ID (`TransactionIdStr`), which is returned
+ unchanged;
+ * a wallet-local identifier of the form ``#<type>:<localIdent>``,
+ as reported in the transaction's ``localTransactionId`` field.
+ Local identifiers are deliberately not portable: importing or
+ merging a wallet can assign different local identifiers;
+ * an external, bank-generated withdrawal reference (such as an
+ LSD 0006 withdrawal-transfer-result reference) that contains
+ the reserve public key of a withdrawal. The reference must
+ match exactly one withdrawal transaction, otherwise the request
+ fails with ``WALLET_TRANSACTION_NOT_FOUND``.
+
+.. ts:def:: ResolveTransactionReferenceRequest
+
+ interface ResolveTransactionReferenceRequest {
+ transactionReference: string;
+ }
+
+
+.. ts:def:: ResolveTransactionReferenceResponse
+
+ interface ResolveTransactionReferenceResponse {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-abortTransaction:
+
+**abortTransaction**
+
+Abort a transaction.
+
+**Request:**
+
+ The request ``args`` must be an `AbortTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_TRANSACTION_ACTION_UNSUPPORTED``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+**Details:**
+
+ For payment transactions, aborting puts the payment into an
+ ``aborting`` state, in which the wallet recovers the spent coins
+ via a refund where possible before the transaction reaches a
+ final state. Whether a transaction can currently be aborted is
+ advertised in its ``txActions``.
+
+.. ts:def:: AbortTransactionRequest
+
+ interface AbortTransactionRequest {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-failTransaction:
+
+**failTransaction**
+
+Mark a transaction as failed, giving up on its ongoing processing.
+
+**Request:**
+
+ The request ``args`` must be a `FailTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_TRANSACTION_ACTION_UNSUPPORTED``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+**Details:**
+
+ This is typically used for transactions in an ``aborting`` state
+ (for example while recovering an aborted payment via a refund):
+ the user stops waiting for the recovery, and the transaction
+ transitions to the final ``failed`` state. The reason is
+ recorded in the transaction's ``failReason``. Whether a
+ transaction can currently be failed is advertised in its
+ ``txActions``.
+
+.. ts:def:: FailTransactionRequest
+
+ interface FailTransactionRequest {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-suspendTransaction:
+
+**suspendTransaction**
+
+Suspend a transaction, stopping any associated network activities
+while keeping the option of trying again at a later time. This can
+be useful to save battery power or bandwidth when an operation is
+expected to take longer, such as a very large withdrawal.
+
+**Request:**
+
+ The request ``args`` must be an `AbortTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_TRANSACTION_ACTION_UNSUPPORTED``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+
+.. _wallet-op-resumeTransaction:
+
+**resumeTransaction**
+
+Resume a transaction that was previously suspended with
+:ref:`suspendTransaction <wallet-op-suspendTransaction>`.
+
+**Request:**
+
+ The request ``args`` must be an `AbortTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_TRANSACTION_ACTION_UNSUPPORTED``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+
+.. _wallet-op-deleteTransaction:
+
+**deleteTransaction**
+
+Permanently delete a transaction from the wallet's transaction
+history.
+
+**Request:**
+
+ The request ``args`` must be a `DeleteTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``WALLET_REQUEST_TRANSACTION_STATE_UNSUPPORTED``.
+
+**Details:**
+
+ Deleting is only possible in states that advertise the ``delete``
+ action (in ``txActions``), typically final or dialog states. Any
+ background task associated with the transaction is stopped.
+
+.. ts:def:: DeleteTransactionRequest
+
+ interface DeleteTransactionRequest {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-retryTransaction:
+
+**retryTransaction**
+
+Immediately retry the transaction's underlying operation by
+resetting the retry timer of its background task.
+
+**Request:**
+
+ The request ``args`` must be a `RetryTransactionRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TRANSACTION_NOT_FOUND``.
+
+**Details:**
+
+ Retrying is only possible in states that advertise the ``retry``
+ action (in ``txActions``); the request does not wait for the
+ retried operation to complete.
+
+.. ts:def:: RetryTransactionRequest
+
+ interface RetryTransactionRequest {
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-listAssociatedRefreshes:
+
+**listAssociatedRefreshes**
+
+List the refresh transactions associated with another transaction.
+
+**Request:**
+
+ The request ``args`` must be a `ListAssociatedRefreshesRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a `ListAssociatedRefreshesResponse`
+ object.
+
+**Details:**
+
+ This operation is declared but not implemented yet; it currently
+ always fails with ``GENERIC_FEATURE_NOT_IMPLEMENTED``.
+
+.. ts:def:: ListAssociatedRefreshesRequest
+
+ interface ListAssociatedRefreshesRequest {
+ transactionId: string;
+ }
+
+
+.. ts:def:: ListAssociatedRefreshesResponse
+
+ interface ListAssociatedRefreshesResponse {
+ transactionIds: string[];
+ }
+
+
+.. _wallet-op-waitTransactionState:
+
+**waitTransactionState**
+
+Wait until a transaction is in a particular state.
+
+**Request:**
+
+ The request ``args`` must be a `WaitTransactionStateRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a `WaitTransactionStateResponse`
+ object.
+
+**Details:**
+
+ This is a long-polling operation: it returns once the
+ transaction's state matches ``txState``, matches one of the
+ ``bailStates``, or (with ``bailOnError``) an error is recorded
+ for the transaction. The response's ``matched`` field says which
+ of the two sets of states ended the wait.
+
+ The ``txState`` and ``bailStates`` fields are
+ `TestingWaitTxStateSpec` values: a `TransactionStatePattern` or a
+ list of patterns (matching when any pattern matches), a
+ wallet-internal numeric state ID, or one of the shorthands
+ ``nonpending`` (any major state other than ``pending``) and
+ ``final`` (any final major state). In a pattern, ``major``,
+ ``minor`` and ``working`` accept the wildcard ``*``; a pattern
+ without ``minor`` only matches states that have no minor state,
+ while a pattern without ``working`` matches regardless of the
+ flag.
+
+ Without ``bailStates`` or ``bailOnError``, a transaction that
+ reaches a state it will never leave keeps the caller waiting
+ until the ``timeout`` expires; the wait then fails with
+ ``GENERIC_TIMEOUT``.
+
+ When ``progressToken`` is set, the wait reports request progress
+ notifications (including recorded transaction errors with their
+ retry counter and remaining retry delay) and can be cancelled
+ with :ref:`cancelProgressToken <wallet-op-cancelProgressToken>`
+ or nudged with
+ :ref:`retryProgressTokenNow <wallet-op-retryProgressTokenNow>`.
+ Cancellation stops only the wait, not the transaction.
+
+.. ts:def:: WaitTransactionStateRequest
+
+ interface WaitTransactionStateRequest {
+ transactionId: TransactionIdStr;
+
+ // Receive request progress notifications and control this wait
+ // via ``cancelProgressToken``/``retryProgressTokenNow``.
+ // Cancellation stops only the wait. Retry-now retries the
+ // transaction if its current state allows it, without
+ // restarting the wait or extending its timeout. Transaction
+ // errors are reported with their recorded retry counter and
+ // remaining delay; without a recorded error retry, the counter
+ // is zero and the delay is "forever".
+ progressToken?: string;
+
+ // Additional identifier that is used in the logs to easily
+ // find the status of the particular wait request.
+ logId?: string;
+
+ // After the timeout has passed, give up on waiting for the
+ // desired state and raise an error instead.
+ timeout?: DurationUnitSpec;
+
+ // If set to true, wait until the desired state is reached
+ // with an error.
+ requireError?: boolean;
+
+ // State(s) to wait for.
+ txState: TestingWaitTxStateSpec;
+
+ // States that end the wait even though they are not the state
+ // that was waited for. The response says which of the two
+ // sets matched. Without this, a transaction that ends up in
+ // a state it will never leave keeps the caller waiting until
+ // the timeout.
+ bailStates?: TestingWaitTxStateSpec;
+
+ // End the wait as soon as an error is recorded for the
+ // transaction. Beware that this includes transient errors of
+ // retried operations, which are cleared again once the
+ // operation succeeds.
+ bailOnError?: boolean;
+ }
+
+
+.. ts:def:: WaitTransactionStateResponse
+
+ interface WaitTransactionStateResponse {
+ // Which set of states ended the wait: the requested state or
+ // one of the bail states.
+ matched: "target" | "bail";
+
+ // State that ended the wait.
+ txState: TransactionState;
+
+ // Wallet-internal state ID, only used for debugging and
+ // testing.
+ stId: number;
+ }
+
+
+.. ts:def:: TestingWaitTxStateSpec
+
+ // State(s) to wait for: a plain pattern or a list of patterns
+ // (matching any of them), a wallet-internal numeric state ID, or
+ // one of the shorthands for a category of states.
+ type TestingWaitTxStateSpec =
+ | TransactionStatePattern
+ | TransactionStatePattern[]
+ | number
+ | "nonpending"
+ | "final";
+
+
+.. ts:def:: TransactionStatePattern
+
+ interface TransactionStatePattern {
+ major: TransactionMajorState | TransactionStateWildcard;
+
+ minor?: TransactionMinorState | TransactionStateWildcard;
+
+ // Required value of the "working" flag of the transaction
+ // state. A transaction state without the flag counts as
+ // false. If left undefined, the flag is not taken into
+ // account when matching, i.e. it behaves like a wildcard.
+ working?: boolean | TransactionStateWildcard;
+ }
+
+
+.. ts:def:: TransactionStateWildcard
+
+ type TransactionStateWildcard = "*";
+
+
+.. ts:def:: DurationUnitSpec
+
+ // A duration given as a sum of the specified units; used for
+ // timeouts.
+ interface DurationUnitSpec {
+ seconds?: number;
+
+ minutes?: number;
+
+ hours?: number;
+
+ days?: number;
+
+ months?: number;
+
+ years?: number;
+ }
diff --git a/core/wallet-core/validation.rst b/core/wallet-core/validation.rst
@@ -0,0 +1,230 @@
+.. _wallet-op-validateIban:
+
+**validateIban**
+
+Validate an International Bank Account Number (IBAN) according to
+ISO 13616, including the country-specific length and the checksum.
+
+**Request:**
+
+ The request must be a `ValidateIbanRequest` object.
+
+**Response:**
+
+ On success, the result is a `ValidateIbanResponse` object.
+
+.. ts:def:: ValidateIbanRequest
+
+ interface ValidateIbanRequest {
+ iban: string;
+ }
+
+.. ts:def:: ValidateIbanResponse
+
+ interface ValidateIbanResponse {
+ valid: boolean;
+ }
+
+
+.. _wallet-op-canonicalizeBaseUrl:
+
+**canonicalizeBaseUrl**
+
+Canonicalize a base URL the way wallet-core does for exchange and
+auditor base URLs.
+
+**Request:**
+
+ The request must be a `CanonicalizeBaseUrlRequest` object.
+
+**Response:**
+
+ On success, the result is a `CanonicalizeBaseUrlResponse` object.
+
+**Details:**
+
+ An ``https://`` scheme is prepended when the URL has no scheme, a
+ trailing slash is appended to the path when missing, and any query
+ and fragment are removed.
+
+.. ts:def:: CanonicalizeBaseUrlRequest
+
+ interface CanonicalizeBaseUrlRequest {
+ url: string;
+ }
+
+.. ts:def:: CanonicalizeBaseUrlResponse
+
+ interface CanonicalizeBaseUrlResponse {
+ url: string;
+ }
+
+
+.. _wallet-op-convertIbanAccountFieldToPayto:
+
+**convertIbanAccountFieldToPayto**
+
+Convert user input for a bank account number into an IBAN payto
+URI.
+
+**Request:**
+
+ The request must be a `ConvertIbanAccountFieldToPaytoRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a
+ `ConvertIbanAccountFieldToPaytoResponse` object.
+
+**Details:**
+
+ Dashes and spaces are stripped from the input. For ``HUF``, the
+ input is treated as a Hungarian BBAN and converted to an IBAN (a
+ developer experiment enables the same conversion for Swiss
+ BBANs); for other currencies, the input must be a valid IBAN.
+ When the input cannot be converted, ``ok`` is ``false``.
+
+.. ts:def:: ConvertIbanAccountFieldToPaytoRequest
+
+ interface ConvertIbanAccountFieldToPaytoRequest {
+ value: string;
+ currency: string;
+ }
+
+.. ts:def:: ConvertIbanAccountFieldToPaytoResponse
+
+ type ConvertIbanAccountFieldToPaytoResponse =
+ | { ok: true; type: "iban" | "bban"; paytoUri: string }
+ | { ok: false };
+
+
+.. _wallet-op-convertIbanPaytoToAccountField:
+
+**convertIbanPaytoToAccountField**
+
+Convert an IBAN payto URI into the account number representation
+expected by the target country's banking interfaces.
+
+**Request:**
+
+ The request must be a `ConvertIbanPaytoToAccountFieldRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a
+ `ConvertIbanPaytoToAccountFieldResponse` object.
+
+**Details:**
+
+ Hungarian IBANs are converted to the domestic BBAN form (a
+ developer experiment enables the same conversion for Swiss
+ IBANs); all other IBANs are returned unchanged.
+
+.. ts:def:: ConvertIbanPaytoToAccountFieldRequest
+
+ interface ConvertIbanPaytoToAccountFieldRequest {
+ paytoUri: string;
+ }
+
+.. ts:def:: ConvertIbanPaytoToAccountFieldResponse
+
+ interface ConvertIbanPaytoToAccountFieldResponse {
+ type: "iban" | "bban";
+ value: string;
+ }
+
+
+.. _wallet-op-getBankingChoicesForPayto:
+
+**getBankingChoicesForPayto**
+
+Get banking applications or websites that can execute the given
+payto URI.
+
+**Request:**
+
+ The request must be a `GetBankingChoicesForPaytoRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetBankingChoicesForPaytoResponse`
+ object.
+
+**Details:**
+
+ Currently, choices are only returned for the ``KUDOS``
+ demonstration currency (links to the demonstrator bank's website
+ and app); the list is empty when the payto URI carries no amount
+ or no choice is known for the currency.
+
+.. ts:def:: GetBankingChoicesForPaytoRequest
+
+ interface GetBankingChoicesForPaytoRequest {
+ paytoUri: string;
+ }
+
+.. ts:def:: GetBankingChoicesForPaytoResponse
+
+ interface GetBankingChoicesForPaytoResponse {
+ choices: BankingChoiceSpec[];
+ }
+
+.. ts:def:: BankingChoiceSpec
+
+ interface BankingChoiceSpec {
+ label: string;
+ type: "link";
+ uri: string;
+ }
+
+
+.. _wallet-op-getQrCodesForPayto:
+
+**getQrCodesForPayto**
+
+Get QR code representations of a payto URI, for example to let the
+user scan the code with a banking app.
+
+**Request:**
+
+ The request must be a `GetQrCodesForPaytoRequest` object.
+
+**Response:**
+
+ On success, the result is a `GetQrCodesForPaytoResponse` object.
+
+**Details:**
+
+ All applicable QR code specifications are returned (an EPC QR
+ code and/or a Swiss QR bill); the list is empty when none
+ applies to the given payto URI.
+
+.. ts:def:: GetQrCodesForPaytoRequest
+
+ interface GetQrCodesForPaytoRequest {
+ paytoUri: string;
+ }
+
+.. ts:def:: GetQrCodesForPaytoResponse
+
+ interface GetQrCodesForPaytoResponse {
+ codes: QrCodeSpec[];
+ }
+
+.. ts:def:: QrCodeSpec
+
+ // Specification of a QR code that includes payment information.
+ interface QrCodeSpec {
+ // Type of the QR code. Depending on the type, different
+ // visual styles might be applied.
+ type: SupportedBankQr;
+
+ // Content of the QR code that should be rendered.
+ qrContent: string;
+ }
+
+.. ts:def:: SupportedBankQr
+
+ type SupportedBankQr = "epc-qr" | "spc";
diff --git a/core/wallet-core/withdrawals.rst b/core/wallet-core/withdrawals.rst
@@ -0,0 +1,625 @@
+.. _wallet-op-prepareWithdrawExchange:
+
+**prepareWithdrawExchange**
+
+Prepare for withdrawing via a ``taler://withdraw-exchange`` URI.
+
+The URI names the exchange to withdraw from and may fix an amount.
+Wallet-core fetches (or updates) the exchange entry, ephemerally adding
+the exchange to the wallet's known exchanges if it is not known yet,
+and checks that the currency of the URI's amount matches the exchange's
+currency.
+
+This is the first step of a manual withdrawal that is initiated on the
+exchange's side. The client can then query the fees and other details
+with :ref:`getWithdrawalDetailsForAmount
+<wallet-op-getWithdrawalDetailsForAmount>` and create the withdrawal
+with :ref:`acceptManualWithdrawal <wallet-op-acceptManualWithdrawal>`.
+
+**Request:**
+
+ The request must be a `PrepareWithdrawExchangeRequest` object.
+
+**Response:**
+
+ On success, the result is a `PrepareWithdrawExchangeResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``, ``GENERIC_CURRENCY_MISMATCH``.
+
+.. ts:def:: PrepareWithdrawExchangeRequest
+
+ interface PrepareWithdrawExchangeRequest {
+ // A taler://withdraw-exchange URI.
+ talerUri: string;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: PrepareWithdrawExchangeResponse
+
+ interface PrepareWithdrawExchangeResponse {
+ // Base URL of the exchange that already existed or was
+ // ephemerally added as an exchange entry to the wallet.
+ exchangeBaseUrl: string;
+
+ // Amount from the taler://withdraw-exchange URI.
+ // Only present if specified in the URI.
+ amount?: AmountString;
+ }
+
+
+.. _wallet-op-prepareBankIntegratedWithdrawal:
+
+**prepareBankIntegratedWithdrawal**
+
+Prepare a bank-integrated withdrawal operation.
+
+Wallet-core resolves the ``taler://withdraw`` URI against the bank's
+bank integration API, obtains the status of the withdrawal operation
+and creates a withdrawal transaction in a dialog state. If the bank
+suggests an exchange that is not known to the wallet yet, it is
+ephemerally added to the wallet's known exchanges.
+
+After the user has reviewed the operation's details (and selected an
+amount and an exchange, where the bank allows choosing them), the
+client confirms the withdrawal with :ref:`confirmWithdrawal
+<wallet-op-confirmWithdrawal>`.
+
+**Request:**
+
+ The request must be a `PrepareBankIntegratedWithdrawalRequest`
+ object.
+
+**Response:**
+
+ On success, the result is a `PrepareBankIntegratedWithdrawalResponse`
+ object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``.
+
+**Details:**
+
+ The operation is idempotent with respect to the URI: calling it
+ again with the same ``talerWithdrawUri`` returns the existing
+ withdrawal transaction instead of creating a new one. If the bank
+ reports the withdrawal operation as already selected, confirmed or
+ aborted and the wallet has no transaction for it, the request fails.
+
+.. ts:def:: PrepareBankIntegratedWithdrawalRequest
+
+ interface PrepareBankIntegratedWithdrawalRequest {
+ talerWithdrawUri: string;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: PrepareBankIntegratedWithdrawalResponse
+
+ interface PrepareBankIntegratedWithdrawalResponse {
+ // Transaction ID of the withdrawal transaction (newly created or
+ // already existing for the same URI).
+ transactionId: TransactionIdStr;
+
+ // Details of the bank-side withdrawal operation.
+ info: WithdrawUriInfoResponse;
+ }
+
+.. ts:def:: WithdrawUriInfoResponse
+
+ interface WithdrawUriInfoResponse {
+ operationId: string;
+ status: WithdrawalOperationStatusFlag;
+
+ // URL for confirming the transfer at the bank.
+ confirmTransferUrl?: string;
+
+ currency: string;
+
+ // Amount that will be withdrawn (raw amount, without fee
+ // considerations). Only given once the amount is fixed.
+ amount: AmountString | undefined;
+
+ // Set to true if the user is allowed to edit the amount.
+ // Note that even with a non-editable amount, the amount might be
+ // undefined at the beginning of the withdrawal process.
+ editableAmount: boolean;
+
+ // Maximum amount that the wallet can choose to withdraw.
+ maxAmount: AmountString | undefined;
+
+ wireFee: AmountString | undefined;
+
+ // Exchange suggested by the bank, if any.
+ defaultExchangeBaseUrl?: string;
+
+ editableExchange: boolean;
+
+ // Exchanges that can be used for the withdrawal, filtered to the
+ // currency of the withdrawal operation. If the exchange is not
+ // editable, contains only the bank-suggested exchange.
+ possibleExchanges: ExchangeListItem[];
+ }
+
+.. ts:def:: WithdrawalOperationStatusFlag
+
+ // Status of a bank-integrated withdrawal operation:
+ // - "pending": pending parameter selection (exchange and reserve
+ // public key)
+ // - "selected": parameters selected, pending confirmation
+ // - "aborted": the operation has been aborted
+ // - "confirmed": the transfer has been confirmed and registered by
+ // the bank
+ type WithdrawalOperationStatusFlag =
+ | "pending"
+ | "selected"
+ | "aborted"
+ | "confirmed";
+
+
+.. _wallet-op-confirmWithdrawal:
+
+**confirmWithdrawal**
+
+Confirm a withdrawal transaction.
+
+Confirms a bank-integrated withdrawal that was previously prepared
+with :ref:`prepareBankIntegratedWithdrawal
+<wallet-op-prepareBankIntegratedWithdrawal>`, selecting the exchange
+to withdraw from and, where the withdrawal operation has an editable
+amount, the amount to withdraw. The exchange's terms of service must
+have been accepted and any pending exchange key change must have been
+confirmed beforehand.
+
+After the confirmation, wallet-core registers the reserve with the
+bank; the user then authorizes the actual wire transfer to the
+exchange in their banking application.
+
+**Request:**
+
+ The request must be a `ConfirmWithdrawalRequest` object.
+
+**Response:**
+
+ On success, the result is an empty object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_KYC_LIMIT_EXCEEDED``, ``WALLET_NO_SUITABLE_EXCHANGE``,
+ ``WALLET_EXCHANGE_TOS_NOT_ACCEPTED``, ``WALLET_TRANSACTION_NOT_FOUND``,
+ ``GENERIC_CURRENCY_MISMATCH``, ``WALLET_CORE_API_BAD_REQUEST``,
+ ``WALLET_EXCHANGE_KEYS_NOT_ACCEPTED``.
+
+**Details:**
+
+ The ``transactionId`` must refer to a withdrawal transaction that is
+ still in the dialog state. Confirming is idempotent: once the
+ withdrawal has been registered with the bank, repeating the
+ confirmation succeeds without further effect.
+
+ The ``amount`` may only be omitted for withdrawals from a foreign
+ account (a ``taler://withdraw`` URI with
+ ``external-confirmation=1``), where the bank fixes the amount when
+ the transfer is confirmed.
+
+.. ts:def:: ConfirmWithdrawalRequest
+
+ interface ConfirmWithdrawalRequest {
+ transactionId: string;
+ exchangeBaseUrl: string;
+ amount?: AmountString | undefined;
+ forcedDenomSel?: ForcedDenomSel;
+ restrictAge?: number;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: ForcedDenomSel
+
+ interface ForcedDenomSel {
+ denoms: {
+ value: AmountString;
+ count: number;
+ }[];
+ }
+
+
+.. _wallet-op-acceptBankIntegratedWithdrawal:
+
+**acceptBankIntegratedWithdrawal**
+
+This operation is **deprecated**. Use
+:ref:`prepareBankIntegratedWithdrawal
+<wallet-op-prepareBankIntegratedWithdrawal>` followed by
+:ref:`confirmWithdrawal <wallet-op-confirmWithdrawal>` instead.
+
+Accept a bank-integrated withdrawal in one step, equivalent to
+preparing the withdrawal and then confirming it with the given
+exchange and amount. Before returning, the wallet tries to register
+the reserve with the bank. Thus, after this call returns, the
+withdrawal operation can be confirmed with the bank via
+``confirmTransferUrl``.
+
+**Request:**
+
+ The request must be an `AcceptBankIntegratedWithdrawalRequest`
+ object.
+
+**Response:**
+
+ On success, the result is an `AcceptWithdrawalResponse` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_KEYS_NOT_ACCEPTED``.
+
+.. ts:def:: AcceptBankIntegratedWithdrawalRequest
+
+ interface AcceptBankIntegratedWithdrawalRequest {
+ talerWithdrawUri: string;
+ exchangeBaseUrl: string;
+ forcedDenomSel?: ForcedDenomSel;
+
+ // Amount to withdraw. If the bank's withdrawal operation uses a
+ // fixed amount, this field must either be left undefined or its
+ // value must match the amount from the withdrawal operation.
+ amount?: AmountString;
+
+ restrictAge?: number;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: AcceptWithdrawalResponse
+
+ interface AcceptWithdrawalResponse {
+ confirmTransferUrl?: string;
+ transactionId: TransactionIdStr;
+ }
+
+
+.. _wallet-op-getWithdrawalDetailsForAmount:
+
+**getWithdrawalDetailsForAmount**
+
+Get details for withdrawing a particular amount (manual withdrawal).
+
+Computes the terms of withdrawing the given amount: the raw amount the
+user has to transfer to the exchange, the effective amount that will be
+added to the wallet balance after withdrawal fees, the number of coins
+that would be withdrawn, the exchange's bank accounts that can receive
+the transfer (including accounts that require currency conversion),
+age-restriction options and a preview of KYC requirements.
+
+No transaction is created. The client uses the result to let the user
+review the withdrawal before creating it with
+:ref:`acceptManualWithdrawal <wallet-op-acceptManualWithdrawal>`.
+
+**Request:**
+
+ The request must be a `GetWithdrawalDetailsForAmountRequest` object.
+
+**Response:**
+
+ On success, the result is a `WithdrawalDetailsForAmount` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_EXCHANGE_ENTRY_NOT_FOUND``,
+ ``WALLET_EXCHANGE_TOS_NOT_ACCEPTED``, ``GENERIC_CURRENCY_MISMATCH``.
+
+**Details:**
+
+ The exchange is selected with ``exchangeBaseUrl``. When it is
+ omitted, ``restrictScope`` names a currency scope and the wallet's
+ preferred exchange for that scope is used instead.
+
+ When ``transactionId`` refers to a prepared bank-integrated
+ withdrawal, the sender account of that withdrawal is taken into
+ account when evaluating account-specific withdrawal rules (KYC).
+
+ An ``unconfirmedKeyChange`` in the result means the exchange changed
+ its key set and the user has not confirmed the change yet; accepting
+ the withdrawal will be refused until the change is confirmed with
+ :ref:`confirmExchangeKeyChange <wallet-op-confirmExchangeKeyChange>`,
+ so this is the point at which to warn the user.
+
+.. ts:def:: GetWithdrawalDetailsForAmountRequest
+
+ interface GetWithdrawalDetailsForAmountRequest {
+ exchangeBaseUrl?: string;
+
+ // Prepared bank-integrated withdrawal whose sender account should
+ // be checked.
+ transactionId?: TransactionIdStr;
+
+ // Specify currency scope for the withdrawal.
+ // May only be used when exchangeBaseUrl is not specified.
+ restrictScope?: ScopeInfo;
+
+ amount: AmountString;
+
+ restrictAge?: number;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: WithdrawalDetailsForAmount
+
+ interface WithdrawalDetailsForAmount extends WithdrawalKycPreview {
+ // Exchange base URL for the withdrawal.
+ exchangeBaseUrl: string;
+
+ // Amount that the user will transfer to the exchange.
+ amountRaw: AmountString;
+
+ // Amount that will be added to the user's wallet balance.
+ amountEffective: AmountString;
+
+ // Number of coins that would be used for withdrawal.
+ // UIs should warn if this number is too high (roughly at >100).
+ numCoins: number;
+
+ // Ways to pay the exchange, including accounts that require
+ // currency conversion.
+ withdrawalAccountsList: WithdrawalExchangeAccountDetails[];
+
+ // If the exchange supports age-restricted coins it will return
+ // the array of ages.
+ ageRestrictionOptions?: number[];
+
+ // Scope info of the currency withdrawn.
+ scopeInfo: ScopeInfo;
+
+ // Set when the exchange changed its key set and the user has not
+ // confirmed the change. Accepting the withdrawal will be refused
+ // until they do, so this is the point at which to warn them.
+ unconfirmedKeyChange?: ExchangeKeyChangeInfo;
+
+ // KYC soft limit.
+ // Withdrawals over that amount will require KYC.
+ kycSoftLimit?: AmountString;
+
+ // KYC hard limit.
+ // Withdrawals over that amount will be denied.
+ kycHardLimit?: AmountString;
+
+ // Ways to pay the exchange.
+ // Deprecated in favor of withdrawalAccountsList.
+ paytoUris: string[];
+ }
+
+.. ts:def:: WithdrawalKycPreview
+
+ interface WithdrawalKycPreview {
+ // Whether the proposed withdrawal needs a KYC warning based on
+ // this wallet's balance, known KYC allowance, advertised
+ // zero-limit rules and known withdrawal volume. This is a
+ // preview, not a guarantee that the exchange will not require
+ // KYC. Optional for compatibility with older wallet-core
+ // versions, which omit it.
+ kycRequired?: boolean;
+
+ // Balance usage from the same evaluation as kycRequired.
+ balanceKyc?: BalanceKycUsage;
+
+ // Account-specific withdrawal-rule preview using this wallet's
+ // history. "ok" only covers exposed rules and available local
+ // history; "unknown" means account limits could not be evaluated
+ // and must not be shown as clearance. Older wallet-core versions
+ // omit this field.
+ withdrawalKycStatus?: WithdrawalKycStatus;
+ }
+
+.. ts:def:: BalanceKycUsage
+
+ // This wallet's balance at the issuing exchange at preview time,
+ // using the same accounting as balance-KYC enforcement (including
+ // pending refresh outputs). Does not reserve capacity for
+ // concurrent withdrawals or report account-wide
+ // transaction-volume/hard-limit usage.
+ interface BalanceKycUsage {
+ currentBalance: AmountString;
+
+ // Current balance plus the selected coins' value after withdrawal
+ // fees.
+ projectedBalance: AmountString;
+
+ // Applicable balance threshold; omitted when no finite limit is
+ // known.
+ threshold?: AmountString;
+
+ // Additional balance permitted, clamped to zero; omitted with
+ // threshold.
+ remaining?: AmountString;
+ }
+
+.. ts:def:: WithdrawalKycStatus
+
+ type WithdrawalKycStatus =
+ | "unknown"
+ | "ok"
+ | "kyc-required"
+ | "hard-limit";
+
+.. ts:def:: WithdrawalExchangeAccountDetails
+
+ interface WithdrawalExchangeAccountDetails {
+ // Payto URI of the exchange. Depending on whether the (manual!)
+ // withdrawal is accepted or just being checked, this already
+ // includes the subject with the reserve public key.
+ paytoUri: string;
+
+ // Whether the account can be used by the user to send funds for a
+ // withdrawal. "ok": account should be shown to the user;
+ // "error": account should not be shown to the user, UIs might
+ // render the error (in conversionError), especially in dev mode.
+ status: "ok" | "error";
+
+ // Transfer amount. Might be in a different currency than the
+ // requested amount for withdrawal. Absent if this is a
+ // conversion account and the conversion failed.
+ transferAmount?: AmountString;
+
+ // Currency specification for the external currency.
+ // Only included if this account requires a currency conversion.
+ currencySpecification?: CurrencySpecification;
+
+ // Further restrictions for sending money to the exchange.
+ creditRestrictions?: AccountRestriction[];
+
+ // Label given to the account or the account's bank by the
+ // exchange.
+ bankLabel?: string;
+
+ // Display priority assigned to this bank account by the exchange.
+ priority?: number;
+
+ // Error that happened when attempting to request the conversion
+ // rate.
+ conversionError?: TalerErrorDetail;
+
+ // Timestamp that indicates when the transfer options expire.
+ // If missing, options do not expire.
+ transferExpiry?: TalerProtocolTimestamp;
+
+ // Options for transferring funds to the exchange for the
+ // withdrawal.
+ transferOptions: TransferOption[];
+ }
+
+.. ts:def:: TransferOption
+
+ type TransferOption =
+ | TransferOptionPayto
+ | TransferOptionUri
+ | TransferOptionSwissQrBill;
+
+.. ts:def:: TransferOptionPayto
+
+ interface TransferOptionPayto {
+ type: "payto";
+ paytoUri: string;
+ qrCodes: QrCodeSpec[];
+ }
+
+.. ts:def:: TransferOptionUri
+
+ interface TransferOptionUri {
+ type: "uri";
+ uri: string;
+ }
+
+.. ts:def:: TransferOptionSwissQrBill
+
+ interface TransferOptionSwissQrBill {
+ type: "ch-qr-bill";
+ paytoUri: string;
+ qrReferenceNumber: string;
+ qrCodes: QrCodeSpec[];
+ }
+
+
+.. _wallet-op-acceptManualWithdrawal:
+
+**acceptManualWithdrawal**
+
+Create a manual withdrawal.
+
+Creates a withdrawal transaction for the given amount at the given
+exchange, with a freshly generated reserve key pair. The user must
+then wire the amount to one of the exchange's bank accounts returned
+in the result; the ``paytoUri`` of each account already includes the
+reserve public key as the transfer subject. Once the transfer
+arrives at the exchange, wallet-core withdraws the coins.
+
+**Request:**
+
+ The request must be an `AcceptManualWithdrawalRequest` object.
+
+**Response:**
+
+ On success, the result is an `AcceptManualWithdrawalResult` object.
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_KYC_LIMIT_EXCEEDED``, ``WALLET_EXCHANGE_TOS_NOT_ACCEPTED``,
+ ``GENERIC_CURRENCY_MISMATCH``, ``WALLET_EXCHANGE_KEYS_NOT_ACCEPTED``.
+
+.. ts:def:: AcceptManualWithdrawalRequest
+
+ interface AcceptManualWithdrawalRequest {
+ exchangeBaseUrl: string;
+ amount: AmountString;
+ restrictAge?: number;
+
+ // Instead of generating a fresh, random reserve key pair, use
+ // the provided reserve private key. Use with caution. Usage of
+ // this field may be restricted to developer mode.
+ forceReservePriv?: EddsaPrivateKeyString;
+
+ progressToken?: string;
+ }
+
+.. ts:def:: AcceptManualWithdrawalResult
+
+ interface AcceptManualWithdrawalResult {
+ // Transaction ID of the newly created withdrawal transaction.
+ transactionId: TransactionIdStr;
+
+ // Public key of the newly created reserve.
+ reservePub: string;
+
+ // Bank accounts of the exchange that can be used to fund the
+ // withdrawal.
+ withdrawalAccountsList: WithdrawalExchangeAccountDetails[];
+ }
+
+
+.. _wallet-op-getWithdrawalDetailsForUri:
+
+**getWithdrawalDetailsForUri**
+
+This operation is **deprecated**. Use
+:ref:`prepareBankIntegratedWithdrawal
+<wallet-op-prepareBankIntegratedWithdrawal>` instead.
+
+Get details for withdrawing via a ``taler://withdraw`` URI, without
+creating a withdrawal transaction. As side effects, the bank is
+queried via the bank integration API and the exchange suggested by the
+bank is ephemerally added to the wallet's list of known exchanges.
+
+**Request:**
+
+ The request must be a `GetWithdrawalDetailsForUriRequest` object.
+
+**Response:**
+
+ On success, the result is a `WithdrawUriInfoResponse` object (see
+ :ref:`prepareBankIntegratedWithdrawal
+ <wallet-op-prepareBankIntegratedWithdrawal>`).
+
+**Expected errors:**
+
+ The caller can handle the following errors inline:
+ ``WALLET_TALER_URI_MALFORMED``.
+
+.. ts:def:: GetWithdrawalDetailsForUriRequest
+
+ interface GetWithdrawalDetailsForUriRequest {
+ talerWithdrawUri: string;
+
+ // Deprecated, not used.
+ restrictAge?: number;
+
+ progressToken?: string;
+ }