commit 1faea487783c82362af5e87631c4cf28b73dd27e
parent 23e092a4473f0ae532b075b2f1f4c5c5bba169bb
Author: Florian Dold <dold@taler.net>
Date: Thu, 10 Sep 2026 01:31:38 +0200
taler-util: describe coin recovery requests and progress
Decode the ordered output denominations in MELT history and the
current reveal endpoint field. Define the recovery request, result and
progress notification types, and handle progress in the browser cache
notification dispatch.
Diffstat:
5 files changed, 83 insertions(+), 0 deletions(-)
diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts
@@ -32,7 +32,10 @@ import {
TransactionIdStr,
} from "./types-taler-wallet.js";
+import { AmountString } from "./types-taler-common.js";
+
export enum NotificationType {
+ CoinRecoveryProgress = "coin-recovery-progress",
BalanceChange = "balance-change",
BankAccountChange = "bank-account-change",
BackupOperationError = "backup-error",
@@ -407,7 +410,33 @@ export interface DatabaseMaintenanceProgressNotification {
error?: TalerErrorDetail;
}
+export type CoinRecoveryPhase =
+ | "starting"
+ | "history"
+ | "derive"
+ | "melt"
+ | "reveal"
+ | "refresh"
+ | "complete"
+ | "incomplete"
+ | "failed"
+ | "cancelled";
+
+export interface CoinRecoveryProgressNotification {
+ type: NotificationType.CoinRecoveryProgress;
+ exchangeBaseUrl: string;
+ progressToken: string;
+ phase: CoinRecoveryPhase;
+ numChecked: number;
+ numDiscovered: number;
+ numQueued: number;
+ numRecovered: number;
+ recoveredAmount: AmountString;
+ numIssues: number;
+}
+
export type WalletNotification =
+ | CoinRecoveryProgressNotification
| BalanceChangeNotification
| BankAccountChangeNotification
| BackupOperationErrorNotification
diff --git a/packages/taler-util/src/types-taler-common.ts b/packages/taler-util/src/types-taler-common.ts
@@ -301,6 +301,8 @@ export interface CoinMeltTransaction extends CoinHistoryItemCommon {
/** Added in exchange protocol v32. */
refresh_seed?: string;
blinding_seed?: string;
+ /** Ordered output denominations, including duplicates. Absent on older exchanges. */
+ denoms_h?: string[];
h_age_commitment?: HashCodeString;
coin_sig: EddsaSignatureString;
}
@@ -422,6 +424,7 @@ export const codecForCoinSpendHistoryItem = (): Codec<CoinSpendHistoryItem> =>
.property("h_denom_pub", codecForString())
.property("refresh_seed", codecOptional(codecForString()))
.property("blinding_seed", codecOptional(codecForString()))
+ .property("denoms_h", codecOptional(codecForList(codecForString())))
.property("h_age_commitment", codecOptional(codecForString()))
.property("coin_sig", codecForString())
.build("CoinMeltTransaction"),
diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts
@@ -635,6 +635,8 @@ export interface ExchangeMeltResponse {
* the exchange may return different values for the refresh_base_url.
*/
refresh_base_url?: string;
+ /** Current protocol name for the reveal endpoint base URL. */
+ reveal_base_url?: string;
}
export const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
@@ -1230,6 +1232,7 @@ export const codecForExchangeMeltResponse = (): Codec<ExchangeMeltResponse> =>
.property("exchange_sig", codecForEddsaSignature())
.property("noreveal_index", codecForNumber())
.property("refresh_base_url", codecOptional(codecForString()))
+ .property("reveal_base_url", codecOptional(codecForString()))
.build("ExchangeMeltResponse");
export interface FutureKeysResponse {
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -4276,6 +4276,53 @@ export interface TestingGetDenomStatsRequest {
exchangeBaseUrl: string;
}
+/** Follow the refresh/link recovery chain for coins at one exchange. */
+export interface TestingRecoverCoinsRequest {
+ exchangeBaseUrl: string;
+ /** Start with fresh coins by default. Unfinished recovery always resumes. */
+ onlyFresh?: boolean;
+ progressToken?: string;
+}
+
+export const codecForTestingRecoverCoinsRequest =
+ (): Codec<TestingRecoverCoinsRequest> =>
+ buildCodecForObject<TestingRecoverCoinsRequest>()
+ .property("exchangeBaseUrl", codecForString())
+ .property("onlyFresh", codecOptional(codecForBoolean()))
+ .property("progressToken", codecOptional(codecForString()))
+ .build("TestingRecoverCoinsRequest");
+
+export 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;
+}
+
+export 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[];
+}
+
/** Validate exchange coin histories and compare balances of unspent coins. */
export interface TestingCheckCoinsRequest {
/** Canonicalized before selecting coins. Only this exchange is contacted. */
diff --git a/packages/wallet-webui/src/api/notifications.ts b/packages/wallet-webui/src/api/notifications.ts
@@ -70,6 +70,7 @@ export function notificationFamilies(
case NotificationType.RequestObservabilityEvent:
case NotificationType.RequestProgressError:
case NotificationType.RequestProgressPhase:
+ case NotificationType.CoinRecoveryProgress:
case NotificationType.DatabaseMaintenanceProgress:
return [];
default: {