taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

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:
Acore/api-wallet-core.rst | 477+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/index.rst | 1+
Acore/wallet-core/balances.rst | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/bank-accounts.rst | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/contacts.rst | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/database.rst | 239+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/deposits.rst | 421+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/diagnostics.rst | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/donau.rst | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/exchanges.rst | 940+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/global-currency.rst | 292+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/hints.rst | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/init.rst | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/mailbox.rst | 260+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/notifications.rst | 570+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/p2p.rst | 548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/payments.rst | 985+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/requests.rst | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/taldir.rst | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/testing.rst | 1060+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/tokens.rst | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/transactions.rst | 1380+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/validation.rst | 230+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/wallet-core/withdrawals.rst | 625+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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; + }