commit 94229c03d8e56dc54cceb24893e0a4a96486810d
parent eda8312b713df9e471d0b8d91a29a4632b90a772
Author: Antoine A <>
Date: Fri, 18 Sep 2026 16:54:52 +0200
DD104 draft
Diffstat:
1 file changed, 232 insertions(+), 0 deletions(-)
diff --git a/design-documents/104-status.rst b/design-documents/104-status.rst
@@ -0,0 +1,231 @@
+DD 104: Wire Gateway Observability and Administration
+#####################################################
+
+:Design status: Draft
+:Implementation status: Not started
+:DD shepherd: Antoine d'Aligny
+:Historical contributors: Antoine d'Aligny
+:First published: 2026-09-18
+:Last substantive change: 2026-09-18
+
+Summary
+=======
+
+This design document presents the challenges we currently face when handling
+transaction failures in wire gateway adapters and proposes a solution for
+addressing them.
+
+Motivation
+==========
+
+In the current design and implementations, both incoming and outgoing
+transactions can fail without administrators being notified or having the
+ability to take corrective action.
+
+Requirements
+============
+
+* The solution must allow administrators to see failures, understand their
+ causes, and possibly fix them.
+* The solution must be lightweight to implement in wire gateway adapters, as
+ the goal of this API is to provide a thin wrapper around how each
+ implementation works. Common logic that can be extracted into another
+ component should be.
+
+Proposed Solution
+=================
+
+Terminology
+-----------
+
+* `in_tx`: final incoming transaction
+* `out_tx`: final outgoing transaction
+* `talerable_in_tx`: talerable final incoming transaction (RESERVE | KYC | MAP)
+* `talerable_out_tx`: talerable final outgoing transaction (deposit)
+* `transfer`: initiated talerable outgoing transaction
+* `initiated`: initiated outgoing transaction
+* `bounce`: bounced invalid outgoing transaction
+
+Current API State
+-----------------
+
+We already have endpoints for querying Talerable transactions:
+`talerable_in_tx` with `GET /history/incoming` and `talerable_out_tx` with
+`GET /history/outgoing`.
+
+We also already have endpoints for querying `transfer` with `GET /transfers`
+and `GET /transfers/$ROW_ID`.
+
+What is missing is access to raw (incompled, malformed, etc) transactions, `in_tx` and `out_tx`, as well
+as initiated and bounced transactions, `initiated` and `bounce`.
+
+Generic API Design
+------------------
+
+Each adapter works differently. In most adapters, `transfer` and `bounce`
+produce an `initiated` operation, while other payment systems, such as
+Cyclos, provide a proper way to perform a bounce idempotently and have a
+specific bounce operation.
+
+Each adapter also has different internal unique identifiers. For example,
+`libeufin_nexus` uses three of them while `depolymerizer-bitcoin` has a
+single transaction ID.
+
+We need an API that can expose enough detail for manual reconciliation while
+remaining generic enough to reuse the same logic across all adapters.
+
+For `transfer`, we already have the paginated `GET /transfers` endpoint and
+the single-entry `GET /transfers/$ROW_ID` endpoint. The problem with this
+design is that it is not well suited to following the progress of transfers.
+Transfers do not progress or reach finality in the order in which they are
+created.
+
+I propose adding a new status-history endpoint,
+`GET /transfers/status-history`. This will allow administrators to understand
+the lifecycle of a transaction and track the progress of all transfers in
+real time.
+
+We also need new endpoints for `incoming` and `outgoing` transactions,
+including paginated and single-entry endpoints, so that administrators can
+inspect all transactions, including incomplete or invalid ones. This is
+necessary to understand and debug why an incoming transaction never reaches
+the Talerable history.
+
+Finally, we need a way to track both `initiated` and `bounce` transactions
+using new endpoints providing paginated listings, status histories and
+single-entry views.
+
+We could potentially remove all `transfer` endpoints, since transfers are a
+subset of `initiated` operatuibs. Whether to do so depends on whether the
+transfer-specific API remains useful as a higher-level abstraction.
+
+We could also enforce a single `initiated` operation concept, event if underneath
+bounces works differently. As long as doing this it not too messy for the
+database layer we should pursue this simplification.
+
+.. ts:def:: InTx
+
+ interface InTx {
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Unstructured implementation specific fields that must be shown.
+ // Contains at least the unique identifiers to be used for manual reconciliation.
+ details: {string: string};
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount?: Amount;
+
+ // Unstructured transaction subject
+ subject?: string;
+
+ // Fee paid by the creditor.
+ // If not null, creditor actually received amount - credit_fee
+ credit_fee?: Amount;
+
+ // Full payto URI to identify the sender of funds.
+ debit_account?: string;
+
+ // ID of the bounce operation if bounced
+ bounce_id?: SafeUint64;
+ }
+
+.. ts:def:: OutTx
+
+ interface OutTx {
+ // Opaque identifier of the returned record.
+ row_id: SafeUint64;
+
+ // Unstructured implementation specific fields that must be shown.
+ // Contains at least the unique identifiers to be used for manual reconciliation.
+ details: {string: string};
+
+ // Date of the transaction.
+ date: Timestamp;
+
+ // Amount received before credit_fee.
+ amount?: Amount;
+
+ // Unstructured transaction subject
+ subject?: string;
+
+ // Fee paid by the debtor.
+ // If not null, debtor actually paid amount + debit_fee
+ debit_fee?: Amount;
+
+ // Full payto URI to identify the receiver of funds.
+ credit_account?: string;
+ }
+
+.. ts:def:: InitiatedTx
+
+ interface InitiatedTx {
+ // Opaque ID of this operation.
+ operation_id: SafeUint64;
+
+ // Amount to transfer.
+ amount: Amount;
+
+ // The recipient's account identifier as a full payto:// URI.
+ credit_account: string;
+
+ // Optional ID of the wire transfer initiation if operation is a transfer
+ transfer_id?: SafeUint64;
+
+ // Optional ID of the bounced incoming transaction if operation is a bounce
+ bounced_id?: SafeUint64;
+
+ // Current status
+ // pending: in progress
+ // transient_failure: has failed but may succeed later
+ // permanent_failure: has failed permanently and will never reach finality
+ // success: has succeeded and reached finality
+ // late_failure: has failed permanently after reaching the success status
+ status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
+
+ // Timestamp that indicates when this status was reached.
+ timestamp: Timestamp;
+ }
+
+.. ts:def:: InitiatedStatus
+
+ interface InitiatedStatus {
+ // Opaque ID of this operation.
+ operation_id: SafeUint64;
+
+ // Opaque ID of this status, used for pagination.
+ row_id: SafeUint64;
+
+ // pending: in progress
+ // transient_failure: has failed but may succeed later
+ // permanent_failure: has failed permanently and will never reach finality
+ // success: has succeeded and reached finality
+ // late_failure: has failed permanently after reaching the success status
+ status: "pending" | "transient_failure" | "permanent_failure" | "success" | "late_failure";
+
+ // Optional unstructured messages about the status. Can be used to document the reasons for failure or the state of progress.
+ status_msg?: string;
+
+ // Timestamp that indicates when this status was reached.
+ timestamp: Timestamp;
+ }
+
+Test Plan
+=========
+
+
+
+Definition of Done
+==================
+
+Alternatives
+============
+
+Drawbacks
+=========
+
+Discussion / Q&A
+================
+\ No newline at end of file