taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 257911ee08b5876d1daf8033ee5f699d99abfca2
parent 597942cfe821911723bece22672a3eef5a4cb131
Author: Florian Dold <florian@dold.me>
Date:   Tue, 21 Jul 2026 10:16:15 +0200

add progressToken retry helper, track retry context in wallet execution context

Diffstat:
Mpackages/taler-util/src/types-taler-wallet.ts | 60++++++++----------------------------------------------------
Mpackages/taler-wallet-core/src/common.ts | 49-------------------------------------------------
Mpackages/taler-wallet-core/src/deposits.ts | 22+++++++++++-----------
Mpackages/taler-wallet-core/src/exchanges.ts | 112+++++++++++++++++++++++--------------------------------------------------------
Mpackages/taler-wallet-core/src/progress.ts | 56+++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/requests.ts | 12+++++-------
Mpackages/taler-wallet-core/src/wallet.ts | 21+++++++--------------
Mpackages/taler-wallet-core/src/withdraw.ts | 22+++++++++++-----------
8 files changed, 129 insertions(+), 225 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -2438,18 +2438,7 @@ export interface GetWithdrawalDetailsForAmountRequest { restrictAge?: number; - /** - * ID provided by the client to cancel the request. - * - * If the same request is made again with the same clientCancellationId, - * all previous requests are cancelled. - * - * The cancelled request will receive an error response with - * an error code that indicates the cancellation. - * - * The cancellation is best-effort, responses might still arrive. - */ - clientCancellationId?: string; + progressToken?: string; } export interface PrepareBankIntegratedWithdrawalRequest { @@ -2516,7 +2505,7 @@ export const codecForGetWithdrawalDetailsForAmountRequest = .property("restrictScope", codecOptional(codecForScopeInfo())) .property("amount", codecForAmountString()) .property("restrictAge", codecOptional(codecForNumber())) - .property("clientCancellationId", codecOptional(codecForString())) + .property("progressToken", codecOptional(codecForString())) .build("GetWithdrawalDetailsForAmountRequest"); export interface AcceptExchangeTosRequest { @@ -3219,18 +3208,7 @@ export interface CheckDepositRequest { */ restrictScope?: ScopeInfo; - /** - * ID provided by the client to cancel the request. - * - * If the same request is made again with the same clientCancellationId, - * all previous requests are cancelled. - * - * The cancelled request will receive an error response with - * an error code that indicates the cancellation. - * - * The cancellation is best-effort, responses might still arrive. - */ - clientCancellationId?: string; + progressToken?: string; } export const codecForCheckDepositRequest = (): Codec<CheckDepositRequest> => @@ -3238,7 +3216,7 @@ export const codecForCheckDepositRequest = (): Codec<CheckDepositRequest> => .property("restrictScope", codecOptional(codecForScopeInfo())) .property("amount", codecForAmountString()) .property("depositPaytoUri", codecForString()) - .property("clientCancellationId", codecOptional(codecForString())) + .property("progressToken", codecOptional(codecForString())) .build("CheckDepositRequest"); export interface CheckDepositResponse { @@ -3505,18 +3483,7 @@ export interface CheckPeerPushDebitRequest { */ restrictScope?: ScopeInfo; - /** - * ID provided by the client to cancel the request. - * - * If the same request is made again with the same clientCancellationId, - * all previous requests are cancelled. - * - * The cancelled request will receive an error response with - * an error code that indicates the cancellation. - * - * The cancellation is best-effort, responses might still arrive. - */ - clientCancellationId?: string; + progressToken?: string; } export const codecForCheckPeerPushDebitRequest = @@ -3525,7 +3492,7 @@ export const codecForCheckPeerPushDebitRequest = .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("restrictScope", codecOptional(codecForScopeInfo())) .property("amount", codecForAmountString()) - .property("clientCancellationId", codecOptional(codecForString())) + .property("progressToken", codecOptional(codecForString())) .build("CheckPeerPushDebitRequest"); export type CheckPeerPushDebitResponse = @@ -3722,18 +3689,7 @@ export interface CheckPeerPullCreditRequest { amount: AmountString; - /** - * ID provided by the client to cancel the request. - * - * If the same request is made again with the same clientCancellationId, - * all previous requests are cancelled. - * - * The cancelled request will receive an error response with - * an error code that indicates the cancellation. - * - * The cancellation is best-effort, responses might still arrive. - */ - clientCancellationId?: string; + progressToken?: string; } export const codecForPreparePeerPullPaymentRequest = @@ -3742,7 +3698,7 @@ export const codecForPreparePeerPullPaymentRequest = .property("amount", codecForAmountString()) .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("restrictScope", codecOptional(codecForScopeInfo())) - .property("clientCancellationId", codecOptional(codecForString())) + .property("progressToken", codecOptional(codecForString())) .build("CheckPeerPullCreditRequest"); export interface CheckPeerPullCreditResponse { diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -1047,55 +1047,6 @@ export function requireExchangeTosAcceptedOrThrow( } /** - * Run a request, but cancel the wallet execution context as soon as the client - * submits another request of the same type. - */ -export async function runWithClientCancellation<R, T>( - wex: WalletExecutionContext, - operation: string, - clientCancellationId: string | undefined, - handler: () => Promise<T>, -): Promise<T> { - const clientCancelKey = clientCancellationId - ? `ccid:${operation}:${clientCancellationId}` - : undefined; - const cts = wex.cts; - if (clientCancelKey && cts) { - const prevCts = wex.ws.clientCancellationMap.get(clientCancelKey); - if (prevCts) { - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: `Cancelling previous key ${clientCancelKey}`, - }); - prevCts.cancel( - `cancelled by subsequent request with same cancellation ID`, - ); - } else { - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: `No previous key ${clientCancelKey}`, - }); - } - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: `Setting clientCancelKey ${clientCancelKey} to ${cts}`, - }); - wex.ws.clientCancellationMap.set(clientCancelKey, cts); - } - try { - return await handler(); - } finally { - wex.oc.observe({ - type: ObservabilityEventType.Message, - contents: `Deleting clientCancelKey ${clientCancelKey} to ${cts}`, - }); - if (clientCancelKey && wex.cts && !wex.cts.token.isCancelled) { - wex.ws.clientCancellationMap.delete(clientCancelKey); - } - } -} - -/** * Run a queued longpool fetch with cancellation token and timeout_ms */ export async function cancelableLongPoll( diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -47,7 +47,9 @@ import { MerchantContractTermsV0, MerchantContractVersion, NotificationType, + Paytos, RefreshReason, + Result, SelectedProspectiveCoin, TalerError, TalerErrorCode, @@ -77,8 +79,6 @@ import { getRandomBytes, hashWire, j2s, - Paytos, - Result, } from "@gnu-taler/taler-util"; import { readResponseJsonOrThrow, @@ -104,25 +104,25 @@ import { genericWaitForState, getGenericRecordHandle, prepareTransferOptionsRaw, - runWithClientCancellation, spendCoins, } from "./common.js"; import { DepositElementStatus, DepositOperationStatus, RefreshOperationStatus, + WalletDepositGroup, + WalletDepositInfoPerExchange, + WalletDepositTrackingInfo, + WalletWithdrawalGroup, + WithdrawalRecordType, timestampAbsoluteFromDb, timestampPreciseFromDb, timestampPreciseToDb, timestampProtocolFromDb, timestampProtocolToDb, - WalletDepositGroup, - WalletDepositTrackingInfo, - WalletDepositInfoPerExchange, - WalletWithdrawalGroup, - WithdrawalRecordType, } from "./db-common.js"; import {} from "./db-indexeddb.js"; +import { WalletDbTransaction } from "./dbtx.js"; import { ReadyExchangeSummary, fetchFreshExchange, @@ -146,6 +146,7 @@ import { generateDepositPermissions, getTotalPaymentCost, } from "./pay-merchant.js"; +import { runWithMaybeProgressContext } from "./progress.js"; import { RefreshTransactionContext, createRefreshGroup, @@ -160,7 +161,6 @@ import { } from "./transactions.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; import { augmentPaytoUrisForKycTransfer } from "./withdraw.js"; -import { WalletDbTransaction } from "./dbtx.js"; /** * Logger. @@ -2001,10 +2001,10 @@ export async function checkDepositGroup( wex: WalletExecutionContext, req: CheckDepositRequest, ): Promise<CheckDepositResponse> { - return await runWithClientCancellation( + return await runWithMaybeProgressContext( wex, "checkDepositGroup", - req.clientCancellationId, + req.progressToken, () => internalCheckDepositGroup(wex, req), ); } diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -109,6 +109,7 @@ import { makeErrorDetail, makeTalerErrorDetail, stringifyScopeInfo, + succeedOrThrow, } from "@gnu-taler/taler-util"; import { HttpRequestLibrary, @@ -133,7 +134,6 @@ import { getExchangeState, getExchangeTosStatusFromRecord, getExchangeUpdateStatusFromRecord, - getRetryDuration, reservePaytoFromExchange, } from "./common.js"; import { @@ -142,21 +142,22 @@ import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, ReserveRecordStatus, + WalletDenomFamilyParams, + WalletDenomLossEvent, + WalletDenomination, + WalletDenominationFamily, + WalletExchangeDetails, + WalletExchangeEntry, + WalletReserve, timestampAbsoluteFromDb, timestampOptionalPreciseFromDb, timestampPreciseFromDb, timestampPreciseToDb, timestampProtocolFromDb, timestampProtocolToDb, - WalletDenomination, - WalletReserve, - WalletExchangeDetails, - WalletExchangeEntry, - WalletDenomLossEvent, - WalletDenomFamilyParams, - WalletDenominationFamily, } from "./db-common.js"; import { ExchangeMigrationReason } from "./db-indexeddb.js"; +import { WalletDbTransaction } from "./dbtx.js"; import { createTimeline, isCandidateWithdrawableDenomRec, @@ -172,7 +173,7 @@ import { PeerPullCreditTransactionContext } from "./pay-peer-pull-credit.js"; import { PeerPullDebitTransactionContext } from "./pay-peer-pull-debit.js"; import { PeerPushCreditTransactionContext } from "./pay-peer-push-credit.js"; import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js"; -import { ProgressContext } from "./progress.js"; +import { ProgressContext, runWithProgressRetries } from "./progress.js"; import { RecoupTransactionContext, createRecoupGroup } from "./recoup.js"; import { RefreshTransactionContext, createRefreshGroup } from "./refresh.js"; import { @@ -191,7 +192,6 @@ import { WithdrawTransactionContext, updateWithdrawalDenomsForExchange, } from "./withdraw.js"; -import { WalletDbTransaction } from "./dbtx.js"; const logger = new Logger("exchanges.ts"); @@ -1246,12 +1246,11 @@ export async function fetchFreshExchange( options: { forceUpdate?: boolean; noBail?: boolean; - progressContext?: ProgressContext; } = {}, ): Promise<ReadyExchangeSummary> { logger.trace(`fetch fresh ${baseUrl} forced ${options.forceUpdate}`); - if (options.progressContext != null && !options.noBail) { + if (wex.progressContext != null && !options.noBail) { // If there is a progress context, we usually want to keep // retrying and not bail out early. logger.warn( @@ -2710,20 +2709,18 @@ export async function getExchangeTos( exchangeBaseUrl: string, acceptedFormat?: string[], acceptLanguage?: string, - progressContext?: ProgressContext, ): Promise<GetExchangeTosResult> { - if (progressContext) { - progressContext.onRetryNow = async () => { + if (wex.progressContext) { + wex.progressContext.onRetryNow = async () => { await resetExchangeRetries(wex, exchangeBaseUrl); }; } const exch = await fetchFreshExchange(wex, exchangeBaseUrl, { - progressContext, - noBail: progressContext != null, + noBail: wex.progressContext != null, }); - if (progressContext) { - delete progressContext.onRetryNow; + if (wex.progressContext) { + delete wex.progressContext.onRetryNow; } switch (exch.tosStatus) { @@ -2744,72 +2741,27 @@ export async function getExchangeTos( httpClient: wex.http, }); - let tosDownload: ExchangeTosDownloadResult | undefined; - - // FIXME: pull out retry logic into helper. - - let retryCount = 0; - - while (1) { - if (progressContext?.cts?.token.isCancelled || progressContext?.finished) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_CORE_REQUEST_CANCELLED, - {}, - ); - } - const cts = CancellationToken.create(); - if (progressContext != null) { - progressContext.onRetryNow = async () => { - cts.cancel(); - retryCount = 0; - }; - } - try { + let tosDownload: ExchangeTosDownloadResult = await runWithProgressRetries( + wex, + async () => { const tosRes = await exchangeClient.getTermsText({ acceptFormats: acceptedFormat, acceptLanguage: acceptLanguage, }); + succeedOrThrow(tosRes); - switch (tosRes.type) { - case "ok": - tosDownload = { - tosAvailableLanguages: - tosRes.response.headers.get("avail-languages")?.split(",") ?? [], - tosContentLanguage: - tosRes.response.headers.get("content-language") ?? "en", - tosContentType: - tosRes.response.headers.get("content-type") ?? "text/plain", - tosEtag: tosRes.response.headers.get("etag") ?? "", - tosText: tosRes.body.text, - }; - break; - default: - continue; - } - } catch (e) { - if (!progressContext) { - throw e; - } - const delay = getRetryDuration(retryCount); - wex.ws.notify({ - type: NotificationType.RequestProgressError, - operation: progressContext.operation, - progressToken: progressContext.progressToken, - error: TalerError.fromException(e).errorDetail, - nextRetryDelay: Duration.toTalerProtocolDuration(delay), - retryCounter: retryCount, - }); - retryCount++; - const didWait = await wex.ws.timerGroup.resolveAfter(delay, cts.token); - if (!didWait) { - logger.info(`timeout cancelled, retrying immediately`); - } - } - - if (tosDownload != null) { - break; - } - } + return { + tosAvailableLanguages: + tosRes.response.headers.get("avail-languages")?.split(",") ?? [], + tosContentLanguage: + tosRes.response.headers.get("content-language") ?? "en", + tosContentType: + tosRes.response.headers.get("content-type") ?? "text/plain", + tosEtag: tosRes.response.headers.get("etag") ?? "", + tosText: tosRes.body.text, + }; + }, + ); checkLogicInvariant(!!tosDownload); diff --git a/packages/taler-wallet-core/src/progress.ts b/packages/taler-wallet-core/src/progress.ts @@ -22,7 +22,10 @@ import { Logger, NotificationType, safeStringifyException, + TalerError, + TalerErrorCode, } from "@gnu-taler/taler-util"; +import { getRetryDuration } from "./common.js"; import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("progress.ts"); @@ -57,7 +60,7 @@ function getProgressKey(args: { let progressContextCounter = 1; -export async function withMaybeProgressContext<T>( +export async function runWithMaybeProgressContext<T>( wex: WalletExecutionContext, op: string, tok: string | undefined, @@ -186,3 +189,54 @@ export async function handleRetryProgressTokenNow( }); return {}; } + +/** + * Run with retries if there is a progress context. + * Otherwise, just try once. + */ +export async function runWithProgressRetries<T>( + wex: WalletExecutionContext, + f: () => Promise<T>, +): Promise<T> { + let retryCount = 0; + while (1) { + if ( + wex.progressContext?.cts?.token.isCancelled || + wex.progressContext?.finished + ) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_CORE_REQUEST_CANCELLED, + {}, + ); + } + const cts = CancellationToken.create(); + if (wex.progressContext != null) { + wex.progressContext.onRetryNow = async () => { + cts.cancel(); + retryCount = 0; + }; + } + try { + return f(); + } catch (e) { + if (!wex.progressContext) { + throw e; + } + const delay = getRetryDuration(retryCount); + wex.ws.notify({ + type: NotificationType.RequestProgressError, + operation: wex.progressContext.operation, + progressToken: wex.progressContext.progressToken, + error: TalerError.fromException(e).errorDetail, + nextRetryDelay: Duration.toTalerProtocolDuration(delay), + retryCounter: retryCount, + }); + retryCount++; + const didWait = await wex.ws.timerGroup.resolveAfter(delay, cts.token); + if (!didWait) { + logger.info(`timeout cancelled, retrying immediately`); + } + } + } + throw Error("not reached"); +} diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts @@ -23,7 +23,6 @@ /** * Imports. */ -import { IdbWalletDbHandle } from "./dbtx-handle-impl.js"; import { AbortTransactionRequest, AcceptBankIntegratedWithdrawalRequest, @@ -282,6 +281,7 @@ import { openStoredBackupsDatabase, walletDbFixups, } from "./db-indexeddb.js"; +import { IdbWalletDbHandle } from "./dbtx-handle-impl.js"; import { isCandidateWithdrawableDenomRec, isWithdrawableDenom, @@ -356,7 +356,7 @@ import { fillDefaults } from "./preset-exchanges.js"; import { handleCancelProgressToken, handleRetryProgressTokenNow, - withMaybeProgressContext, + runWithMaybeProgressContext, } from "./progress.js"; import { forceRefresh } from "./refresh.js"; import { convertTaskToTransactionId, getActiveTaskIds } from "./shepherd.js"; @@ -428,10 +428,10 @@ import { */ import { + WalletExecutionContext, applyRunConfigDefaults, getDenomInfo, migrateMaterializedTransactions, - WalletExecutionContext, } from "./wallet.js"; const logger = new Logger("requests.ts"); @@ -712,7 +712,7 @@ async function handlePrepareWithdrawExchange( } const exchangeBaseUrl = parsedUri.exchangeBaseUrl; - return await withMaybeProgressContext( + return await runWithMaybeProgressContext( wex, "prepareWithdrawExchange", req.progressToken, @@ -724,7 +724,6 @@ async function handlePrepareWithdrawExchange( } const exchange = await fetchFreshExchange(wex, exchangeBaseUrl, { noBail: pc != null, - progressContext: pc, }); if (parsedUri.amount) { const amt = Amounts.parseOrThrow(parsedUri.amount); @@ -1069,7 +1068,7 @@ async function handleGetExchangeTos( wex: WalletExecutionContext, req: GetExchangeTosRequest, ): Promise<GetExchangeTosResult> { - return await withMaybeProgressContext( + return await runWithMaybeProgressContext( wex, "getExchangeTos", req.progressToken, @@ -1079,7 +1078,6 @@ async function handleGetExchangeTos( req.exchangeBaseUrl, req.acceptedFormat, req.acceptLanguage, - pc, ); }, ); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -23,14 +23,13 @@ /** * Imports. */ -import { AccessStats, BridgeIDBFactory } from "@gnu-taler/idb-bridge"; import { AmountJson, AsyncCondition, + Cache, CancellationToken, CoreApiResponse, DenominationInfo, - Cache, Duration, FlightRecordEntry, FlightRecordEvent, @@ -62,19 +61,16 @@ import { safeStringifyException, } from "@gnu-taler/taler-util"; import { type HttpRequestLibrary } from "@gnu-taler/taler-util/http"; -import { dispatchRequestInternal } from "./requests.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { CryptoDispatcher, CryptoWorkerFactory, } from "./crypto/workers/crypto-dispatcher.js"; import { ConfigRecordKey, WalletDenomination } from "./db-common.js"; -import { WalletDbTransaction } from "./dbtx.js"; -import { TransactionAbortedError } from "./query.js"; +import { IdbWalletDbHandle } from "./dbtx-handle-impl.js"; import { WalletDbHandle } from "./dbtx-handle.js"; -import { IdbWalletDbHandle, SqliteWalletDbHandle } from "./dbtx-handle-impl.js"; import { watchForCacheInvalidation } from "./dbtx-shared.js"; -import { NativeSqliteWalletDb } from "./dbtx-sqlite.js"; +import { WalletDbTransaction } from "./dbtx.js"; import { UnverifiedDenomError } from "./denomSelection.js"; import { DevExperimentHttpLib, DevExperimentState } from "./dev-experiments.js"; import { @@ -88,6 +84,8 @@ import { observeTalerCrypto, } from "./observable-wrappers.js"; import { ProgressContext } from "./progress.js"; +import { TransactionAbortedError } from "./query.js"; +import { dispatchRequestInternal } from "./requests.js"; import { TaskScheduler, TaskSchedulerImpl } from "./shepherd.js"; import { rematerializeTransactions } from "./transactions.js"; import { @@ -123,6 +121,7 @@ export interface WalletExecutionContext { readonly cts: CancellationToken.Source | undefined; readonly taskScheduler: TaskScheduler; readonly dbRetryState: DbRetryState; + progressContext?: ProgressContext; } export interface DbRetryState { @@ -600,12 +599,7 @@ export class Wallet { timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, ): Promise<Wallet> { - const w = new Wallet( - dbHandle, - httpFactory, - timer, - cryptoWorkerFactory, - ); + const w = new Wallet(dbHandle, httpFactory, timer, cryptoWorkerFactory); w._client = await getClientFromWalletState(w.ws); return w; } @@ -669,7 +663,6 @@ export class InternalWalletState { private _config: Readonly<WalletRunConfig> | undefined; - private _http: HttpRequestLibrary | undefined = undefined; devExperimentState: DevExperimentState = {}; diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -133,26 +133,26 @@ import { makeCoinsVisible, prepareTransferOptionsRaw, requireExchangeTosAcceptedOrThrow, - runWithClientCancellation, } from "./common.js"; import { EddsaKeyPairStrings } from "./crypto/cryptoImplementation.js"; import { DenominationVerificationStatus, PlanchetStatus, - WithdrawalGroupStatus, - timestampAbsoluteFromDb, - timestampPreciseFromDb, - timestampPreciseToDb, - timestampProtocolToDb, + WalletCoin, WalletDenomination, WalletOperationRetry, - WalletCoin, WalletPlanchet, - WgInfo, WalletWithdrawalGroup, + WgInfo, + WithdrawalGroupStatus, WithdrawalRecordType, + timestampAbsoluteFromDb, + timestampPreciseFromDb, + timestampPreciseToDb, + timestampProtocolToDb, } from "./db-common.js"; import { CoinSourceType } from "./db-indexeddb.js"; +import { WalletDbTransaction } from "./dbtx.js"; import { selectForcedWithdrawalDenominations, selectWithdrawalDenominations, @@ -185,6 +185,7 @@ import { isKycOperationDue, runKycCheckAlgo, } from "./kyc.js"; +import { runWithMaybeProgressContext } from "./progress.js"; import { constructTransactionIdentifier, isUnsuccessfulTransaction, @@ -192,7 +193,6 @@ import { } from "./transactions.js"; import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; -import { WalletDbTransaction } from "./dbtx.js"; /** * Logger for this file. @@ -4342,10 +4342,10 @@ export async function getWithdrawalDetailsForAmount( wex: WalletExecutionContext, req: GetWithdrawalDetailsForAmountRequest, ): Promise<WithdrawalDetailsForAmount> { - return runWithClientCancellation( + return runWithMaybeProgressContext( wex, "getWithdrawalDetailsForAmount", - req.clientCancellationId, + req.progressToken, async () => internalGetWithdrawalDetailsForAmount(wex, req), ); }