commit 475ac2e513e2b7a1b576592749038ee4ec61d3e8
parent 32944ad9a392da1d86a570f277232026c0bb8fd4
Author: Florian Dold <florian@dold.me>
Date: Mon, 15 Sep 2025 13:37:55 +0200
wallet-core: implement allowCompletion for addExchange request, implement completeExchangeBaseUrl request
Diffstat:
3 files changed, 100 insertions(+), 5 deletions(-)
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -2013,22 +2013,31 @@ export type GetExchangeEntryByUrlResponse = ExchangeListItem;
export interface AddExchangeRequest {
/**
- * @deprecated Use {@link uri} instead
- */
- exchangeBaseUrl?: string;
-
- /**
* 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 use a separate API call to start a forced exchange update instead
*/
forceUpdate?: boolean;
+
+ /**
+ * @deprecated Use {@link uri} instead
+ */
+ exchangeBaseUrl?: string;
}
export interface AddExchangeResponse {
@@ -4161,3 +4170,33 @@ export const codecForImportDbFromFileRequest =
buildCodecForObject<ImportDbFromFileRequest>()
.property("path", codecForString())
.build("ImportDbFromFileRequest");
+
+export interface CompleteBaseUrlRequest {
+ url: string;
+}
+
+export const codecForCompleteBaseUrlRequest =
+ (): Codec<CompleteBaseUrlRequest> =>
+ buildCodecForObject<CompleteBaseUrlRequest>()
+ .property("url", codecForString())
+ .build("CompleteBaseUrlRequest");
+
+export 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;
+ };
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -54,6 +54,8 @@ import {
CheckPeerPushDebitRequest,
CheckPeerPushDebitResponse,
CoinDumpJson,
+ CompleteBaseUrlRequest,
+ CompleteBaseUrlResult,
ConfirmPayRequest,
ConfirmPayResult,
ConfirmPeerPullDebitRequest,
@@ -265,6 +267,7 @@ export enum WalletApiOperation {
UpdateExchangeEntry = "updateExchangeEntry",
PrepareWithdrawExchange = "prepareWithdrawExchange",
GetExchangeResources = "getExchangeResources",
+ CompleteExchangeBaseUrl = "completeExchangeBaseUrl",
DeleteExchange = "deleteExchange",
ListGlobalCurrencyExchanges = "listGlobalCurrencyExchanges",
ListGlobalCurrencyAuditors = "listGlobalCurrencyAuditors",
@@ -695,6 +698,16 @@ export type RemoveGlobalCurrencyAuditorOp = {
// group: Exchange Management
/**
+ * Force a refresh on coins where it would not
+ * be necessary.
+ */
+export type CompleteExchangeBaseUrlOp = {
+ op: WalletApiOperation.CompleteExchangeBaseUrl;
+ request: CompleteBaseUrlRequest;
+ response: CompleteBaseUrlResult;
+};
+
+/**
* List exchanges known to the wallet.
*/
export type ListExchangesOp = {
@@ -1513,6 +1526,7 @@ export type WalletOperations = {
[WalletApiOperation.TestingWaitExchangeWalletKyc]: TestingWaitExchangeWalletKycOp;
[WalletApiOperation.TestingPlanMigrateExchangeBaseUrl]: TestingPlanMigrateExchangeBaseUrlOp;
[WalletApiOperation.HintApplicationResumed]: HintApplicationResumedOp;
+ [WalletApiOperation.CompleteExchangeBaseUrl]: CompleteExchangeBaseUrlOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -52,6 +52,8 @@ import {
Codec,
CoinDumpJson,
CoinStatus,
+ CompleteBaseUrlRequest,
+ CompleteBaseUrlResult,
ConfirmPayRequest,
ConfirmPayResult,
CoreApiResponse,
@@ -155,6 +157,7 @@ import {
codecForCheckPayTemplateRequest,
codecForCheckPeerPullPaymentRequest,
codecForCheckPeerPushDebitRequest,
+ codecForCompleteBaseUrlRequest,
codecForConfirmPayRequest,
codecForConfirmPeerPushPaymentRequest,
codecForConfirmWithdrawalRequestRequest,
@@ -869,6 +872,30 @@ async function handleRecoverStoredBackup(
return {};
}
+const urlCharRegex = /^[a-zA-Z0-9\-_.~!*'();:@&=+$,/?%#[\]]+$/;
+
+export async function handleCompleteExchangeBaseUrl(
+ wex: WalletExecutionContext,
+ req: CompleteBaseUrlRequest,
+): Promise<CompleteBaseUrlResult> {
+ const trimmedUrl = req.url.trim();
+
+ if (!urlCharRegex.test(trimmedUrl)) {
+ return {
+ status: "bad-syntax",
+ error: {
+ code: TalerErrorCode.WALLET_CORE_API_BAD_REQUEST,
+ },
+ };
+ }
+
+ // FIXME: Do completion via network.
+ return {
+ completion: canonicalizeBaseUrl(trimmedUrl),
+ status: "ok",
+ };
+}
+
async function handleSetWalletRunConfig(
wex: WalletExecutionContext,
req: InitRequest,
@@ -997,6 +1024,17 @@ async function handleAddExchange(
"AddExchangeRequest must either specify uri or exchangeBaseUrl",
);
}
+
+ if (req.allowCompletion) {
+ const completeRes = await handleCompleteExchangeBaseUrl(wex, {
+ url: exchangeBaseUrl,
+ });
+ if (completeRes.status != "ok") {
+ throw TalerError.fromUncheckedDetail(completeRes.error);
+ }
+ exchangeBaseUrl = completeRes.completion;
+ }
+
await fetchFreshExchange(wex, exchangeBaseUrl, {});
// Exchange has been explicitly added upon user request.
// Thus, we mark it as "used".
@@ -1837,6 +1875,10 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
codec: codecForEmptyObject(),
handler: listStoredBackups,
},
+ [WalletApiOperation.CompleteExchangeBaseUrl]: {
+ codec: codecForCompleteBaseUrlRequest(),
+ handler: handleCompleteExchangeBaseUrl,
+ },
[WalletApiOperation.SetWalletRunConfig]: {
codec: codecForInitRequest(),
handler: handleSetWalletRunConfig,