commit 68ab975d0acb3adcd348d5dafb967686c9e7bf15
parent 1242672fd2ac96e783b891d7823b4e5880d2a99c
Author: Florian Dold <dold@taler.net>
Date: Sun, 19 Jul 2026 23:12:12 +0200
wallet: move Cache to taler-util and API handlers to requests.ts
wallet.ts keeps the lifecycle, execution context and DB plumbing.
Diffstat:
5 files changed, 2883 insertions(+), 2805 deletions(-)
diff --git a/packages/taler-util/src/cache.ts b/packages/taler-util/src/cache.ts
@@ -0,0 +1,80 @@
+/*
+ This file is part of GNU Taler
+ (C) 2026 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * In-memory cache with a per-entry expiry and a capacity bound.
+ */
+
+import { AbsoluteTime, Duration } from "./time.js";
+
+export class Cache<T> {
+ private map: Map<string, [AbsoluteTime, T]> = new Map();
+
+ constructor(
+ private maxCapacity: number,
+ private cacheDuration: Duration,
+ ) {}
+
+ get(key: string): T | undefined {
+ const r = this.map.get(key);
+ if (!r) {
+ return undefined;
+ }
+
+ if (AbsoluteTime.isExpired(r[0])) {
+ this.map.delete(key);
+ return undefined;
+ }
+
+ return r[1];
+ }
+
+ async getOrPut(key: string, lambda: () => Promise<T>): Promise<T>;
+ async getOrPut(
+ key: string,
+ lambda: () => Promise<T | undefined>,
+ ): Promise<T | undefined>;
+ async getOrPut(
+ key: string,
+ lambda: () => Promise<T | undefined>,
+ ): Promise<T | undefined> {
+ const cached = this.get(key);
+ if (cached != null) {
+ return cached;
+ } else {
+ const computed = await lambda();
+ if (computed != null) {
+ this.put(key, computed);
+ }
+ return computed;
+ }
+ }
+
+ clear(): void {
+ this.map.clear();
+ }
+
+ put(key: string, value: T): void {
+ if (this.map.size > this.maxCapacity) {
+ this.map.clear();
+ }
+ const expiry = AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ this.cacheDuration,
+ );
+ this.map.set(key, [expiry, value]);
+ }
+}
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
@@ -4,6 +4,7 @@ export * from "./base64.js";
export * from "./bech32.js";
export * from "./bitcoin.js";
export * from "./CancellationToken.js";
+export * from "./cache.js";
export * from "./codec.js";
export * from "./contract-terms.js";
export * from "./errors.js";
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -78,10 +78,10 @@ import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js";
import { RefreshTransactionContext } from "./refresh.js";
import { rematerializeTransactions } from "./transactions.js";
import {
- WalletExecutionContext,
handleAddGlobalCurrencyExchange,
handleRemoveGlobalCurrencyExchange,
-} from "./wallet.js";
+} from "./requests.js";
+import { WalletExecutionContext } from "./wallet.js";
import { WithdrawTransactionContext } from "./withdraw.js";
const logger = new Logger("dev-experiments.ts");
diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts
@@ -0,0 +1,2655 @@
+/*
+ This file is part of GNU Taler
+ (C) 2015-2019 GNUnet e.V.
+ (C) 2025 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * High-level wallet operations that should be independent from the underlying
+ * browser extension interface.
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AbortTransactionRequest,
+ AcceptBankIntegratedWithdrawalRequest,
+ AcceptManualWithdrawalRequest,
+ AcceptManualWithdrawalResult,
+ AcceptWithdrawalResponse,
+ ActiveTask,
+ AddBankAccountRequest,
+ AddBankAccountResponse,
+ AddExchangeRequest,
+ AddExchangeResponse,
+ AddGlobalCurrencyAuditorRequest,
+ AddGlobalCurrencyExchangeRequest,
+ AmountString,
+ Amounts,
+ CanonicalizeBaseUrlRequest,
+ CanonicalizeBaseUrlResponse,
+ Codec,
+ CoinDumpJson,
+ CoinStatus,
+ CompleteBaseUrlRequest,
+ CompleteBaseUrlResult,
+ ConfirmPayRequest,
+ ConfirmPayResult,
+ ConvertIbanAccountFieldToPaytoRequest,
+ ConvertIbanAccountFieldToPaytoResponse,
+ ConvertIbanPaytoToAccountFieldRequest,
+ ConvertIbanPaytoToAccountFieldResponse,
+ CreateStoredBackupResponse,
+ DeleteDiscountRequest,
+ DeleteExchangeRequest,
+ DeleteStoredBackupRequest,
+ DeleteSubscriptionRequest,
+ EmptyObject,
+ ExchangeEntryStatus,
+ ExportDbToFileRequest,
+ ExportDbToFileResponse,
+ FailTransactionRequest,
+ ForgetBankAccountRequest,
+ GetActiveTasksResponse,
+ GetBankAccountByIdRequest,
+ GetBankAccountByIdResponse,
+ GetBankingChoicesForPaytoRequest,
+ GetBankingChoicesForPaytoResponse,
+ GetChoicesForPaymentRequest,
+ GetChoicesForPaymentResult,
+ GetCurrencySpecificationRequest,
+ GetCurrencySpecificationResponse,
+ GetDefaultExchangesResponse,
+ GetDepositWireTypesForCurrencyRequest,
+ GetDepositWireTypesForCurrencyResponse,
+ GetDepositWireTypesRequest,
+ GetDepositWireTypesResponse,
+ GetExchangeTosRequest,
+ GetExchangeTosResult,
+ GetPerformanceStatsRequest,
+ GetPerformanceStatsResponse,
+ GetQrCodesForPaytoRequest,
+ GetQrCodesForPaytoResponse,
+ HintNetworkAvailabilityRequest,
+ HostPortPath,
+ ImportDbFromFileRequest,
+ ImportDbRequest,
+ InitRequest,
+ InitResponse,
+ IntegrationTestArgs,
+ IntegrationTestV2Args,
+ ListBankAccountsRequest,
+ ListBankAccountsResponse,
+ ListDiscountsRequest,
+ ListDiscountsResponse,
+ ListGlobalCurrencyAuditorsResponse,
+ ListGlobalCurrencyExchangesResponse,
+ ListSubscriptionsRequest,
+ ListSubscriptionsResponse,
+ Logger,
+ NotificationType,
+ PaytoType,
+ Paytos,
+ PerformanceTable,
+ PrepareWithdrawExchangeRequest,
+ PrepareWithdrawExchangeResponse,
+ RecoverStoredBackupRequest,
+ RemoveGlobalCurrencyAuditorRequest,
+ RemoveGlobalCurrencyExchangeRequest,
+ Result,
+ RunFixupRequest,
+ ScopeType,
+ SharePaymentRequest,
+ SharePaymentResult,
+ StartRefundQueryRequest,
+ StoredBackupList,
+ SuspendTransactionRequest,
+ TalerBankIntegrationHttpClient,
+ TalerError,
+ TalerErrorCode,
+ TalerProtocolTimestamp,
+ TalerUriAction,
+ TalerUris,
+ TestingCorruptWithdrawalCoinSelRequest,
+ TestingGetDenomStatsRequest,
+ TestingGetDenomStatsResponse,
+ TestingGetDiagnosticsResponse,
+ TestingGetFlightRecordsResponse,
+ TestingGetReserveHistoryRequest,
+ TestingSetTimetravelRequest,
+ TestingWaitBalanceRequest,
+ TestingWaitExchangeReadyRequest,
+ TransactionType,
+ TransactionsResponse,
+ UpdateExchangeEntryRequest,
+ ValidateIbanRequest,
+ ValidateIbanResponse,
+ WalletBankAccountInfo,
+ WalletCoreVersion,
+ WireTypeDetails,
+ WithdrawTestBalanceRequest,
+ canonicalizeBaseUrl,
+ checkDbInvariant,
+ codecForAbortTransaction,
+ codecForAcceptBankIntegratedWithdrawalRequest,
+ codecForAcceptExchangeTosRequest,
+ codecForAcceptManualWithdrawalRequest,
+ codecForAcceptPeerPullPaymentRequest,
+ codecForAddBankAccountRequest,
+ codecForAddContactRequest,
+ codecForAddExchangeRequest,
+ codecForAddGlobalCurrencyAuditorRequest,
+ codecForAddGlobalCurrencyExchangeRequest,
+ codecForAddMailboxMessageRequest,
+ codecForAny,
+ codecForApplyDevExperiment,
+ codecForCancelProgressToken,
+ codecForCanonicalizeBaseUrlRequest,
+ codecForCheckDepositRequest,
+ codecForCheckPayTemplateRequest,
+ codecForCheckPeerPullPaymentRequest,
+ codecForCheckPeerPushDebitRequest,
+ codecForCompleteBaseUrlRequest,
+ codecForConfirmPayRequest,
+ codecForConfirmPeerPushPaymentRequest,
+ codecForConfirmWithdrawalRequestRequest,
+ codecForConvertAmountRequest,
+ codecForConvertIbanAccountFieldToPaytoRequest,
+ codecForConvertIbanPaytoToAccountFieldRequest,
+ codecForCreateDepositGroupRequest,
+ codecForDeleteContactRequest,
+ codecForDeleteDiscountRequest,
+ codecForDeleteExchangeRequest,
+ codecForDeleteMailboxMessageRequest,
+ codecForDeleteStoredBackupRequest,
+ codecForDeleteSubscriptionRequest,
+ codecForDeleteTransactionRequest,
+ codecForEmptyObject,
+ codecForExportDbToFileRequest,
+ codecForFailTransactionRequest,
+ codecForForceRefreshRequest,
+ codecForForgetBankAccount,
+ codecForGetBalanceDetailRequest,
+ codecForGetBankAccountByIdRequest,
+ codecForGetBankingChoicesForPaytoRequest,
+ codecForGetChoicesForPaymentRequest,
+ codecForGetCurrencyInfoRequest,
+ codecForGetDepositWireTypesForCurrencyRequest,
+ codecForGetDepositWireTypesRequest,
+ codecForGetDonauStatementsRequest,
+ codecForGetExchangeEntryByUrlRequest,
+ codecForGetExchangeResourcesRequest,
+ codecForGetExchangeTosRequest,
+ codecForGetMaxDepositAmountRequest,
+ codecForGetMaxPeerPushDebitAmountRequest,
+ codecForGetPerformanceStatsRequest,
+ codecForGetQrCodesForPaytoRequest,
+ codecForGetTransactionsV2Request,
+ codecForGetWithdrawalDetailsForAmountRequest,
+ codecForGetWithdrawalDetailsForUri,
+ codecForHintNetworkAvailabilityRequest,
+ codecForImportDbFromFileRequest,
+ codecForImportDbRequest,
+ codecForInitRequest,
+ codecForInitiatePeerPullPaymentRequest,
+ codecForInitiatePeerPushDebitRequest,
+ codecForIntegrationTestArgs,
+ codecForIntegrationTestV2Args,
+ codecForListBankAccounts,
+ codecForListDiscountsRequest,
+ codecForListExchangesRequest,
+ codecForListSubscriptionsRequest,
+ codecForMailboxBaseUrl,
+ codecForMailboxConfiguration,
+ codecForPrepareBankIntegratedWithdrawalRequest,
+ codecForPreparePayRequest,
+ codecForPreparePayTemplateRequest,
+ codecForPreparePeerPullPaymentRequest,
+ codecForPreparePeerPushCreditRequest,
+ codecForPrepareRefundRequest,
+ codecForPrepareWithdrawExchangeRequest,
+ codecForRecoverStoredBackupRequest,
+ codecForRemoveGlobalCurrencyAuditorRequest,
+ codecForRemoveGlobalCurrencyExchangeRequest,
+ codecForResumeTransaction,
+ codecForRetryProgressTokenNowRequest,
+ codecForRetryTransactionRequest,
+ codecForRunFixupRequest,
+ codecForSendTalerUriMailboxMessageRequest,
+ codecForSetCoinSuspendedRequest,
+ codecForSetDonauRequest,
+ codecForSharePaymentRequest,
+ codecForStartExchangeWalletKycRequest,
+ codecForStartRefundQueryRequest,
+ codecForSuspendTransaction,
+ codecForTaldirLookupRequest,
+ codecForTaldirRegistrationCompletionRequest,
+ codecForTaldirRegistrationRequest,
+ codecForTestPayArgs,
+ codecForTestingCorruptWithdrawalCoinSelRequest,
+ codecForTestingGetDenomStatsRequest,
+ codecForTestingGetReserveHistoryRequest,
+ codecForTestingPlanMigrateExchangeBaseUrlRequest,
+ codecForTestingSetTimetravelRequest,
+ codecForTestingWaitBalanceRequest,
+ codecForTestingWaitExchangeReadyRequest,
+ codecForTestingWaitWalletKycRequest,
+ codecForTransactionByIdRequest,
+ codecForTransactionsRequest,
+ codecForUpdateExchangeEntryRequest,
+ codecForValidateIbanRequest,
+ codecForWithdrawTestBalance,
+ convertCHF_BBANtoIBAN,
+ convertHUF_BBANtoIBAN,
+ encodeCrock,
+ getErrorDetailFromException,
+ getQrCodesForPayto,
+ getRandomBytes,
+ j2s,
+ parseIban,
+ setDangerousTimetravel,
+ setGlobalLogLevelFromString,
+ validateIban,
+} from "@gnu-taler/taler-util";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import { getBalanceDetail, getBalances } from "./balance.js";
+import {
+ getMaxDepositAmount,
+ getMaxPeerPushDebitAmount,
+} from "./coinSelection.js";
+import { cancelableFetch } from "./common.js";
+import { addContact, deleteContact, listContacts } from "./contacts.js";
+import {
+ ConfigRecordKey,
+ timestampAbsoluteFromDb,
+ timestampProtocolToDb,
+} from "./db-common.js";
+import {
+ CoinSourceType,
+ clearDatabase,
+ exportDb,
+ importDb,
+ openStoredBackupsDatabase,
+ walletDbFixups,
+} from "./db-indexeddb.js";
+import {
+ isCandidateWithdrawableDenomRec,
+ isWithdrawableDenom,
+} from "./denominations.js";
+import { checkDepositGroup, createDepositGroup } from "./deposits.js";
+import { applyDevExperiment } from "./dev-experiments.js";
+import {
+ handleGetDonau,
+ handleGetDonauStatements,
+ handleSetDonau,
+} from "./donau.js";
+import {
+ acceptExchangeTermsOfService,
+ deleteExchange,
+ fetchFreshExchange,
+ forgetExchangeTermsOfService,
+ getExchangeDetailedInfo,
+ getExchangeDetailsInTx,
+ getExchangeResources,
+ getExchangeTos,
+ handleStartExchangeWalletKyc,
+ handleTestingPlanMigrateExchangeBaseUrl,
+ handleTestingWaitExchangeState,
+ handleTestingWaitExchangeWalletKyc,
+ listExchanges,
+ lookupExchangeByUri,
+ markExchangeUsed,
+ resetExchangeRetries,
+ startUpdateExchangeEntry,
+ waitReadyExchange,
+} from "./exchanges.js";
+import { convertDepositAmount } from "./instructedAmountConversion.js";
+import {
+ addMailboxMessage,
+ createNewMailbox,
+ deleteMailboxMessage,
+ getMailbox,
+ listMailboxMessages,
+ refreshMailbox,
+ sendTalerUriMessage,
+} from "./mailbox.js";
+import {
+ confirmPay,
+ getChoicesForPayment,
+ preparePayForTemplate,
+ preparePayForTemplateV2,
+ preparePayForUri,
+ preparePayForUriV2,
+ sharePayment,
+ startQueryRefund,
+ startRefundQueryForUri,
+} from "./pay-merchant.js";
+import {
+ checkPeerPullCredit,
+ initiatePeerPullPayment,
+} from "./pay-peer-pull-credit.js";
+import {
+ confirmPeerPullDebit,
+ preparePeerPullDebit,
+} from "./pay-peer-pull-debit.js";
+import {
+ confirmPeerPushCredit,
+ preparePeerPushCredit,
+} from "./pay-peer-push-credit.js";
+import {
+ checkPeerPushDebit,
+ checkPeerPushDebitV2,
+ initiatePeerPushDebit,
+} from "./pay-peer-push-debit.js";
+import { checkPayForTemplate } from "./pay-template.js";
+import { fillDefaults } from "./preset-exchanges.js";
+import {
+ handleCancelProgressToken,
+ handleRetryProgressTokenNow,
+ withMaybeProgressContext,
+} from "./progress.js";
+import { forceRefresh } from "./refresh.js";
+import { convertTaskToTransactionId, getActiveTaskIds } from "./shepherd.js";
+import {
+ completeAliasRegistration,
+ lookupAlias,
+ registerAlias,
+} from "./taldir.js";
+import {
+ WithdrawTestBalanceResult,
+ runIntegrationTest,
+ runIntegrationTest2,
+ testPay,
+ testingWaitBalance,
+ waitTasksDone,
+ waitTransactionState,
+ waitUntilAllTransactionsFinal,
+ waitUntilRefreshesDone,
+ withdrawTestBalance,
+} from "./testing.js";
+import {
+ deleteDiscount,
+ deleteSubscription,
+ listDiscounts,
+ listSubscriptions,
+} from "./tokenFamilies.js";
+import {
+ abortTransaction,
+ deleteTransaction,
+ failTransaction,
+ getTransactionById,
+ getTransactions,
+ getTransactionsV2,
+ parseTransactionIdentifier,
+ rematerializeTransactions,
+ restartAll as restartAllRunningTasks,
+ resumeTransaction,
+ retryAll,
+ retryTransaction,
+ suspendTransaction,
+} from "./transactions.js";
+import {
+ WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION,
+ WALLET_COREBANK_API_PROTOCOL_VERSION,
+ WALLET_CORE_API_PROTOCOL_VERSION,
+ WALLET_EXCHANGE_PROTOCOL_VERSION,
+ WALLET_MERCHANT_PROTOCOL_VERSION,
+} from "./versions.js";
+import {
+ WalletApiOperation,
+ WalletCoreRequestType,
+ WalletCoreResponseType,
+} from "./wallet-api-types.js";
+import {
+ acceptBankIntegratedWithdrawal,
+ confirmWithdrawal,
+ createManualWithdrawal,
+ getWithdrawalDetailsForAmount,
+ getWithdrawalDetailsForUri,
+ prepareBankIntegratedWithdrawal,
+} from "./withdraw.js";
+
+/**
+ * Request handlers for the wallet-core API, and the table that dispatches to
+ * them.
+ *
+ * Split out of wallet.ts, which keeps the wallet lifecycle, the execution
+ * context and the database plumbing these handlers run on top of.
+ */
+
+import {
+ applyRunConfigDefaults,
+ getDenomInfo,
+ migrateMaterializedTransactions,
+ WalletExecutionContext,
+} from "./wallet.js";
+
+const logger = new Logger("requests.ts");
+
+/**
+ * List bank accounts known to the wallet from
+ * previous withdrawals.
+ */
+async function handleListBankAccounts(
+ wex: WalletExecutionContext,
+ req: ListBankAccountsRequest,
+): Promise<ListBankAccountsResponse> {
+ const accounts: WalletBankAccountInfo[] = [];
+ const currency = req.currency;
+ await wex.runWalletDbTx(async (tx) => {
+ const knownAccounts = await tx.listBankAccounts();
+ for (const r of knownAccounts) {
+ if (currency && r.currencies && !r.currencies.includes(currency)) {
+ continue;
+ }
+ const payto = Result.orUndefined(Paytos.fromString(r.paytoUri));
+ if (payto) {
+ accounts.push({
+ bankAccountId: r.bankAccountId,
+ paytoUri: r.paytoUri,
+ label: r.label,
+ kycCompleted: r.kycCompleted,
+ currencies: r.currencies,
+ });
+ }
+ }
+ });
+ return { accounts };
+}
+
+async function handleGetBankAccountById(
+ wex: WalletExecutionContext,
+ req: GetBankAccountByIdRequest,
+): Promise<GetBankAccountByIdResponse> {
+ const acct = await wex.runWalletDbTx(async (tx) => {
+ return tx.getBankAccount(req.bankAccountId);
+ });
+ if (!acct) {
+ throw Error(`bank account ${req.bankAccountId} not found`);
+ }
+ return acct;
+}
+
+/**
+ * Remove a known bank account.
+ */
+async function forgetBankAccount(
+ wex: WalletExecutionContext,
+ bankAccountId: string,
+): Promise<void> {
+ await wex.runWalletDbTx(async (tx) => {
+ const account = await tx.getBankAccount(bankAccountId);
+ if (!account) {
+ throw Error(`account not found: ${bankAccountId}`);
+ }
+ await tx.deleteBankAccount(account.bankAccountId);
+ });
+ wex.ws.notify({
+ type: NotificationType.BankAccountChange,
+ bankAccountId,
+ });
+ return;
+}
+
+async function setCoinSuspended(
+ wex: WalletExecutionContext,
+ coinPub: string,
+ suspended: boolean,
+): Promise<void> {
+ await wex.runWalletDbTx(async (tx) => {
+ const c = await tx.getCoin(coinPub);
+ if (!c) {
+ logger.warn(`coin ${coinPub} not found, won't suspend`);
+ return;
+ }
+ const coinAvailability = await tx.getCoinAvailability(
+ c.exchangeBaseUrl,
+ c.denomPubHash,
+ c.maxAge,
+ );
+ checkDbInvariant(
+ !!coinAvailability,
+ `no denom info for ${c.denomPubHash} age ${c.maxAge}`,
+ );
+ if (suspended) {
+ if (c.status !== CoinStatus.Fresh) {
+ return;
+ }
+ if (coinAvailability.freshCoinCount === 0) {
+ throw Error(
+ `invalid coin count ${coinAvailability.freshCoinCount} in DB`,
+ );
+ }
+ coinAvailability.freshCoinCount--;
+ c.status = CoinStatus.FreshSuspended;
+ } else {
+ if (c.status == CoinStatus.Dormant) {
+ return;
+ }
+ coinAvailability.freshCoinCount++;
+ c.status = CoinStatus.Fresh;
+ }
+ await tx.upsertCoin(c);
+ await tx.upsertCoinAvailability(coinAvailability);
+ });
+}
+
+/**
+ * Dump the public information of coins we have in an easy-to-process format.
+ */
+async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> {
+ const coinsJson: CoinDumpJson = { coins: [] };
+ logger.info("dumping coins");
+ await wex.runWalletDbTx(async (tx) => {
+ const coins = await tx.listAllCoins();
+ for (const c of coins) {
+ const denom = await tx.getDenomination(c.exchangeBaseUrl, c.denomPubHash);
+ if (!denom) {
+ logger.warn("no denom found for coin");
+ continue;
+ }
+ const cs = c.coinSource;
+ let refreshParentCoinPub: string | undefined;
+ if (cs.type == CoinSourceType.Refresh) {
+ refreshParentCoinPub = cs.oldCoinPub;
+ }
+ let withdrawalReservePub: string | undefined;
+ if (cs.type == CoinSourceType.Withdraw) {
+ withdrawalReservePub = cs.reservePub;
+ }
+ const denomInfo = await getDenomInfo(
+ wex,
+ tx,
+ c.exchangeBaseUrl,
+ c.denomPubHash,
+ );
+ if (!denomInfo) {
+ logger.warn("no denomination found for coin");
+ continue;
+ }
+ const historyRec = await tx.getCoinHistory(c.coinPub);
+ coinsJson.coins.push({
+ coinPub: c.coinPub,
+ denomPub: denom.denomPub,
+ denomPubHash: c.denomPubHash,
+ denomValue: denom.value,
+ exchangeBaseUrl: c.exchangeBaseUrl,
+ refreshParentCoinPub: refreshParentCoinPub,
+ withdrawalReservePub: withdrawalReservePub,
+ coinStatus: c.status,
+ ageCommitmentProof: c.ageCommitmentProof,
+ history: historyRec ? historyRec.history : [],
+ });
+ }
+ });
+ return coinsJson;
+}
+
+async function createStoredBackup(
+ wex: WalletExecutionContext,
+): Promise<CreateStoredBackupResponse> {
+ const backup = await exportDb(wex.ws.idbFactory);
+ const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
+ const name = `backup-${new Date().getTime()}`;
+ await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.backupMeta.add({
+ name,
+ });
+ await tx.backupData.add(backup, name);
+ });
+ return {
+ name,
+ };
+}
+
+async function listStoredBackups(
+ wex: WalletExecutionContext,
+): Promise<StoredBackupList> {
+ const storedBackups: StoredBackupList = {
+ storedBackups: [],
+ };
+ const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
+ await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.backupMeta.iter().forEach((x) => {
+ storedBackups.storedBackups.push({
+ name: x.name,
+ });
+ });
+ });
+ return storedBackups;
+}
+
+async function deleteStoredBackup(
+ wex: WalletExecutionContext,
+ req: DeleteStoredBackupRequest,
+): Promise<void> {
+ const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
+ await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
+ await tx.backupData.delete(req.name);
+ await tx.backupMeta.delete(req.name);
+ });
+}
+
+async function recoverStoredBackup(
+ wex: WalletExecutionContext,
+ req: RecoverStoredBackupRequest,
+): Promise<void> {
+ logger.info(`Recovering stored backup ${req.name}`);
+ const { name } = req;
+ const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
+ const bd = await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
+ const backupMeta = await tx.backupMeta.get(name);
+ if (!backupMeta) {
+ throw Error("backup not found");
+ }
+ const backupData = await tx.backupData.get(name);
+ if (!backupData) {
+ throw Error("no backup data (DB corrupt)");
+ }
+ return backupData;
+ });
+ logger.info(`backup found, now importing`);
+ await importDb(wex.db.idbHandle(), bd);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await rematerializeTransactions(wex, tx.wtx);
+ // Clear fixups. Okay since they are idempotent.
+ const fixups = await tx.fixups.getAll();
+ for (const f of fixups) {
+ await tx.fixups.delete(f.fixupName);
+ }
+ });
+ logger.info(`import done`);
+}
+
+async function handlePrepareWithdrawExchange(
+ wex: WalletExecutionContext,
+ req: PrepareWithdrawExchangeRequest,
+): Promise<PrepareWithdrawExchangeResponse> {
+ const parsedUri = Result.orUndefined(TalerUris.parse(req.talerUri));
+ if (parsedUri?.type !== TalerUriAction.WithdrawExchange) {
+ throw Error("expected a taler://withdraw-exchange URI");
+ }
+ const exchangeBaseUrl = parsedUri.exchangeBaseUrl;
+
+ return await withMaybeProgressContext(
+ wex,
+ "prepareWithdrawExchange",
+ req.progressToken,
+ async (pc) => {
+ if (pc) {
+ pc.onRetryNow = async () => {
+ await resetExchangeRetries(wex, exchangeBaseUrl);
+ };
+ }
+ const exchange = await fetchFreshExchange(wex, exchangeBaseUrl, {
+ noBail: pc != null,
+ progressContext: pc,
+ });
+ if (parsedUri.amount) {
+ const amt = Amounts.parseOrThrow(parsedUri.amount);
+ if (amt.currency !== exchange.currency) {
+ throw Error("mismatch of currency (URI vs exchange)");
+ }
+ }
+ return {
+ exchangeBaseUrl,
+ amount: parsedUri.amount,
+ };
+ },
+ );
+}
+
+async function handleSharePayment(
+ wex: WalletExecutionContext,
+ req: SharePaymentRequest,
+): Promise<SharePaymentResult> {
+ return await sharePayment(wex, req.merchantBaseUrl, req.orderId);
+}
+
+async function handleDeleteStoredBackup(
+ wex: WalletExecutionContext,
+ req: DeleteStoredBackupRequest,
+): Promise<EmptyObject> {
+ await deleteStoredBackup(wex, req);
+ return {};
+}
+
+async function handleRecoverStoredBackup(
+ wex: WalletExecutionContext,
+ req: RecoverStoredBackupRequest,
+): Promise<EmptyObject> {
+ await recoverStoredBackup(wex, req);
+ 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,
+) {
+ if (logger.shouldLogTrace()) {
+ const initType = wex.ws.initCalled
+ ? "repeat initialization"
+ : "first initialization";
+ logger.trace(`init request (${initType}): ${j2s(req)}`);
+ }
+
+ // Write to the DB to make sure that we're failing early in
+ // case the DB is not writeable.
+ try {
+ await wex.runWalletDbTx(async (tx) => {
+ tx.upsertConfig({
+ key: ConfigRecordKey.LastInitInfo,
+ value: timestampProtocolToDb(TalerProtocolTimestamp.now()),
+ });
+ });
+ } catch (e) {
+ logger.error(
+ "error writing to database during initialization (while writing last error info)",
+ );
+ const err = getErrorDetailFromException(e);
+ logger.error(`details: ${j2s(err)}`);
+ throw TalerError.fromDetail(TalerErrorCode.WALLET_DB_UNAVAILABLE, {
+ innerError: err,
+ });
+ }
+ wex.ws.initWithConfig(applyRunConfigDefaults(req.config));
+
+ if (wex.ws.config.testing.skipDefaults) {
+ logger.trace("skipping defaults");
+ } else {
+ logger.trace("filling defaults");
+ await fillDefaults(wex);
+ }
+
+ if (req.config?.logLevel) {
+ setGlobalLogLevelFromString(req.config.logLevel);
+ }
+
+ await migrateMaterializedTransactions(wex);
+
+ const resp: InitResponse = {
+ versionInfo: await handleGetVersion(wex),
+ };
+
+ if (req.config?.lazyTaskLoop) {
+ logger.trace("lazily starting task loop");
+ } else {
+ await wex.taskScheduler.ensureRunning();
+ }
+
+ wex.ws.initCalled = true;
+ return resp;
+}
+
+async function handleWithdrawTestkudos(wex: WalletExecutionContext) {
+ return await withdrawTestBalance(wex, {
+ amount: "TESTKUDOS:10" as AmountString,
+ corebankApiBaseUrl: "https://bank.test.taler.net/",
+ exchangeBaseUrl: "https://exchange.test.taler.net/",
+ });
+}
+
+async function handleWithdrawTestBalance(
+ wex: WalletExecutionContext,
+ req: WithdrawTestBalanceRequest,
+): Promise<WithdrawTestBalanceResult> {
+ return await withdrawTestBalance(wex, req);
+}
+
+async function handleRunIntegrationTest(
+ wex: WalletExecutionContext,
+ req: IntegrationTestArgs,
+): Promise<EmptyObject> {
+ await runIntegrationTest(wex, req);
+ return {};
+}
+
+async function handleRunIntegrationTestV2(
+ wex: WalletExecutionContext,
+ req: IntegrationTestV2Args,
+): Promise<EmptyObject> {
+ await runIntegrationTest2(wex, req);
+ return {};
+}
+
+async function handleValidateIban(
+ wex: WalletExecutionContext,
+ req: ValidateIbanRequest,
+): Promise<ValidateIbanResponse> {
+ const valRes = validateIban(req.iban);
+ const resp: ValidateIbanResponse = {
+ valid: valRes.type === "valid",
+ };
+ return resp;
+}
+
+async function handleAddExchange(
+ wex: WalletExecutionContext,
+ req: AddExchangeRequest,
+): Promise<AddExchangeResponse> {
+ let exchangeBaseUrl: string;
+ if (req.exchangeBaseUrl) {
+ logger.warn(
+ "Deprecated request property: AddExchangeRequest.exchangeBaseUrl",
+ );
+ exchangeBaseUrl = req.exchangeBaseUrl;
+ } else if (req.uri) {
+ if (req.uri.startsWith("taler")) {
+ const p = Result.orUndefined(TalerUris.parse(req.uri));
+ if (p?.type !== TalerUriAction.AddExchange) {
+ throw Error("invalid taler://add-exchange/ URI");
+ }
+ exchangeBaseUrl = p.exchangeBaseUrl;
+ } else if (req.allowCompletion) {
+ const completeRes = await handleCompleteExchangeBaseUrl(wex, {
+ url: req.uri,
+ });
+ if (completeRes.status != "ok") {
+ throw TalerError.fromUncheckedDetail(completeRes.error);
+ }
+ exchangeBaseUrl = completeRes.completion;
+ } else if (req.uri.startsWith("http")) {
+ const canonUrl = canonicalizeBaseUrl(req.uri);
+ if (req.uri != canonUrl) {
+ throw Error("exchange base URL must be canonicalized");
+ }
+ exchangeBaseUrl = req.uri;
+ } else {
+ throw Error("AddExchangeRequest.uri must be http(s):// or taler://");
+ }
+ } else {
+ throw Error(
+ "AddExchangeRequest must either specify uri or exchangeBaseUrl",
+ );
+ }
+
+ // FIXME: Check /config before adding the exchange entry.
+
+ // FIXME: We probably should not wait synchronously here.
+ await fetchFreshExchange(wex, exchangeBaseUrl, {});
+ // Exchange has been explicitly added upon user request.
+ // Thus, we mark it as "used".
+ if (!req.ephemeral) {
+ await wex.runWalletDbTx(async (tx) => {
+ await markExchangeUsed(tx, exchangeBaseUrl);
+ });
+ }
+ return {
+ exchangeBaseUrl,
+ };
+}
+
+async function handleUpdateExchangeEntry(
+ wex: WalletExecutionContext,
+ req: UpdateExchangeEntryRequest,
+): Promise<EmptyObject> {
+ await startUpdateExchangeEntry(wex, req.exchangeBaseUrl, {
+ forceUpdate: !!req.force,
+ forceUnavailable: !!req.force,
+ });
+ return {};
+}
+
+async function handleTestingGetDenomStats(
+ wex: WalletExecutionContext,
+ req: TestingGetDenomStatsRequest,
+): Promise<TestingGetDenomStatsResponse> {
+ const denomStats: TestingGetDenomStatsResponse = {
+ numKnown: 0,
+ numLost: 0,
+ numOffered: 0,
+ };
+ await wex.runWalletDbTx(async (tx) => {
+ const denoms = await tx.getDenominationsByExchange(req.exchangeBaseUrl);
+ for (const d of denoms) {
+ denomStats.numKnown++;
+ if (d.isOffered) {
+ denomStats.numOffered++;
+ }
+ if (d.isLost) {
+ denomStats.numLost++;
+ }
+ }
+ });
+ return denomStats;
+}
+
+async function handleAddBankAccount(
+ wex: WalletExecutionContext,
+ req: AddBankAccountRequest,
+): Promise<AddBankAccountResponse> {
+ if (Result.isError(Paytos.fromString(req.paytoUri))) {
+ throw Error("invalid payto");
+ }
+ const acctId = await wex.runWalletDbTx(async (tx) => {
+ let currencies = req.currencies;
+ let myId: string;
+ const oldAcct = await tx.getBankAccountByPaytoUri(req.paytoUri);
+ if (req.replaceBankAccountId) {
+ myId = req.replaceBankAccountId;
+ } else if (oldAcct) {
+ myId = oldAcct.bankAccountId;
+ currencies = [
+ ...new Set([...(req.currencies ?? []), ...(oldAcct.currencies ?? [])]),
+ ];
+ currencies.sort();
+ } else {
+ // New Account!
+ myId = `acct:${encodeCrock(getRandomBytes(32))}`;
+ }
+ await tx.upsertBankAccount({
+ bankAccountId: myId,
+ paytoUri: req.paytoUri,
+ label: req.label,
+ currencies,
+ kycCompleted: false,
+ });
+ return myId;
+ });
+ wex.ws.notify({
+ type: NotificationType.BankAccountChange,
+ bankAccountId: acctId,
+ });
+ return {
+ bankAccountId: acctId,
+ };
+}
+
+async function handleForgetBankAccount(
+ wex: WalletExecutionContext,
+ req: ForgetBankAccountRequest,
+): Promise<EmptyObject> {
+ await forgetBankAccount(wex, req.bankAccountId);
+ return {};
+}
+
+// FIXME: Doesn't have proper type!
+async function handleTestingGetReserveHistory(
+ wex: WalletExecutionContext,
+ req: TestingGetReserveHistoryRequest,
+): Promise<any> {
+ const reserve = await wex.runWalletDbTx(async (tx) => {
+ return tx.getReserveByReservePub(req.reservePub);
+ });
+ if (!reserve) {
+ throw Error("no reserve pub found");
+ }
+ const sigResp = await wex.cryptoApi.signReserveHistoryReq({
+ reservePriv: reserve.reservePriv,
+ startOffset: 0,
+ });
+ const exchangeBaseUrl = req.exchangeBaseUrl;
+ const url = new URL(`reserves/${req.reservePub}/history`, exchangeBaseUrl);
+ const resp = await cancelableFetch(wex, url, {
+ headers: { ["Taler-Reserve-History-Signature"]: sigResp.sig },
+ });
+ const historyJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
+ return historyJson;
+}
+
+async function handleAcceptManualWithdrawal(
+ wex: WalletExecutionContext,
+ req: AcceptManualWithdrawalRequest,
+): Promise<AcceptManualWithdrawalResult> {
+ const res = await createManualWithdrawal(wex, {
+ amount: Amounts.parseOrThrow(req.amount),
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ restrictAge: req.restrictAge,
+ forceReservePriv: req.forceReservePriv,
+ });
+ return res;
+}
+
+async function handleGetExchangeTos(
+ wex: WalletExecutionContext,
+ req: GetExchangeTosRequest,
+): Promise<GetExchangeTosResult> {
+ return await withMaybeProgressContext(
+ wex,
+ "getExchangeTos",
+ req.progressToken,
+ async (pc) => {
+ return getExchangeTos(
+ wex,
+ req.exchangeBaseUrl,
+ req.acceptedFormat,
+ req.acceptLanguage,
+ pc,
+ );
+ },
+ );
+}
+
+async function handleGetQrCodesForPayto(
+ wex: WalletExecutionContext,
+ req: GetQrCodesForPaytoRequest,
+): Promise<GetQrCodesForPaytoResponse> {
+ return {
+ codes: getQrCodesForPayto(req.paytoUri),
+ };
+}
+
+async function handleGetBankingChoicesForPayto(
+ wex: WalletExecutionContext,
+ req: GetBankingChoicesForPaytoRequest,
+): Promise<GetBankingChoicesForPaytoResponse> {
+ const parsedPayto = Result.orUndefined(Paytos.fromString(req.paytoUri));
+ if (!parsedPayto) {
+ throw Error("invalid payto URI");
+ }
+ const amount = parsedPayto.params["amount"];
+ if (!amount) {
+ logger.warn("payto URI has no amount");
+ return {
+ choices: [],
+ };
+ }
+ const currency = Amounts.currencyOf(amount);
+ switch (currency) {
+ case "KUDOS":
+ return {
+ choices: [
+ {
+ label: "Demobank Website",
+ type: "link",
+ uri: `https://bank.demo.taler.net/webui/#/transfer/${encodeURIComponent(
+ req.paytoUri,
+ )}`,
+ },
+ {
+ label: "Demobank App",
+ type: "link",
+ uri: `https://bank.demo.taler.net/app/transfer/${encodeURIComponent(
+ req.paytoUri,
+ )}`,
+ },
+ ],
+ };
+ break;
+ default:
+ return {
+ choices: [],
+ };
+ }
+}
+
+async function handleGetChoicesForPayment(
+ wex: WalletExecutionContext,
+ req: GetChoicesForPaymentRequest,
+): Promise<GetChoicesForPaymentResult> {
+ return await getChoicesForPayment(wex, req.transactionId, req.forcedCoinSel);
+}
+
+async function handleConfirmPay(
+ wex: WalletExecutionContext,
+ req: ConfirmPayRequest,
+): Promise<ConfirmPayResult> {
+ return await confirmPay(wex, {
+ transactionId: req.transactionId,
+ choiceIndex: req.choiceIndex,
+ forcedCoinSel: undefined,
+ sessionIdOverride: req.sessionId,
+ useDonau: req.useDonau,
+ noWait: req.noWait ?? wex.ws.devExperimentState.flagConfirmPayNoWait,
+ });
+}
+
+async function handleListDiscounts(
+ wex: WalletExecutionContext,
+ req: ListDiscountsRequest,
+): Promise<ListDiscountsResponse> {
+ return await listDiscounts(wex, req.tokenIssuePubHash, req.merchantBaseUrl);
+}
+
+async function handleDeleteDiscount(
+ wex: WalletExecutionContext,
+ req: DeleteDiscountRequest,
+): Promise<EmptyObject> {
+ return await deleteDiscount(wex, req.tokenFamilyHash);
+}
+
+async function handleListSubscriptions(
+ wex: WalletExecutionContext,
+ req: ListSubscriptionsRequest,
+): Promise<ListSubscriptionsResponse> {
+ return await listSubscriptions(
+ wex,
+ req.tokenIssuePubHash,
+ req.merchantBaseUrl,
+ );
+}
+
+async function handleDeleteSubscription(
+ wex: WalletExecutionContext,
+ req: DeleteSubscriptionRequest,
+): Promise<EmptyObject> {
+ return await deleteSubscription(wex, req.tokenFamilyHash);
+}
+
+async function handleAbortTransaction(
+ wex: WalletExecutionContext,
+ req: AbortTransactionRequest,
+): Promise<EmptyObject> {
+ await abortTransaction(wex, req.transactionId);
+ return {};
+}
+
+async function handleSuspendTransaction(
+ wex: WalletExecutionContext,
+ req: SuspendTransactionRequest,
+): Promise<EmptyObject> {
+ await suspendTransaction(wex, req.transactionId);
+ return {};
+}
+
+async function handleGetActiveTasks(
+ wex: WalletExecutionContext,
+ req: EmptyObject,
+): Promise<GetActiveTasksResponse> {
+ const allTasksId = (await getActiveTaskIds(wex.ws)).taskIds;
+
+ const tasksInfo = await Promise.all(
+ allTasksId.map(async (id) => {
+ return await wex.runWalletDbTx(async (tx) => {
+ return tx.getOperationRetry(id);
+ });
+ }),
+ );
+
+ const tasks = allTasksId.map((taskId, i): ActiveTask => {
+ const transaction = convertTaskToTransactionId(taskId);
+ const d = tasksInfo[i];
+
+ const firstTry = !d
+ ? undefined
+ : timestampAbsoluteFromDb(d.retryInfo.firstTry);
+ const nextTry = !d
+ ? undefined
+ : timestampAbsoluteFromDb(d.retryInfo.nextRetry);
+ const counter = d?.retryInfo.retryCounter;
+ const lastError = d?.lastError;
+
+ return {
+ taskId: taskId,
+ retryCounter: counter,
+ firstTry,
+ nextTry,
+ lastError,
+ transaction,
+ };
+ });
+ return { tasks };
+}
+
+async function handleFailTransaction(
+ wex: WalletExecutionContext,
+ req: FailTransactionRequest,
+): Promise<EmptyObject> {
+ await failTransaction(wex, req.transactionId);
+ return {};
+}
+
+async function handleTestingGetSampleTransactions(
+ wex: WalletExecutionContext,
+ req: EmptyObject,
+): Promise<TransactionsResponse> {
+ // FIXME!
+ return { transactions: [] };
+ // These are out of date!
+ //return { transactions: sampleWalletCoreTransactions };
+}
+
+async function handleStartRefundQuery(
+ wex: WalletExecutionContext,
+ req: StartRefundQueryRequest,
+): Promise<EmptyObject> {
+ const txIdParsed = parseTransactionIdentifier(req.transactionId);
+ if (!txIdParsed) {
+ throw Error("invalid transaction ID");
+ }
+ if (txIdParsed.tag !== TransactionType.Payment) {
+ throw Error("expected payment transaction ID");
+ }
+ await startQueryRefund(wex, txIdParsed.proposalId);
+ return {};
+}
+
+async function handleHintNetworkAvailability(
+ wex: WalletExecutionContext,
+ req: HintNetworkAvailabilityRequest,
+): Promise<EmptyObject> {
+ // If network was already available, don't do anything
+ if (wex.ws.networkAvailable === req.isNetworkAvailable) {
+ return {};
+ }
+ wex.ws.networkAvailable = req.isNetworkAvailable;
+ // When network becomes available, restart tasks as they're blocked
+ // waiting for the network.
+ // When network goes down, restart tasks so they notice the network
+ // is down and wait.
+ await restartAllRunningTasks(wex);
+ return {};
+}
+
+async function handleGetDepositWireTypes(
+ wex: WalletExecutionContext,
+ req: GetDepositWireTypesRequest,
+): Promise<GetDepositWireTypesResponse> {
+ const wtSet: Set<string> = new Set();
+ const wireTypeDetails: WireTypeDetails[] = [];
+ const talerBankHostnames: string[] = [];
+ await wex.runWalletDbTx(async (tx) => {
+ const exchanges = await tx.getExchanges();
+ for (const exchange of exchanges) {
+ const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
+ if (!det) {
+ continue;
+ }
+ if (req.currency !== undefined && det.currency !== req.currency) {
+ continue;
+ }
+ for (const acc of det.wireInfo.accounts) {
+ let usable = true;
+ for (const dr of acc.debit_restrictions) {
+ if (dr.type === "deny") {
+ usable = false;
+ break;
+ }
+ }
+ if (!usable) {
+ continue;
+ }
+ const parsedPayto = Result.orUndefined(
+ Paytos.fromString(acc.payto_uri),
+ );
+ if (!parsedPayto) {
+ continue;
+ }
+ let preferredEntryType: "iban" | "bban" | undefined = undefined;
+ if (parsedPayto.targetType === PaytoType.IBAN) {
+ if (det.currency === "HUF") {
+ preferredEntryType = "bban";
+ } else {
+ preferredEntryType = "iban";
+ }
+ }
+ if (parsedPayto.targetType === PaytoType.TalerBank) {
+ if (!talerBankHostnames.includes(parsedPayto.host)) {
+ talerBankHostnames.push(parsedPayto.host);
+ }
+ }
+ if (!wtSet.has(parsedPayto.targetType!)) {
+ wtSet.add(parsedPayto.targetType!);
+ wireTypeDetails.push({
+ paymentTargetType: parsedPayto.targetType!,
+ // Will possibly extended later by other exchanges
+ // with the same wire type.
+ talerBankHostnames,
+ preferredEntryType,
+ });
+ }
+ }
+ }
+ });
+ return {
+ wireTypeDetails,
+ };
+}
+
+async function handleGetDepositWireTypesForCurrency(
+ wex: WalletExecutionContext,
+ req: GetDepositWireTypesForCurrencyRequest,
+): Promise<GetDepositWireTypesForCurrencyResponse> {
+ const wtSet: Set<string> = new Set();
+ const wireTypeDetails: WireTypeDetails[] = [];
+ const talerBankHostnames: string[] = [];
+ await wex.runWalletDbTx(async (tx) => {
+ const exchanges = await tx.getExchanges();
+ for (const exchange of exchanges) {
+ const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
+ if (!det) {
+ continue;
+ }
+ if (det.currency !== req.currency) {
+ continue;
+ }
+ for (const acc of det.wireInfo.accounts) {
+ let usable = true;
+ for (const dr of acc.debit_restrictions) {
+ if (dr.type === "deny") {
+ usable = false;
+ break;
+ }
+ }
+ if (!usable) {
+ continue;
+ }
+ const parsedPayto = Result.orUndefined(
+ Paytos.fromString(acc.payto_uri),
+ );
+ if (!parsedPayto) {
+ continue;
+ }
+ if (parsedPayto.targetType === PaytoType.TalerBank) {
+ if (!talerBankHostnames.includes(parsedPayto.host)) {
+ talerBankHostnames.push(parsedPayto.host);
+ }
+ }
+ if (!wtSet.has(parsedPayto.targetType!)) {
+ wtSet.add(parsedPayto.targetType!);
+ wireTypeDetails.push({
+ paymentTargetType: parsedPayto.targetType!,
+ // Will possibly extended later by other exchanges
+ // with the same wire type.
+ talerBankHostnames,
+ });
+ }
+ }
+ }
+ });
+ return {
+ wireTypes: [...wtSet],
+ wireTypeDetails,
+ };
+}
+
+async function handleListGlobalCurrencyExchanges(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<ListGlobalCurrencyExchangesResponse> {
+ const resp: ListGlobalCurrencyExchangesResponse = {
+ exchanges: [],
+ };
+ await wex.runWalletDbTx(async (tx) => {
+ const gceList = await tx.listGlobalCurrencyExchanges();
+ for (const gce of gceList) {
+ resp.exchanges.push({
+ currency: gce.currency,
+ exchangeBaseUrl: gce.exchangeBaseUrl,
+ exchangeMasterPub: gce.exchangeMasterPub,
+ });
+ }
+ });
+ return resp;
+}
+
+async function handleListGlobalCurrencyAuditors(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<ListGlobalCurrencyAuditorsResponse> {
+ const resp: ListGlobalCurrencyAuditorsResponse = {
+ auditors: [],
+ };
+ await wex.runWalletDbTx(async (tx) => {
+ const gcaList = await tx.listGlobalCurrencyAuditors();
+ for (const gca of gcaList) {
+ resp.auditors.push({
+ currency: gca.currency,
+ auditorBaseUrl: gca.auditorBaseUrl,
+ auditorPub: gca.auditorPub,
+ });
+ }
+ });
+ return resp;
+}
+
+export async function handleAddGlobalCurrencyExchange(
+ wex: WalletExecutionContext,
+ req: AddGlobalCurrencyExchangeRequest,
+): Promise<EmptyObject> {
+ await wex.runWalletDbTx(async (tx) => {
+ const existingRec = await tx.getGlobalCurrencyExchange(
+ req.currency,
+ req.exchangeBaseUrl,
+ req.exchangeMasterPub,
+ );
+ if (existingRec) {
+ return;
+ }
+ wex.ws.exchangeCache.clear();
+ // Re-scope currency info from exchange scope to global scope. The scope
+ // string is the primary key, so this writes a new record and leaves the
+ // exchange-scoped one, as it did before.
+ const infoRec = await tx.getCurrencyInfo({
+ type: ScopeType.Exchange,
+ currency: req.currency,
+ url: req.exchangeBaseUrl,
+ });
+ if (infoRec) {
+ await tx.upsertCurrencyInfo({
+ scopeInfo: { type: ScopeType.Global, currency: req.currency },
+ currencySpec: infoRec.currencySpec,
+ source: infoRec.source,
+ });
+ }
+ await tx.addGlobalCurrencyExchange({
+ currency: req.currency,
+ exchangeBaseUrl: req.exchangeBaseUrl,
+ exchangeMasterPub: req.exchangeMasterPub,
+ });
+ });
+ return {};
+}
+
+async function handleRemoveGlobalCurrencyAuditor(
+ wex: WalletExecutionContext,
+ req: RemoveGlobalCurrencyAuditorRequest,
+): Promise<EmptyObject> {
+ await wex.runWalletDbTx(async (tx) => {
+ const existingRec = await tx.getGlobalCurrencyAuditor(
+ req.currency,
+ req.auditorBaseUrl,
+ req.auditorPub,
+ );
+ if (!existingRec) {
+ return;
+ }
+ checkDbInvariant(
+ !!existingRec.id,
+ `no global currency for ${req.currency}`,
+ );
+ await tx.deleteGlobalCurrencyAuditor(existingRec.id);
+ wex.ws.exchangeCache.clear();
+ });
+ return {};
+}
+
+export async function handleRemoveGlobalCurrencyExchange(
+ wex: WalletExecutionContext,
+ req: RemoveGlobalCurrencyExchangeRequest,
+): Promise<EmptyObject> {
+ await wex.runWalletDbTx(async (tx) => {
+ const existingRec = await tx.getGlobalCurrencyExchange(
+ req.currency,
+ req.exchangeBaseUrl,
+ req.exchangeMasterPub,
+ );
+ if (!existingRec) {
+ return;
+ }
+ wex.ws.exchangeCache.clear();
+ checkDbInvariant(
+ !!existingRec.id,
+ `no global exchange for ${req.currency}`,
+ );
+ await tx.deleteCurrencyInfo({
+ type: ScopeType.Global,
+ currency: req.currency,
+ });
+ await tx.deleteGlobalCurrencyExchange(existingRec.id);
+ });
+ return {};
+}
+
+async function handleAddGlobalCurrencyAuditor(
+ wex: WalletExecutionContext,
+ req: AddGlobalCurrencyAuditorRequest,
+): Promise<EmptyObject> {
+ await wex.runWalletDbTx(async (tx) => {
+ const existingRec = await tx.getGlobalCurrencyAuditor(
+ req.currency,
+ req.auditorBaseUrl,
+ req.auditorPub,
+ );
+ if (existingRec) {
+ return;
+ }
+ await tx.addGlobalCurrencyAuditor({
+ currency: req.currency,
+ auditorBaseUrl: req.auditorBaseUrl,
+ auditorPub: req.auditorPub,
+ });
+ wex.ws.exchangeCache.clear();
+ });
+ return {};
+}
+
+async function handleShutdown(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<EmptyObject> {
+ logger.info(`Shutdown requested`);
+ wex.ws.stop();
+ return {};
+}
+
+async function handleTestingSetTimetravel(
+ wex: WalletExecutionContext,
+ req: TestingSetTimetravelRequest,
+): Promise<EmptyObject> {
+ setDangerousTimetravel(req.offsetMs);
+ await wex.taskScheduler.reload();
+ return {};
+}
+
+async function handleCanonicalizeBaseUrl(
+ _wex: WalletExecutionContext,
+ req: CanonicalizeBaseUrlRequest,
+): Promise<CanonicalizeBaseUrlResponse> {
+ return {
+ url: canonicalizeBaseUrl(req.url),
+ };
+}
+
+async function handleDeleteExchange(
+ wex: WalletExecutionContext,
+ req: DeleteExchangeRequest,
+): Promise<EmptyObject> {
+ await deleteExchange(wex, req);
+ return {};
+}
+
+async function handleCreateStoredBackup(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<CreateStoredBackupResponse> {
+ return await createStoredBackup(wex);
+}
+
+async function handleExportDbToFile(
+ wex: WalletExecutionContext,
+ req: ExportDbToFileRequest,
+): Promise<ExportDbToFileResponse> {
+ const res = await wex.ws.dbImplementation.exportToFile(
+ req.directory,
+ req.stem,
+ req.forceFormat,
+ );
+ return {
+ path: res.path,
+ };
+}
+
+async function handleImportDb(
+ wex: WalletExecutionContext,
+ req: ImportDbRequest,
+): Promise<EmptyObject> {
+ // FIXME: This should atomically re-materialize transactions!
+ await importDb(wex.db.idbHandle(), req.dump);
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await rematerializeTransactions(wex, tx.wtx);
+ // Clear fixups. Okay since they are idempotent.
+ const fixups = await tx.fixups.getAll();
+ for (const f of fixups) {
+ await tx.fixups.delete(f.fixupName);
+ }
+ });
+ return {};
+}
+
+async function handleImportDbFromFile(
+ wex: WalletExecutionContext,
+ req: ImportDbFromFileRequest,
+): Promise<EmptyObject> {
+ if (req.path.endsWith(".json")) {
+ const dump = await wex.ws.dbImplementation.readBackupJson(req.path);
+ return await handleImportDb(wex, {
+ dump,
+ });
+ } else {
+ throw Error("DB file import only supports .json files at the moment");
+ }
+}
+
+async function handleAcceptBankIntegratedWithdrawal(
+ wex: WalletExecutionContext,
+ req: AcceptBankIntegratedWithdrawalRequest,
+): Promise<AcceptWithdrawalResponse> {
+ return await acceptBankIntegratedWithdrawal(wex, {
+ selectedExchange: req.exchangeBaseUrl,
+ talerWithdrawUri: req.talerWithdrawUri,
+ forcedDenomSel: req.forcedDenomSel,
+ restrictAge: req.restrictAge,
+ amount: req.amount,
+ });
+}
+
+async function handleGetCurrencySpecification(
+ wex: WalletExecutionContext,
+ req: GetCurrencySpecificationRequest,
+): Promise<GetCurrencySpecificationResponse> {
+ const spec = await wex.runWalletDbTx(async (tx) => {
+ return tx.getCurrencyInfo(req.scope);
+ });
+ if (spec) {
+ if (
+ wex.ws.devExperimentState.fakeDemoShortcuts != null &&
+ req.scope.type === ScopeType.Exchange &&
+ req.scope.url === "https://exchange.demo.taler.net/"
+ ) {
+ spec.currencySpec.common_amounts =
+ wex.ws.devExperimentState.fakeDemoShortcuts;
+ }
+ return {
+ currencySpecification: spec.currencySpec,
+ };
+ }
+ // Hard-coded mock for KUDOS and TESTKUDOS
+ if (req.scope.currency === "KUDOS") {
+ const kudosResp: GetCurrencySpecificationResponse = {
+ currencySpecification: {
+ name: "Kudos (Taler Demonstrator)",
+ num_fractional_input_digits: 2,
+ num_fractional_normal_digits: 2,
+ num_fractional_trailing_zero_digits: 2,
+ alt_unit_names: {
+ "0": "ク",
+ },
+ },
+ };
+ return kudosResp;
+ } else if (req.scope.currency === "TESTKUDOS") {
+ const testkudosResp: GetCurrencySpecificationResponse = {
+ currencySpecification: {
+ name: "Test (Taler Unstable Demonstrator)",
+ num_fractional_input_digits: 0,
+ num_fractional_normal_digits: 0,
+ num_fractional_trailing_zero_digits: 0,
+ alt_unit_names: {
+ "0": "テ",
+ },
+ },
+ };
+ return testkudosResp;
+ }
+ const defaultResp: GetCurrencySpecificationResponse = {
+ currencySpecification: {
+ name: req.scope.currency,
+ num_fractional_input_digits: 2,
+ num_fractional_normal_digits: 2,
+ num_fractional_trailing_zero_digits: 2,
+ alt_unit_names: {
+ "0": req.scope.currency,
+ },
+ },
+ };
+ return defaultResp;
+}
+
+export async function handleHintApplicationResumed(
+ wex: WalletExecutionContext,
+ req: EmptyObject,
+): Promise<EmptyObject> {
+ logger.info("handling hintApplicationResumed");
+ await restartAllRunningTasks(wex);
+ return {};
+}
+
+export async function handleTestingRunFixup(
+ wex: WalletExecutionContext,
+ req: RunFixupRequest,
+): Promise<EmptyObject> {
+ for (const fixup of walletDbFixups) {
+ if (fixup.name != req.id) {
+ continue;
+ }
+ await wex.runLegacyWalletDbTx(async (tx) => {
+ await fixup.fn(tx);
+ await rematerializeTransactions(wex, tx.wtx);
+ });
+ return {};
+ }
+ throw Error("fixup not found");
+}
+
+async function handleGetVersion(
+ wex: WalletExecutionContext,
+): Promise<WalletCoreVersion> {
+ const result: WalletCoreVersion = {
+ implementationSemver: walletCoreBuildInfo.implementationSemver,
+ implementationGitHash: walletCoreBuildInfo.implementationGitHash,
+ hash: undefined,
+ version: WALLET_CORE_API_PROTOCOL_VERSION,
+ exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
+ merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
+ bankConversionApiRange: WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION,
+ bankIntegrationApiRange: TalerBankIntegrationHttpClient.PROTOCOL_VERSION,
+ corebankApiRange: WALLET_COREBANK_API_PROTOCOL_VERSION,
+ bank: TalerBankIntegrationHttpClient.PROTOCOL_VERSION,
+ devMode: wex.ws.config.testing.devModeActive,
+ };
+ return result;
+}
+
+export async function handleConvertIbanAccountFieldToPayto(
+ wex: WalletExecutionContext,
+ req: ConvertIbanAccountFieldToPaytoRequest,
+): Promise<ConvertIbanAccountFieldToPaytoResponse> {
+ const strippedInput = req.value.replace(/[- ]/g, "");
+ if (req.currency === "HUF") {
+ const iban = convertHUF_BBANtoIBAN(strippedInput);
+ if (Result.isOk(iban)) {
+ return {
+ ok: true,
+ paytoUri: `payto://iban/${iban.value}`,
+ type: "iban",
+ };
+ } else {
+ return {
+ ok: false,
+ };
+ }
+ }
+ if (wex.ws.devExperimentState.fakeChfBban && req.currency === "CHF") {
+ const iban = convertCHF_BBANtoIBAN(strippedInput);
+ if (Result.isOk(iban)) {
+ return {
+ ok: true,
+ paytoUri: `payto://iban/${iban.value}`,
+ type: "iban",
+ };
+ } else {
+ return {
+ ok: false,
+ };
+ }
+ }
+ const parsedIban = parseIban(strippedInput);
+ if (Result.isOk(parsedIban)) {
+ return {
+ ok: true,
+ paytoUri: `payto://iban/${strippedInput}`,
+ type: "iban",
+ };
+ } else {
+ return {
+ ok: false,
+ };
+ }
+}
+
+export async function handleConvertIbanPaytoToAccountField(
+ wex: WalletExecutionContext,
+ req: ConvertIbanPaytoToAccountFieldRequest,
+): Promise<ConvertIbanPaytoToAccountFieldResponse> {
+ const payto = Result.unpack(Paytos.fromString(req.paytoUri));
+ const iban = payto.normalizedPath;
+ if (iban.startsWith("HU")) {
+ let bban = iban.slice(4);
+ if (bban.endsWith("00000000")) {
+ bban = bban.slice(0, bban.length - 8);
+ }
+ return {
+ type: "bban",
+ value: bban,
+ };
+ } else if (wex.ws.devExperimentState.fakeChfBban && iban.startsWith("CH")) {
+ return {
+ type: "bban",
+ value: iban.slice(4),
+ };
+ }
+ return {
+ type: "iban",
+ value: iban,
+ };
+}
+
+export async function handleGetFlightRecords(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<TestingGetFlightRecordsResponse> {
+ return { flightRecords: wex.ws.flightRecords };
+}
+
+export async function handleGetDefaultExchanges(
+ wex: WalletExecutionContext,
+ _req: EmptyObject,
+): Promise<GetDefaultExchangesResponse> {
+ const defaultExchanges: GetDefaultExchangesResponse["defaultExchanges"] = [];
+ const myExchanges = await listExchanges(wex, {
+ filterByType: "prod",
+ });
+ for (const exch of myExchanges.exchanges) {
+ switch (exch.exchangeEntryStatus) {
+ case ExchangeEntryStatus.Ephemeral:
+ continue;
+ }
+ if (exch.currency === "UNKNOWN") {
+ continue;
+ }
+ defaultExchanges.push({
+ currency: exch.currency,
+ currencySpec: exch.currencySpec,
+ talerUri: TalerUris.stringify({
+ type: TalerUriAction.WithdrawExchange,
+ exchangeBaseUrl: exch.exchangeBaseUrl as HostPortPath,
+ }),
+ });
+ }
+ if (wex.ws.devExperimentState.fakeDefaultExchangeDemo) {
+ defaultExchanges.push({
+ talerUri: "taler://withdraw-exchange/exchange.demo.taler.net/",
+ currency: "KUDOS",
+ currencySpec: {
+ name: "Kudos",
+ common_amounts: ["KUDOS:5", "KUDOS:10", "KUDOS:25", "KUDOS:50"],
+ num_fractional_input_digits: 2,
+ num_fractional_normal_digits: 2,
+ num_fractional_trailing_zero_digits: 2,
+ alt_unit_names: {
+ "0": "ク",
+ },
+ },
+ });
+ }
+ return {
+ defaultExchanges,
+ };
+}
+
+export async function handleTestingCorruptWithdrawalCoinSel(
+ wex: WalletExecutionContext,
+ req: TestingCorruptWithdrawalCoinSelRequest,
+): Promise<EmptyObject> {
+ const txId = parseTransactionIdentifier(req.transactionId);
+ if (txId?.tag !== TransactionType.Withdrawal) {
+ throw Error("expected withdrawal transaction ID");
+ }
+ await wex.runWalletDbTx(async (tx) => {
+ const wg = await tx.getWithdrawalGroup(txId.withdrawalGroupId);
+ if (!wg) {
+ return;
+ }
+ if (wg.denomsSel && (wg.denomsSel.selectedDenoms.length ?? 0) > 0) {
+ wg.denomsSel.selectedDenoms[0].denomPubHash = encodeCrock(
+ getRandomBytes(64),
+ );
+ await tx.upsertWithdrawalGroup(wg);
+ }
+ });
+ return {};
+}
+
+export async function handleGetDiagnostics(
+ wex: WalletExecutionContext,
+ req: EmptyObject,
+): Promise<TestingGetDiagnosticsResponse> {
+ const cnt: Record<string, number> = {};
+ const exchangeEntries: TestingGetDiagnosticsResponse["exchangeEntries"] = [];
+ await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+ cnt["coinAvailability"] = await tx.coinAvailability.count();
+ cnt["coins"] = await tx.coins.count();
+ cnt["denominationFamilies"] = await tx.denominationFamilies.count();
+ cnt["denominations"] = await tx.denominations.count();
+ cnt["exchangeDetails"] = await tx.exchangeDetails.count();
+ cnt["exchangeSignKeys"] = await tx.exchangeSignKeys.count();
+ cnt["exchanges"] = await tx.exchanges.count();
+ for (const exch of await tx.exchanges.getAll()) {
+ const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
+ exch.baseUrl,
+ );
+ let numWithdrawableDenoms = 0;
+ let numCandidateWithdrawableDenoms = 0;
+ for (let i = 0; i < denoms.length; i++) {
+ const d = denoms[i];
+ if (isWithdrawableDenom(d)) {
+ numWithdrawableDenoms++;
+ }
+ if (isCandidateWithdrawableDenomRec(d)) {
+ numCandidateWithdrawableDenoms++;
+ }
+ }
+ exchangeEntries.push({
+ exchangeBaseUrl: exch.baseUrl,
+ numDenoms: denoms.length,
+ numCandidateWithdrawableDenoms,
+ numWithdrawableDenoms,
+ });
+ }
+ });
+ return {
+ version: 0,
+ idbObjectStoreCounts: cnt,
+ exchangeEntries,
+ };
+}
+
+export async function handleTestingWaitExchangeReady(
+ wex: WalletExecutionContext,
+ req: TestingWaitExchangeReadyRequest,
+): Promise<EmptyObject> {
+ await waitReadyExchange(wex, req.exchangeBaseUrl, {
+ noBail: req.noBail,
+ forceUpdate: req.forceUpdate,
+ waitAutoRefresh: req.waitAutoRefresh,
+ });
+ return {};
+}
+
+export async function handleTestingWaitBalance(
+ wex: WalletExecutionContext,
+ req: TestingWaitBalanceRequest,
+): Promise<EmptyObject> {
+ await testingWaitBalance(wex, req);
+ return {};
+}
+
+export async function handleGetPerformanceStats(
+ wex: WalletExecutionContext,
+ req: GetPerformanceStatsRequest,
+): Promise<GetPerformanceStatsResponse> {
+ return {
+ stats: PerformanceTable.limit(wex.ws.performanceStats, req.limit),
+ };
+}
+
+interface HandlerWithValidator<Tag extends WalletApiOperation> {
+ codec: Codec<WalletCoreRequestType<Tag>>;
+ handler: (
+ wex: WalletExecutionContext,
+ req: WalletCoreRequestType<Tag>,
+ ) => Promise<WalletCoreResponseType<Tag>>;
+}
+
+const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
+ [WalletApiOperation.CancelProgressToken]: {
+ codec: codecForCancelProgressToken(),
+ handler: handleCancelProgressToken,
+ },
+ [WalletApiOperation.RetryProgressTokenNow]: {
+ codec: codecForRetryProgressTokenNowRequest(),
+ handler: handleRetryProgressTokenNow,
+ },
+ [WalletApiOperation.TestingWaitExchangeReady]: {
+ codec: codecForTestingWaitExchangeReadyRequest(),
+ handler: handleTestingWaitExchangeReady,
+ },
+ [WalletApiOperation.TestingWaitBalance]: {
+ codec: codecForTestingWaitBalanceRequest(),
+ handler: handleTestingWaitBalance,
+ },
+ [WalletApiOperation.TestingCorruptWithdrawalCoinSel]: {
+ codec: codecForTestingCorruptWithdrawalCoinSelRequest(),
+ handler: handleTestingCorruptWithdrawalCoinSel,
+ },
+ [WalletApiOperation.GetDefaultExchanges]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetDefaultExchanges,
+ },
+ [WalletApiOperation.TestingGetFlightRecords]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetFlightRecords,
+ },
+ [WalletApiOperation.GetDiagnostics]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetDiagnostics,
+ },
+ [WalletApiOperation.ConvertIbanAccountFieldToPayto]: {
+ codec: codecForConvertIbanAccountFieldToPaytoRequest(),
+ handler: handleConvertIbanAccountFieldToPayto,
+ },
+ [WalletApiOperation.ConvertIbanPaytoToAccountField]: {
+ codec: codecForConvertIbanPaytoToAccountFieldRequest(),
+ handler: handleConvertIbanPaytoToAccountField,
+ },
+ [WalletApiOperation.TestingGetPerformanceStats]: {
+ codec: codecForGetPerformanceStatsRequest(),
+ handler: handleGetPerformanceStats,
+ },
+ [WalletApiOperation.TestingWaitExchangeState]: {
+ codec: codecForAny(),
+ handler: handleTestingWaitExchangeState,
+ },
+ [WalletApiOperation.GetDonauStatements]: {
+ codec: codecForGetDonauStatementsRequest(),
+ handler: handleGetDonauStatements,
+ },
+ [WalletApiOperation.GetDonau]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetDonau,
+ },
+ [WalletApiOperation.SetDonau]: {
+ codec: codecForSetDonauRequest(),
+ handler: handleSetDonau,
+ },
+ [WalletApiOperation.AddContact]: {
+ codec: codecForAddContactRequest(),
+ handler: addContact,
+ },
+ [WalletApiOperation.DeleteContact]: {
+ codec: codecForDeleteContactRequest(),
+ handler: deleteContact,
+ },
+ [WalletApiOperation.GetContacts]: {
+ codec: codecForEmptyObject(),
+ handler: listContacts,
+ },
+ [WalletApiOperation.RegisterAlias]: {
+ codec: codecForTaldirRegistrationRequest(),
+ handler: registerAlias,
+ },
+ [WalletApiOperation.CompleteRegisterAlias]: {
+ codec: codecForTaldirRegistrationCompletionRequest(),
+ handler: completeAliasRegistration,
+ },
+ [WalletApiOperation.LookupAlias]: {
+ codec: codecForTaldirLookupRequest(),
+ handler: lookupAlias,
+ },
+ [WalletApiOperation.InitializeMailbox]: {
+ codec: codecForMailboxBaseUrl(),
+ handler: createNewMailbox,
+ },
+ [WalletApiOperation.GetMailbox]: {
+ codec: codecForMailboxBaseUrl(),
+ handler: getMailbox,
+ },
+ [WalletApiOperation.SendTalerUriMailboxMessage]: {
+ codec: codecForSendTalerUriMailboxMessageRequest(),
+ handler: sendTalerUriMessage,
+ },
+ [WalletApiOperation.AddMailboxMessage]: {
+ codec: codecForAddMailboxMessageRequest(),
+ handler: addMailboxMessage,
+ },
+ [WalletApiOperation.DeleteMailboxMessage]: {
+ codec: codecForDeleteMailboxMessageRequest(),
+ handler: deleteMailboxMessage,
+ },
+ [WalletApiOperation.GetMailboxMessages]: {
+ codec: codecForEmptyObject(),
+ handler: listMailboxMessages,
+ },
+ [WalletApiOperation.RefreshMailbox]: {
+ codec: codecForMailboxConfiguration(),
+ handler: refreshMailbox,
+ },
+ [WalletApiOperation.TestingGetDbStats]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex) => {
+ return wex.ws.dbImplementation.getStats();
+ },
+ },
+ [WalletApiOperation.ExportDbToFile]: {
+ codec: codecForExportDbToFileRequest(),
+ handler: handleExportDbToFile,
+ },
+ [WalletApiOperation.HintApplicationResumed]: {
+ codec: codecForEmptyObject(),
+ handler: handleHintApplicationResumed,
+ },
+ [WalletApiOperation.TestingRunFixup]: {
+ codec: codecForRunFixupRequest(),
+ handler: handleTestingRunFixup,
+ },
+ [WalletApiOperation.AbortTransaction]: {
+ codec: codecForAbortTransaction(),
+ handler: handleAbortTransaction,
+ },
+ [WalletApiOperation.CreateStoredBackup]: {
+ codec: codecForEmptyObject(),
+ handler: handleCreateStoredBackup,
+ },
+ [WalletApiOperation.DeleteStoredBackup]: {
+ codec: codecForDeleteStoredBackupRequest(),
+ handler: handleDeleteStoredBackup,
+ },
+ [WalletApiOperation.ListStoredBackups]: {
+ codec: codecForEmptyObject(),
+ handler: listStoredBackups,
+ },
+ [WalletApiOperation.CompleteExchangeBaseUrl]: {
+ codec: codecForCompleteBaseUrlRequest(),
+ handler: handleCompleteExchangeBaseUrl,
+ },
+ [WalletApiOperation.SetWalletRunConfig]: {
+ codec: codecForInitRequest(),
+ handler: handleSetWalletRunConfig,
+ },
+ // Alias for SetWalletRunConfig
+ [WalletApiOperation.InitWallet]: {
+ codec: codecForInitRequest(),
+ handler: handleSetWalletRunConfig,
+ },
+ [WalletApiOperation.RecoverStoredBackup]: {
+ codec: codecForRecoverStoredBackupRequest(),
+ handler: handleRecoverStoredBackup,
+ },
+ [WalletApiOperation.WithdrawTestkudos]: {
+ codec: codecForEmptyObject(),
+ handler: handleWithdrawTestkudos,
+ },
+ [WalletApiOperation.WithdrawTestBalance]: {
+ codec: codecForWithdrawTestBalance(),
+ handler: handleWithdrawTestBalance,
+ },
+ [WalletApiOperation.RunIntegrationTest]: {
+ codec: codecForIntegrationTestArgs(),
+ handler: handleRunIntegrationTest,
+ },
+ [WalletApiOperation.RunIntegrationTestV2]: {
+ codec: codecForIntegrationTestV2Args(),
+ handler: handleRunIntegrationTestV2,
+ },
+ [WalletApiOperation.ValidateIban]: {
+ codec: codecForValidateIbanRequest(),
+ handler: handleValidateIban,
+ },
+ [WalletApiOperation.TestPay]: {
+ codec: codecForTestPayArgs(),
+ handler: testPay,
+ },
+ [WalletApiOperation.GetTransactions]: {
+ codec: codecForTransactionsRequest(),
+ handler: getTransactions,
+ },
+ [WalletApiOperation.GetTransactionsV2]: {
+ codec: codecForGetTransactionsV2Request(),
+ handler: getTransactionsV2,
+ },
+ [WalletApiOperation.GetTransactionById]: {
+ codec: codecForTransactionByIdRequest(),
+ handler: getTransactionById,
+ },
+ [WalletApiOperation.AddExchange]: {
+ codec: codecForAddExchangeRequest(),
+ handler: handleAddExchange,
+ },
+ [WalletApiOperation.TestingPing]: {
+ codec: codecForEmptyObject(),
+ handler: async () => ({}),
+ },
+ [WalletApiOperation.UpdateExchangeEntry]: {
+ codec: codecForUpdateExchangeEntryRequest(),
+ handler: handleUpdateExchangeEntry,
+ },
+ [WalletApiOperation.TestingGetDenomStats]: {
+ codec: codecForTestingGetDenomStatsRequest(),
+ handler: handleTestingGetDenomStats,
+ },
+ [WalletApiOperation.ListExchanges]: {
+ codec: codecForListExchangesRequest(),
+ handler: listExchanges,
+ },
+ [WalletApiOperation.GetExchangeEntryByUrl]: {
+ codec: codecForGetExchangeEntryByUrlRequest(),
+ handler: lookupExchangeByUri,
+ },
+ [WalletApiOperation.GetExchangeDetailedInfo]: {
+ codec: codecForGetExchangeEntryByUrlRequest(),
+ handler: (wex, req) => getExchangeDetailedInfo(wex, req.exchangeBaseUrl),
+ },
+ [WalletApiOperation.ListBankAccounts]: {
+ codec: codecForListBankAccounts(),
+ handler: handleListBankAccounts,
+ },
+ [WalletApiOperation.GetBankAccountById]: {
+ codec: codecForGetBankAccountByIdRequest(),
+ handler: handleGetBankAccountById,
+ },
+ [WalletApiOperation.AddBankAccount]: {
+ codec: codecForAddBankAccountRequest(),
+ handler: handleAddBankAccount,
+ },
+ [WalletApiOperation.ForgetBankAccount]: {
+ codec: codecForForgetBankAccount(),
+ handler: handleForgetBankAccount,
+ },
+ [WalletApiOperation.GetWithdrawalDetailsForUri]: {
+ codec: codecForGetWithdrawalDetailsForUri(),
+ handler: (wex, req) =>
+ getWithdrawalDetailsForUri(wex, req.talerWithdrawUri),
+ },
+ [WalletApiOperation.TestingGetReserveHistory]: {
+ codec: codecForTestingGetReserveHistoryRequest(),
+ handler: handleTestingGetReserveHistory,
+ },
+ [WalletApiOperation.AcceptManualWithdrawal]: {
+ codec: codecForAcceptManualWithdrawalRequest(),
+ handler: handleAcceptManualWithdrawal,
+ },
+ [WalletApiOperation.GetWithdrawalDetailsForAmount]: {
+ codec: codecForGetWithdrawalDetailsForAmountRequest(),
+ handler: getWithdrawalDetailsForAmount,
+ },
+ [WalletApiOperation.GetBalances]: {
+ codec: codecForEmptyObject(),
+ handler: getBalances,
+ },
+ [WalletApiOperation.GetBalanceDetail]: {
+ codec: codecForGetBalanceDetailRequest(),
+ handler: getBalanceDetail,
+ },
+ [WalletApiOperation.SetExchangeTosAccepted]: {
+ codec: codecForAcceptExchangeTosRequest(),
+ handler: async (wex, req) => {
+ await acceptExchangeTermsOfService(wex, req.exchangeBaseUrl);
+ return {};
+ },
+ },
+ [WalletApiOperation.SetExchangeTosForgotten]: {
+ codec: codecForAcceptExchangeTosRequest(),
+ handler: async (wex, req) => {
+ await forgetExchangeTermsOfService(wex, req.exchangeBaseUrl);
+ return {};
+ },
+ },
+ [WalletApiOperation.AcceptBankIntegratedWithdrawal]: {
+ codec: codecForAcceptBankIntegratedWithdrawalRequest(),
+ handler: handleAcceptBankIntegratedWithdrawal,
+ },
+ [WalletApiOperation.ConfirmWithdrawal]: {
+ codec: codecForConfirmWithdrawalRequestRequest(),
+ handler: confirmWithdrawal,
+ },
+ [WalletApiOperation.PrepareBankIntegratedWithdrawal]: {
+ codec: codecForPrepareBankIntegratedWithdrawalRequest(),
+ handler: prepareBankIntegratedWithdrawal,
+ },
+ [WalletApiOperation.GetExchangeTos]: {
+ codec: codecForGetExchangeTosRequest(),
+ handler: handleGetExchangeTos,
+ },
+ [WalletApiOperation.SharePayment]: {
+ codec: codecForSharePaymentRequest(),
+ handler: handleSharePayment,
+ },
+ [WalletApiOperation.PrepareWithdrawExchange]: {
+ codec: codecForPrepareWithdrawExchangeRequest(),
+ handler: handlePrepareWithdrawExchange,
+ },
+ [WalletApiOperation.CheckPayForTemplate]: {
+ codec: codecForCheckPayTemplateRequest(),
+ handler: checkPayForTemplate,
+ },
+ [WalletApiOperation.PreparePayForUri]: {
+ codec: codecForPreparePayRequest(),
+ handler: (wex, req) => preparePayForUri(wex, req.talerPayUri),
+ },
+ [WalletApiOperation.PreparePayForTemplate]: {
+ codec: codecForPreparePayTemplateRequest(),
+ handler: preparePayForTemplate,
+ },
+ [WalletApiOperation.PreparePayForUriV2]: {
+ codec: codecForPreparePayRequest(),
+ handler: (wex, req) => preparePayForUriV2(wex, req.talerPayUri),
+ },
+ [WalletApiOperation.PreparePayForTemplateV2]: {
+ codec: codecForPreparePayTemplateRequest(),
+ handler: preparePayForTemplateV2,
+ },
+ [WalletApiOperation.GetQrCodesForPayto]: {
+ codec: codecForGetQrCodesForPaytoRequest(),
+ handler: handleGetQrCodesForPayto,
+ },
+ [WalletApiOperation.GetChoicesForPayment]: {
+ codec: codecForGetChoicesForPaymentRequest(),
+ handler: handleGetChoicesForPayment,
+ },
+ [WalletApiOperation.ConfirmPay]: {
+ codec: codecForConfirmPayRequest(),
+ handler: handleConfirmPay,
+ },
+ [WalletApiOperation.ListDiscounts]: {
+ codec: codecForListDiscountsRequest(),
+ handler: handleListDiscounts,
+ },
+ [WalletApiOperation.DeleteDiscount]: {
+ codec: codecForDeleteDiscountRequest(),
+ handler: handleDeleteDiscount,
+ },
+ [WalletApiOperation.ListSubscriptions]: {
+ codec: codecForListSubscriptionsRequest(),
+ handler: handleListSubscriptions,
+ },
+ [WalletApiOperation.DeleteSubscription]: {
+ codec: codecForDeleteSubscriptionRequest(),
+ handler: handleDeleteSubscription,
+ },
+ [WalletApiOperation.SuspendTransaction]: {
+ codec: codecForSuspendTransaction(),
+ handler: handleSuspendTransaction,
+ },
+ [WalletApiOperation.GetActiveTasks]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetActiveTasks,
+ },
+ [WalletApiOperation.FailTransaction]: {
+ codec: codecForFailTransactionRequest(),
+ handler: handleFailTransaction,
+ },
+ [WalletApiOperation.ResumeTransaction]: {
+ codec: codecForResumeTransaction(),
+ handler: async (wex, req) => {
+ await resumeTransaction(wex, req.transactionId);
+ return {};
+ },
+ },
+ [WalletApiOperation.DumpCoins]: {
+ codec: codecForEmptyObject(),
+ handler: dumpCoins,
+ },
+ [WalletApiOperation.SetCoinSuspended]: {
+ codec: codecForSetCoinSuspendedRequest(),
+ handler: async (wex, req) => {
+ await setCoinSuspended(wex, req.coinPub, req.suspended);
+ return {};
+ },
+ },
+ [WalletApiOperation.TestingGetSampleTransactions]: {
+ codec: codecForEmptyObject(),
+ handler: handleTestingGetSampleTransactions,
+ },
+ [WalletApiOperation.StartRefundQueryForUri]: {
+ codec: codecForPrepareRefundRequest(),
+ handler: (wex, req) => startRefundQueryForUri(wex, req.talerRefundUri),
+ },
+ [WalletApiOperation.StartRefundQuery]: {
+ codec: codecForStartRefundQueryRequest(),
+ handler: handleStartRefundQuery,
+ },
+ [WalletApiOperation.TestingWaitTransactionState]: {
+ codec: codecForAny(),
+ handler: async (wex, req) => {
+ await waitTransactionState(wex, req);
+ return {};
+ },
+ },
+ [WalletApiOperation.GetCurrencySpecification]: {
+ codec: codecForGetCurrencyInfoRequest(),
+ handler: handleGetCurrencySpecification,
+ },
+ [WalletApiOperation.HintNetworkAvailability]: {
+ codec: codecForHintNetworkAvailabilityRequest(),
+ handler: handleHintNetworkAvailability,
+ },
+ [WalletApiOperation.ConvertDepositAmount]: {
+ codec: codecForConvertAmountRequest,
+ handler: convertDepositAmount,
+ },
+ [WalletApiOperation.GetMaxDepositAmount]: {
+ codec: codecForGetMaxDepositAmountRequest(),
+ handler: getMaxDepositAmount,
+ },
+ [WalletApiOperation.GetMaxPeerPushDebitAmount]: {
+ codec: codecForGetMaxPeerPushDebitAmountRequest(),
+ handler: getMaxPeerPushDebitAmount,
+ },
+ [WalletApiOperation.CheckDeposit]: {
+ codec: codecForCheckDepositRequest(),
+ handler: checkDepositGroup,
+ },
+ [WalletApiOperation.CreateDepositGroup]: {
+ codec: codecForCreateDepositGroupRequest(),
+ handler: createDepositGroup,
+ },
+ [WalletApiOperation.DeleteTransaction]: {
+ codec: codecForDeleteTransactionRequest(),
+ handler: async (wex, req) => {
+ await deleteTransaction(wex, req.transactionId);
+ return {};
+ },
+ },
+ [WalletApiOperation.RetryTransaction]: {
+ codec: codecForRetryTransactionRequest(),
+ handler: async (wex, req) => {
+ await retryTransaction(wex, req.transactionId);
+ return {};
+ },
+ },
+ [WalletApiOperation.TestCrypto]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ return await wex.cryptoApi.hashString({ str: "hello world" });
+ },
+ },
+ [WalletApiOperation.ClearDb]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ await clearDatabase(wex.db.idbHandle());
+ wex.ws.flightRecords = [];
+ wex.ws.clearAllCaches();
+ await wex.taskScheduler.reload();
+ return {};
+ },
+ },
+ [WalletApiOperation.Recycle]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ throw Error("not implemented");
+ },
+ },
+ [WalletApiOperation.ExportDb]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ const dbDump = await exportDb(wex.ws.idbFactory);
+ return dbDump;
+ },
+ },
+ [WalletApiOperation.GetDepositWireTypes]: {
+ codec: codecForGetDepositWireTypesRequest(),
+ handler: handleGetDepositWireTypes,
+ },
+ [WalletApiOperation.GetDepositWireTypesForCurrency]: {
+ codec: codecForGetDepositWireTypesForCurrencyRequest(),
+ handler: handleGetDepositWireTypesForCurrency,
+ },
+ [WalletApiOperation.ListGlobalCurrencyExchanges]: {
+ codec: codecForEmptyObject(),
+ handler: handleListGlobalCurrencyExchanges,
+ },
+ [WalletApiOperation.ListGlobalCurrencyAuditors]: {
+ codec: codecForEmptyObject(),
+ handler: handleListGlobalCurrencyAuditors,
+ },
+ [WalletApiOperation.AddGlobalCurrencyExchange]: {
+ codec: codecForAddGlobalCurrencyExchangeRequest(),
+ handler: handleAddGlobalCurrencyExchange,
+ },
+ [WalletApiOperation.RemoveGlobalCurrencyExchange]: {
+ codec: codecForRemoveGlobalCurrencyExchangeRequest(),
+ handler: handleRemoveGlobalCurrencyExchange,
+ },
+ [WalletApiOperation.AddGlobalCurrencyAuditor]: {
+ codec: codecForAddGlobalCurrencyAuditorRequest(),
+ handler: handleAddGlobalCurrencyAuditor,
+ },
+ [WalletApiOperation.TestingWaitTasksDone]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ await waitTasksDone(wex);
+ return {};
+ },
+ },
+ [WalletApiOperation.TestingResetAllRetries]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ await retryAll(wex);
+ return {};
+ },
+ },
+ [WalletApiOperation.RemoveGlobalCurrencyAuditor]: {
+ codec: codecForRemoveGlobalCurrencyAuditorRequest(),
+ handler: handleRemoveGlobalCurrencyAuditor,
+ },
+ [WalletApiOperation.ImportDb]: {
+ codec: codecForImportDbRequest(),
+ handler: handleImportDb,
+ },
+ [WalletApiOperation.ImportDbFromFile]: {
+ codec: codecForImportDbFromFileRequest(),
+ handler: handleImportDbFromFile,
+ },
+ [WalletApiOperation.CheckPeerPushDebit]: {
+ codec: codecForCheckPeerPushDebitRequest(),
+ handler: checkPeerPushDebit,
+ },
+ [WalletApiOperation.CheckPeerPushDebitV2]: {
+ codec: codecForCheckPeerPushDebitRequest(),
+ handler: checkPeerPushDebitV2,
+ },
+ [WalletApiOperation.InitiatePeerPushDebit]: {
+ codec: codecForInitiatePeerPushDebitRequest(),
+ handler: initiatePeerPushDebit,
+ },
+ [WalletApiOperation.PreparePeerPushCredit]: {
+ codec: codecForPreparePeerPushCreditRequest(),
+ handler: preparePeerPushCredit,
+ },
+ [WalletApiOperation.ConfirmPeerPushCredit]: {
+ codec: codecForConfirmPeerPushPaymentRequest(),
+ handler: confirmPeerPushCredit,
+ },
+ [WalletApiOperation.CheckPeerPullCredit]: {
+ codec: codecForPreparePeerPullPaymentRequest(),
+ handler: checkPeerPullCredit,
+ },
+ [WalletApiOperation.InitiatePeerPullCredit]: {
+ codec: codecForInitiatePeerPullPaymentRequest(),
+ handler: initiatePeerPullPayment,
+ },
+ [WalletApiOperation.PreparePeerPullDebit]: {
+ codec: codecForCheckPeerPullPaymentRequest(),
+ handler: preparePeerPullDebit,
+ },
+ [WalletApiOperation.ConfirmPeerPullDebit]: {
+ codec: codecForAcceptPeerPullPaymentRequest(),
+ handler: confirmPeerPullDebit,
+ },
+ [WalletApiOperation.ApplyDevExperiment]: {
+ codec: codecForApplyDevExperiment(),
+ handler: async (wex, req) => {
+ await applyDevExperiment(wex, req.devExperimentUri);
+ return {};
+ },
+ },
+ [WalletApiOperation.Shutdown]: {
+ codec: codecForEmptyObject(),
+ handler: handleShutdown,
+ },
+ [WalletApiOperation.GetVersion]: {
+ codec: codecForEmptyObject(),
+ handler: handleGetVersion,
+ },
+ [WalletApiOperation.TestingWaitTransactionsFinal]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ await waitUntilAllTransactionsFinal(wex);
+ return {};
+ },
+ },
+ [WalletApiOperation.TestingWaitRefreshesFinal]: {
+ codec: codecForEmptyObject(),
+ handler: async (wex, req) => {
+ await waitUntilRefreshesDone(wex);
+ return {};
+ },
+ },
+ [WalletApiOperation.TestingSetTimetravel]: {
+ codec: codecForTestingSetTimetravelRequest(),
+ handler: async (wex, req) => {
+ await handleTestingSetTimetravel(wex, req);
+ return {};
+ },
+ },
+ [WalletApiOperation.DeleteExchange]: {
+ codec: codecForDeleteExchangeRequest(),
+ handler: handleDeleteExchange,
+ },
+ [WalletApiOperation.GetExchangeResources]: {
+ codec: codecForGetExchangeResourcesRequest(),
+ handler: async (wex, req) => {
+ return await getExchangeResources(wex, req.exchangeBaseUrl);
+ },
+ },
+ [WalletApiOperation.CanonicalizeBaseUrl]: {
+ codec: codecForCanonicalizeBaseUrlRequest(),
+ handler: handleCanonicalizeBaseUrl,
+ },
+ [WalletApiOperation.ForceRefresh]: {
+ codec: codecForForceRefreshRequest(),
+ handler: async (wex, req) => {
+ await forceRefresh(wex, req);
+ return {};
+ },
+ },
+ [WalletApiOperation.ListAssociatedRefreshes]: {
+ codec: codecForAny(),
+ handler: async (wex, req) => {
+ throw Error("not implemented");
+ },
+ },
+ [WalletApiOperation.GetBankingChoicesForPayto]: {
+ codec: codecForGetBankingChoicesForPaytoRequest(),
+ handler: handleGetBankingChoicesForPayto,
+ },
+ [WalletApiOperation.StartExchangeWalletKyc]: {
+ codec: codecForStartExchangeWalletKycRequest(),
+ handler: handleStartExchangeWalletKyc,
+ },
+ [WalletApiOperation.TestingWaitExchangeWalletKyc]: {
+ codec: codecForTestingWaitWalletKycRequest(),
+ handler: handleTestingWaitExchangeWalletKyc,
+ },
+ [WalletApiOperation.TestingPlanMigrateExchangeBaseUrl]: {
+ codec: codecForTestingPlanMigrateExchangeBaseUrlRequest(),
+ handler: handleTestingPlanMigrateExchangeBaseUrl,
+ },
+};
+
+/**
+ * Implementation of the "wallet-core" API.
+ */
+export async function dispatchRequestInternal(
+ wex: WalletExecutionContext,
+ operation: WalletApiOperation,
+ payload: unknown,
+): Promise<WalletCoreResponseType<typeof operation>> {
+ if (!wex.ws.initCalled && operation !== WalletApiOperation.InitWallet) {
+ throw Error(
+ `wallet must be initialized before running operation ${operation}`,
+ );
+ }
+
+ const h: HandlerWithValidator<any> = handlers[operation];
+
+ if (!h) {
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
+ {
+ operation,
+ },
+ "unknown operation",
+ );
+ }
+
+ const req = h.codec.decode(payload);
+ return await h.handler(wex, req);
+}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -29,89 +29,15 @@ import {
IDBDatabase,
} from "@gnu-taler/idb-bridge";
import {
- AbortTransactionRequest,
- AbsoluteTime,
- AcceptBankIntegratedWithdrawalRequest,
- AcceptManualWithdrawalRequest,
- AcceptManualWithdrawalResult,
- AcceptWithdrawalResponse,
- ActiveTask,
- AddBankAccountRequest,
- AddBankAccountResponse,
- AddExchangeRequest,
- AddExchangeResponse,
- AddGlobalCurrencyAuditorRequest,
- AddGlobalCurrencyExchangeRequest,
AmountJson,
- AmountString,
- Amounts,
AsyncCondition,
CancellationToken,
- CanonicalizeBaseUrlRequest,
- CanonicalizeBaseUrlResponse,
- Codec,
- CoinDumpJson,
- CoinStatus,
- CompleteBaseUrlRequest,
- CompleteBaseUrlResult,
- ConfirmPayRequest,
- ConfirmPayResult,
- ConvertIbanAccountFieldToPaytoRequest,
- ConvertIbanAccountFieldToPaytoResponse,
- ConvertIbanPaytoToAccountFieldRequest,
- ConvertIbanPaytoToAccountFieldResponse,
CoreApiResponse,
- CreateStoredBackupResponse,
- DeleteDiscountRequest,
- DeleteExchangeRequest,
- DeleteStoredBackupRequest,
- DeleteSubscriptionRequest,
DenominationInfo,
+ Cache,
Duration,
- EmptyObject,
- ExchangeEntryStatus,
- ExportDbToFileRequest,
- ExportDbToFileResponse,
- FailTransactionRequest,
FlightRecordEntry,
FlightRecordEvent,
- ForgetBankAccountRequest,
- GetActiveTasksResponse,
- GetBankAccountByIdRequest,
- GetBankAccountByIdResponse,
- GetBankingChoicesForPaytoRequest,
- GetBankingChoicesForPaytoResponse,
- GetChoicesForPaymentRequest,
- GetChoicesForPaymentResult,
- GetCurrencySpecificationRequest,
- GetCurrencySpecificationResponse,
- GetDefaultExchangesResponse,
- GetDepositWireTypesForCurrencyRequest,
- GetDepositWireTypesForCurrencyResponse,
- GetDepositWireTypesRequest,
- GetDepositWireTypesResponse,
- GetExchangeTosRequest,
- GetExchangeTosResult,
- GetPerformanceStatsRequest,
- GetPerformanceStatsResponse,
- GetQrCodesForPaytoRequest,
- GetQrCodesForPaytoResponse,
- HintNetworkAvailabilityRequest,
- HostPortPath,
- ImportDbFromFileRequest,
- ImportDbRequest,
- InitRequest,
- InitResponse,
- IntegrationTestArgs,
- IntegrationTestV2Args,
- ListBankAccountsRequest,
- ListBankAccountsResponse,
- ListDiscountsRequest,
- ListDiscountsResponse,
- ListGlobalCurrencyAuditorsResponse,
- ListGlobalCurrencyExchangesResponse,
- ListSubscriptionsRequest,
- ListSubscriptionsResponse,
Logger,
LongpollQueue,
NotificationType,
@@ -120,307 +46,54 @@ import {
ObservableHttpClientLibrary,
OpenedPromise,
PartialWalletRunConfig,
- PaytoType,
- Paytos,
PerformanceTable,
- PrepareWithdrawExchangeRequest,
- PrepareWithdrawExchangeResponse,
- RecoverStoredBackupRequest,
- RemoveGlobalCurrencyAuditorRequest,
- RemoveGlobalCurrencyExchangeRequest,
Result,
- RunFixupRequest,
- ScopeType,
- SharePaymentRequest,
- SharePaymentResult,
- StartRefundQueryRequest,
- StoredBackupList,
- SuspendTransactionRequest,
- TalerBankIntegrationHttpClient,
TalerError,
TalerErrorCode,
TalerExchangeHttpClient,
TalerMerchantInstanceHttpClient,
TalerPreciseTimestamp,
- TalerProtocolTimestamp,
- TalerUriAction,
- TalerUris,
- TestingCorruptWithdrawalCoinSelRequest,
- TestingGetDenomStatsRequest,
- TestingGetDenomStatsResponse,
- TestingGetDiagnosticsResponse,
- TestingGetFlightRecordsResponse,
- TestingGetReserveHistoryRequest,
- TestingSetTimetravelRequest,
- TestingWaitBalanceRequest,
- TestingWaitExchangeReadyRequest,
TimerAPI,
TimerGroup,
- TransactionType,
- TransactionsResponse,
- UpdateExchangeEntryRequest,
- ValidateIbanRequest,
- ValidateIbanResponse,
- WalletBankAccountInfo,
- WalletCoreVersion,
WalletNotification,
WalletRunConfig,
- WireTypeDetails,
- WithdrawTestBalanceRequest,
assertUnreachable,
- canonicalizeBaseUrl,
- checkDbInvariant,
- codecForAbortTransaction,
- codecForAcceptBankIntegratedWithdrawalRequest,
- codecForAcceptExchangeTosRequest,
- codecForAcceptManualWithdrawalRequest,
- codecForAcceptPeerPullPaymentRequest,
- codecForAddBankAccountRequest,
- codecForAddContactRequest,
- codecForAddExchangeRequest,
- codecForAddGlobalCurrencyAuditorRequest,
- codecForAddGlobalCurrencyExchangeRequest,
- codecForAddMailboxMessageRequest,
- codecForAny,
- codecForApplyDevExperiment,
- codecForCancelProgressToken,
- codecForCanonicalizeBaseUrlRequest,
- codecForCheckDepositRequest,
- codecForCheckPayTemplateRequest,
- codecForCheckPeerPullPaymentRequest,
- codecForCheckPeerPushDebitRequest,
- codecForCompleteBaseUrlRequest,
- codecForConfirmPayRequest,
- codecForConfirmPeerPushPaymentRequest,
- codecForConfirmWithdrawalRequestRequest,
- codecForConvertAmountRequest,
- codecForConvertIbanAccountFieldToPaytoRequest,
- codecForConvertIbanPaytoToAccountFieldRequest,
- codecForCreateDepositGroupRequest,
- codecForDeleteContactRequest,
- codecForDeleteDiscountRequest,
- codecForDeleteExchangeRequest,
- codecForDeleteMailboxMessageRequest,
- codecForDeleteStoredBackupRequest,
- codecForDeleteSubscriptionRequest,
- codecForDeleteTransactionRequest,
- codecForEmptyObject,
- codecForExportDbToFileRequest,
- codecForFailTransactionRequest,
- codecForForceRefreshRequest,
- codecForForgetBankAccount,
- codecForGetBalanceDetailRequest,
- codecForGetBankAccountByIdRequest,
- codecForGetBankingChoicesForPaytoRequest,
- codecForGetChoicesForPaymentRequest,
- codecForGetCurrencyInfoRequest,
- codecForGetDepositWireTypesForCurrencyRequest,
- codecForGetDepositWireTypesRequest,
- codecForGetDonauStatementsRequest,
- codecForGetExchangeEntryByUrlRequest,
- codecForGetExchangeResourcesRequest,
- codecForGetExchangeTosRequest,
- codecForGetMaxDepositAmountRequest,
- codecForGetMaxPeerPushDebitAmountRequest,
- codecForGetPerformanceStatsRequest,
- codecForGetQrCodesForPaytoRequest,
- codecForGetTransactionsV2Request,
- codecForGetWithdrawalDetailsForAmountRequest,
- codecForGetWithdrawalDetailsForUri,
- codecForHintNetworkAvailabilityRequest,
- codecForImportDbFromFileRequest,
- codecForImportDbRequest,
- codecForInitRequest,
- codecForInitiatePeerPullPaymentRequest,
- codecForInitiatePeerPushDebitRequest,
- codecForIntegrationTestArgs,
- codecForIntegrationTestV2Args,
- codecForListBankAccounts,
- codecForListDiscountsRequest,
- codecForListExchangesRequest,
- codecForListSubscriptionsRequest,
- codecForMailboxBaseUrl,
- codecForMailboxConfiguration,
- codecForPrepareBankIntegratedWithdrawalRequest,
- codecForPreparePayRequest,
- codecForPreparePayTemplateRequest,
- codecForPreparePeerPullPaymentRequest,
- codecForPreparePeerPushCreditRequest,
- codecForPrepareRefundRequest,
- codecForPrepareWithdrawExchangeRequest,
- codecForRecoverStoredBackupRequest,
- codecForRemoveGlobalCurrencyAuditorRequest,
- codecForRemoveGlobalCurrencyExchangeRequest,
- codecForResumeTransaction,
- codecForRetryProgressTokenNowRequest,
- codecForRetryTransactionRequest,
- codecForRunFixupRequest,
- codecForSendTalerUriMailboxMessageRequest,
- codecForSetCoinSuspendedRequest,
- codecForSetDonauRequest,
- codecForSharePaymentRequest,
- codecForStartExchangeWalletKycRequest,
- codecForStartRefundQueryRequest,
- codecForSuspendTransaction,
- codecForTaldirLookupRequest,
- codecForTaldirRegistrationCompletionRequest,
- codecForTaldirRegistrationRequest,
- codecForTestPayArgs,
- codecForTestingCorruptWithdrawalCoinSelRequest,
- codecForTestingGetDenomStatsRequest,
- codecForTestingGetReserveHistoryRequest,
- codecForTestingPlanMigrateExchangeBaseUrlRequest,
- codecForTestingSetTimetravelRequest,
- codecForTestingWaitBalanceRequest,
- codecForTestingWaitExchangeReadyRequest,
- codecForTestingWaitWalletKycRequest,
- codecForTransactionByIdRequest,
- codecForTransactionsRequest,
- codecForUpdateExchangeEntryRequest,
- codecForValidateIbanRequest,
- codecForWithdrawTestBalance,
- convertCHF_BBANtoIBAN,
- convertHUF_BBANtoIBAN,
- encodeCrock,
getErrorDetailFromException,
- getQrCodesForPayto,
- getRandomBytes,
j2s,
openPromise,
- parseIban,
performanceDelta,
performanceNow,
safeStringifyException,
- setDangerousTimetravel,
- setGlobalLogLevelFromString,
- stringifyScopeInfo,
- validateIban,
} from "@gnu-taler/taler-util";
-import {
- readSuccessResponseJsonOrThrow,
- type HttpRequestLibrary,
-} from "@gnu-taler/taler-util/http";
-import { getBalanceDetail, getBalances } from "./balance.js";
-import {
- getMaxDepositAmount,
- getMaxPeerPushDebitAmount,
-} from "./coinSelection.js";
-import { cancelableFetch } from "./common.js";
-import { addContact, deleteContact, listContacts } from "./contacts.js";
+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 {
- ConfigRecordKey,
- timestampAbsoluteFromDb,
- timestampProtocolToDb,
- WalletDenomination,
-} from "./db-common.js";
-import {
- CoinSourceType,
WalletIndexedDbStoresV1,
WalletIndexedDbTransaction,
applyFixups,
- clearDatabase,
- exportDb,
- importDb,
- openStoredBackupsDatabase,
openTalerDatabase,
- walletDbFixups,
} from "./db-indexeddb.js";
import { WalletDbTransaction } from "./dbtx.js";
import { IdbWalletTransaction } from "./dbtx-indexeddb.js";
-import {
- isCandidateWithdrawableDenomRec,
- isWithdrawableDenom,
-} from "./denominations.js";
import { UnverifiedDenomError } from "./denomSelection.js";
-import { checkDepositGroup, createDepositGroup } from "./deposits.js";
-import {
- DevExperimentHttpLib,
- DevExperimentState,
- applyDevExperiment,
-} from "./dev-experiments.js";
-import {
- handleGetDonau,
- handleGetDonauStatements,
- handleSetDonau,
-} from "./donau.js";
+import { DevExperimentHttpLib, DevExperimentState } from "./dev-experiments.js";
import {
OutdatedExchangeError,
ReadyExchangeSummary,
- acceptExchangeTermsOfService,
- deleteExchange,
fetchFreshExchange,
- forgetExchangeTermsOfService,
- getExchangeDetailedInfo,
- getExchangeDetailsInTx,
- getExchangeResources,
- getExchangeTos,
- handleStartExchangeWalletKyc,
- handleTestingPlanMigrateExchangeBaseUrl,
- handleTestingWaitExchangeState,
- handleTestingWaitExchangeWalletKyc,
- listExchanges,
- lookupExchangeByUri,
- markExchangeUsed,
- resetExchangeRetries,
- startUpdateExchangeEntry,
- waitReadyExchange,
} from "./exchanges.js";
-import { convertDepositAmount } from "./instructedAmountConversion.js";
-import {
- addMailboxMessage,
- createNewMailbox,
- deleteMailboxMessage,
- getMailbox,
- listMailboxMessages,
- refreshMailbox,
- sendTalerUriMessage,
-} from "./mailbox.js";
import {
ObservableDbAccess,
ObservableTaskScheduler,
observeTalerCrypto,
} from "./observable-wrappers.js";
-import {
- confirmPay,
- getChoicesForPayment,
- preparePayForTemplate,
- preparePayForTemplateV2,
- preparePayForUri,
- preparePayForUriV2,
- sharePayment,
- startQueryRefund,
- startRefundQueryForUri,
-} from "./pay-merchant.js";
-import {
- checkPeerPullCredit,
- initiatePeerPullPayment,
-} from "./pay-peer-pull-credit.js";
-import {
- confirmPeerPullDebit,
- preparePeerPullDebit,
-} from "./pay-peer-pull-debit.js";
-import {
- confirmPeerPushCredit,
- preparePeerPushCredit,
-} from "./pay-peer-push-credit.js";
-import {
- checkPeerPushDebit,
- checkPeerPushDebitV2,
- initiatePeerPushDebit,
-} from "./pay-peer-push-debit.js";
-import { checkPayForTemplate } from "./pay-template.js";
-import { fillDefaults } from "./preset-exchanges.js";
-import {
- ProgressContext,
- handleCancelProgressToken,
- handleRetryProgressTokenNow,
- withMaybeProgressContext,
-} from "./progress.js";
+import { ProgressContext } from "./progress.js";
import {
AfterCommitInfo,
DbAccess,
@@ -428,58 +101,8 @@ import {
TransactionAbortedError,
TriggerSpec,
} from "./query.js";
-import { forceRefresh } from "./refresh.js";
-import {
- TaskScheduler,
- TaskSchedulerImpl,
- convertTaskToTransactionId,
- getActiveTaskIds,
-} from "./shepherd.js";
-import {
- completeAliasRegistration,
- lookupAlias,
- registerAlias,
-} from "./taldir.js";
-import {
- WithdrawTestBalanceResult,
- runIntegrationTest,
- runIntegrationTest2,
- testPay,
- testingWaitBalance,
- waitTasksDone,
- waitTransactionState,
- waitUntilAllTransactionsFinal,
- waitUntilRefreshesDone,
- withdrawTestBalance,
-} from "./testing.js";
-import {
- deleteDiscount,
- deleteSubscription,
- listDiscounts,
- listSubscriptions,
-} from "./tokenFamilies.js";
-import {
- abortTransaction,
- deleteTransaction,
- failTransaction,
- getTransactionById,
- getTransactions,
- getTransactionsV2,
- parseTransactionIdentifier,
- rematerializeTransactions,
- restartAll as restartAllRunningTasks,
- resumeTransaction,
- retryAll,
- retryTransaction,
- suspendTransaction,
-} from "./transactions.js";
-import {
- WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION,
- WALLET_COREBANK_API_PROTOCOL_VERSION,
- WALLET_CORE_API_PROTOCOL_VERSION,
- WALLET_EXCHANGE_PROTOCOL_VERSION,
- WALLET_MERCHANT_PROTOCOL_VERSION,
-} from "./versions.js";
+import { TaskScheduler, TaskSchedulerImpl } from "./shepherd.js";
+import { rematerializeTransactions } from "./transactions.js";
import {
WalletApiOperation,
WalletCoreApiClient,
@@ -489,15 +112,7 @@ import {
WalletOperations,
walletApiExpectedErrors,
} from "./wallet-api-types.js";
-import {
- acceptBankIntegratedWithdrawal,
- confirmWithdrawal,
- createManualWithdrawal,
- getWithdrawalDetailsForAmount,
- getWithdrawalDetailsForUri,
- prepareBankIntegratedWithdrawal,
- updateWithdrawalDenomsForExchange,
-} from "./withdraw.js";
+import { updateWithdrawalDenomsForExchange } from "./withdraw.js";
const logger = new Logger("wallet.ts");
@@ -521,2382 +136,166 @@ export interface WalletExecutionContext {
*/
runWalletDbTx<T>(f: (tx: WalletDbTransaction) => Promise<T>): Promise<T>;
readonly ws: InternalWalletState;
- readonly cryptoApi: TalerCryptoInterface;
- readonly cancellationToken: CancellationToken;
- readonly http: HttpRequestLibrary;
- readonly db: DbAccess<typeof WalletIndexedDbStoresV1>;
- readonly oc: ObservabilityContext;
- readonly cts: CancellationToken.Source | undefined;
- readonly taskScheduler: TaskScheduler;
- readonly dbRetryState: DbRetryState;
-}
-
-export interface DbRetryState {
- retriedExchangeUpdate?: Set<string>;
-}
-
-export function walletExchangeClient(
- baseUrl: string,
- wex: WalletExecutionContext,
-): TalerExchangeHttpClient {
- return new TalerExchangeHttpClient(baseUrl, {
- httpClient: wex.http,
- cancelationToken: wex.cancellationToken,
- longPollQueue: wex.ws.longpollQueue,
- });
-}
-
-export function walletMerchantClient(
- baseUrl: string,
- wex: WalletExecutionContext,
-): TalerMerchantInstanceHttpClient {
- return new TalerMerchantInstanceHttpClient(
- baseUrl,
- wex.http,
- undefined,
- wex.cancellationToken,
- );
-}
-
-export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
-export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
-
-export type NotificationListener = (n: WalletNotification) => void;
-
-type CancelFn = () => void;
-
-/**
- * Incremented each time we want to re-materialize transactions.
- *
- * Bumped to 4 for BUG-084: after the status-enum digit fixup
- * (fixup20260718StatusEnumDigits) rewrites the raw records, transactionsMeta
- * and its byStatus index must be rebuilt from the corrected values.
- */
-const MATERIALIZED_TRANSACTIONS_VERSION = 4;
-
-async function migrateMaterializedTransactions(
- wex: WalletExecutionContext,
-): Promise<void> {
- await wex.runWalletDbTx(async (tx) => {
- const ver = await tx.getConfig(
- ConfigRecordKey.MaterializedTransactionsVersion,
- );
- if (ver) {
- if (ver.value == MATERIALIZED_TRANSACTIONS_VERSION) {
- return;
- }
- if (ver.value > MATERIALIZED_TRANSACTIONS_VERSION) {
- logger.error(
- "database is newer than code (materializedTransactionsVersion)",
- );
- return;
- }
- }
-
- await rematerializeTransactions(wex, tx);
-
- await tx.upsertConfig({
- key: ConfigRecordKey.MaterializedTransactionsVersion,
- value: MATERIALIZED_TRANSACTIONS_VERSION,
- });
- });
-}
-
-export async function getDenomInfo(
- wex: WalletExecutionContext,
- tx: WalletDbTransaction | LegacyWalletTxHandle,
- exchangeBaseUrl: string,
- denomPubHash: string,
-): Promise<DenominationInfo | undefined> {
- const key = `${exchangeBaseUrl}:${denomPubHash}`;
- return wex.ws.denomInfoCache.getOrPut(key, async () => {
- const d =
- "getDenomination" in tx
- ? await tx.getDenomination(exchangeBaseUrl, denomPubHash)
- : await tx.wtx.getDenomination(exchangeBaseUrl, denomPubHash);
- if (d != null) {
- return WalletDenomination.toDenomInfo(d);
- } else {
- return undefined;
- }
- });
-}
-
-/**
- * List bank accounts known to the wallet from
- * previous withdrawals.
- */
-async function handleListBankAccounts(
- wex: WalletExecutionContext,
- req: ListBankAccountsRequest,
-): Promise<ListBankAccountsResponse> {
- const accounts: WalletBankAccountInfo[] = [];
- const currency = req.currency;
- await wex.runWalletDbTx(async (tx) => {
- const knownAccounts = await tx.listBankAccounts();
- for (const r of knownAccounts) {
- if (currency && r.currencies && !r.currencies.includes(currency)) {
- continue;
- }
- const payto = Result.orUndefined(Paytos.fromString(r.paytoUri));
- if (payto) {
- accounts.push({
- bankAccountId: r.bankAccountId,
- paytoUri: r.paytoUri,
- label: r.label,
- kycCompleted: r.kycCompleted,
- currencies: r.currencies,
- });
- }
- }
- });
- return { accounts };
-}
-
-async function handleGetBankAccountById(
- wex: WalletExecutionContext,
- req: GetBankAccountByIdRequest,
-): Promise<GetBankAccountByIdResponse> {
- const acct = await wex.runWalletDbTx(async (tx) => {
- return tx.getBankAccount(req.bankAccountId);
- });
- if (!acct) {
- throw Error(`bank account ${req.bankAccountId} not found`);
- }
- return acct;
-}
-
-/**
- * Remove a known bank account.
- */
-async function forgetBankAccount(
- wex: WalletExecutionContext,
- bankAccountId: string,
-): Promise<void> {
- await wex.runWalletDbTx(async (tx) => {
- const account = await tx.getBankAccount(bankAccountId);
- if (!account) {
- throw Error(`account not found: ${bankAccountId}`);
- }
- await tx.deleteBankAccount(account.bankAccountId);
- });
- wex.ws.notify({
- type: NotificationType.BankAccountChange,
- bankAccountId,
- });
- return;
-}
-
-async function setCoinSuspended(
- wex: WalletExecutionContext,
- coinPub: string,
- suspended: boolean,
-): Promise<void> {
- await wex.runWalletDbTx(async (tx) => {
- const c = await tx.getCoin(coinPub);
- if (!c) {
- logger.warn(`coin ${coinPub} not found, won't suspend`);
- return;
- }
- const coinAvailability = await tx.getCoinAvailability(
- c.exchangeBaseUrl,
- c.denomPubHash,
- c.maxAge,
- );
- checkDbInvariant(
- !!coinAvailability,
- `no denom info for ${c.denomPubHash} age ${c.maxAge}`,
- );
- if (suspended) {
- if (c.status !== CoinStatus.Fresh) {
- return;
- }
- if (coinAvailability.freshCoinCount === 0) {
- throw Error(
- `invalid coin count ${coinAvailability.freshCoinCount} in DB`,
- );
- }
- coinAvailability.freshCoinCount--;
- c.status = CoinStatus.FreshSuspended;
- } else {
- if (c.status == CoinStatus.Dormant) {
- return;
- }
- coinAvailability.freshCoinCount++;
- c.status = CoinStatus.Fresh;
- }
- await tx.upsertCoin(c);
- await tx.upsertCoinAvailability(coinAvailability);
- });
-}
-
-/**
- * Dump the public information of coins we have in an easy-to-process format.
- */
-async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> {
- const coinsJson: CoinDumpJson = { coins: [] };
- logger.info("dumping coins");
- await wex.runWalletDbTx(async (tx) => {
- const coins = await tx.listAllCoins();
- for (const c of coins) {
- const denom = await tx.getDenomination(c.exchangeBaseUrl, c.denomPubHash);
- if (!denom) {
- logger.warn("no denom found for coin");
- continue;
- }
- const cs = c.coinSource;
- let refreshParentCoinPub: string | undefined;
- if (cs.type == CoinSourceType.Refresh) {
- refreshParentCoinPub = cs.oldCoinPub;
- }
- let withdrawalReservePub: string | undefined;
- if (cs.type == CoinSourceType.Withdraw) {
- withdrawalReservePub = cs.reservePub;
- }
- const denomInfo = await getDenomInfo(
- wex,
- tx,
- c.exchangeBaseUrl,
- c.denomPubHash,
- );
- if (!denomInfo) {
- logger.warn("no denomination found for coin");
- continue;
- }
- const historyRec = await tx.getCoinHistory(c.coinPub);
- coinsJson.coins.push({
- coinPub: c.coinPub,
- denomPub: denom.denomPub,
- denomPubHash: c.denomPubHash,
- denomValue: denom.value,
- exchangeBaseUrl: c.exchangeBaseUrl,
- refreshParentCoinPub: refreshParentCoinPub,
- withdrawalReservePub: withdrawalReservePub,
- coinStatus: c.status,
- ageCommitmentProof: c.ageCommitmentProof,
- history: historyRec ? historyRec.history : [],
- });
- }
- });
- return coinsJson;
-}
-
-/**
- * Get an API client from an internal wallet state object.
- */
-let id = 0;
-async function getClientFromWalletState(
- ws: InternalWalletState,
-): Promise<WalletCoreApiClient> {
- const client: WalletCoreApiClient = {
- async call(op, payload): Promise<any> {
- id = (id + 1) % (Number.MAX_SAFE_INTEGER - 100);
- const res = await dispatchWalletCoreApiRequest(
- ws,
- op,
- String(id),
- payload,
- );
- switch (res.type) {
- case "error":
- throw TalerError.fromUncheckedDetail(res.error);
- case "response":
- return res.result;
- }
- },
- callForResult: async function <Op extends keyof WalletOperations>(
- op: Op,
- payload: WalletCoreRequestType<Op>,
- ): Promise<Result<WalletCoreResponseType<Op>, WalletCoreErrorType<Op>>> {
- id = (id + 1) % (Number.MAX_SAFE_INTEGER - 100);
- const res = await dispatchWalletCoreApiRequest(
- ws,
- op,
- String(id),
- payload,
- );
- switch (res.type) {
- case "error": {
- if (op in walletApiExpectedErrors) {
- const errs = (
- walletApiExpectedErrors as {
- [x: string]: readonly TalerErrorCode[];
- }
- )[op];
- if (errs.includes(res.error.code)) {
- return Result.errorWithDetail(
- res.error.code as WalletCoreErrorType<Op>,
- res.error,
- );
- }
- }
- throw TalerError.fromUncheckedDetail(res.error);
- }
- case "response":
- return Result.of(res.result as WalletCoreResponseType<Op>);
- default:
- assertUnreachable(res);
- }
- },
- };
- return client;
-}
-
-async function createStoredBackup(
- wex: WalletExecutionContext,
-): Promise<CreateStoredBackupResponse> {
- const backup = await exportDb(wex.ws.idbFactory);
- const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
- const name = `backup-${new Date().getTime()}`;
- await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
- await tx.backupMeta.add({
- name,
- });
- await tx.backupData.add(backup, name);
- });
- return {
- name,
- };
-}
-
-async function listStoredBackups(
- wex: WalletExecutionContext,
-): Promise<StoredBackupList> {
- const storedBackups: StoredBackupList = {
- storedBackups: [],
- };
- const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
- await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
- await tx.backupMeta.iter().forEach((x) => {
- storedBackups.storedBackups.push({
- name: x.name,
- });
- });
- });
- return storedBackups;
-}
-
-async function deleteStoredBackup(
- wex: WalletExecutionContext,
- req: DeleteStoredBackupRequest,
-): Promise<void> {
- const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
- await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
- await tx.backupData.delete(req.name);
- await tx.backupMeta.delete(req.name);
- });
-}
-
-async function recoverStoredBackup(
- wex: WalletExecutionContext,
- req: RecoverStoredBackupRequest,
-): Promise<void> {
- logger.info(`Recovering stored backup ${req.name}`);
- const { name } = req;
- const backupsDb = await openStoredBackupsDatabase(wex.ws.idbFactory);
- const bd = await backupsDb.runAllStoresReadWriteTx({}, async (tx) => {
- const backupMeta = await tx.backupMeta.get(name);
- if (!backupMeta) {
- throw Error("backup not found");
- }
- const backupData = await tx.backupData.get(name);
- if (!backupData) {
- throw Error("no backup data (DB corrupt)");
- }
- return backupData;
- });
- logger.info(`backup found, now importing`);
- await importDb(wex.db.idbHandle(), bd);
- await wex.runLegacyWalletDbTx(async (tx) => {
- await rematerializeTransactions(wex, tx.wtx);
- // Clear fixups. Okay since they are idempotent.
- const fixups = await tx.fixups.getAll();
- for (const f of fixups) {
- await tx.fixups.delete(f.fixupName);
- }
- });
- logger.info(`import done`);
-}
-
-async function handlePrepareWithdrawExchange(
- wex: WalletExecutionContext,
- req: PrepareWithdrawExchangeRequest,
-): Promise<PrepareWithdrawExchangeResponse> {
- const parsedUri = Result.orUndefined(TalerUris.parse(req.talerUri));
- if (parsedUri?.type !== TalerUriAction.WithdrawExchange) {
- throw Error("expected a taler://withdraw-exchange URI");
- }
- const exchangeBaseUrl = parsedUri.exchangeBaseUrl;
-
- return await withMaybeProgressContext(
- wex,
- "prepareWithdrawExchange",
- req.progressToken,
- async (pc) => {
- if (pc) {
- pc.onRetryNow = async () => {
- await resetExchangeRetries(wex, exchangeBaseUrl);
- };
- }
- const exchange = await fetchFreshExchange(wex, exchangeBaseUrl, {
- noBail: pc != null,
- progressContext: pc,
- });
- if (parsedUri.amount) {
- const amt = Amounts.parseOrThrow(parsedUri.amount);
- if (amt.currency !== exchange.currency) {
- throw Error("mismatch of currency (URI vs exchange)");
- }
- }
- return {
- exchangeBaseUrl,
- amount: parsedUri.amount,
- };
- },
- );
-}
-
-async function handleSharePayment(
- wex: WalletExecutionContext,
- req: SharePaymentRequest,
-): Promise<SharePaymentResult> {
- return await sharePayment(wex, req.merchantBaseUrl, req.orderId);
-}
-
-async function handleDeleteStoredBackup(
- wex: WalletExecutionContext,
- req: DeleteStoredBackupRequest,
-): Promise<EmptyObject> {
- await deleteStoredBackup(wex, req);
- return {};
-}
-
-async function handleRecoverStoredBackup(
- wex: WalletExecutionContext,
- req: RecoverStoredBackupRequest,
-): Promise<EmptyObject> {
- await recoverStoredBackup(wex, req);
- 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,
-) {
- if (logger.shouldLogTrace()) {
- const initType = wex.ws.initCalled
- ? "repeat initialization"
- : "first initialization";
- logger.trace(`init request (${initType}): ${j2s(req)}`);
- }
-
- // Write to the DB to make sure that we're failing early in
- // case the DB is not writeable.
- try {
- await wex.runWalletDbTx(async (tx) => {
- tx.upsertConfig({
- key: ConfigRecordKey.LastInitInfo,
- value: timestampProtocolToDb(TalerProtocolTimestamp.now()),
- });
- });
- } catch (e) {
- logger.error(
- "error writing to database during initialization (while writing last error info)",
- );
- const err = getErrorDetailFromException(e);
- logger.error(`details: ${j2s(err)}`);
- throw TalerError.fromDetail(TalerErrorCode.WALLET_DB_UNAVAILABLE, {
- innerError: err,
- });
- }
- wex.ws.initWithConfig(applyRunConfigDefaults(req.config));
-
- if (wex.ws.config.testing.skipDefaults) {
- logger.trace("skipping defaults");
- } else {
- logger.trace("filling defaults");
- await fillDefaults(wex);
- }
-
- if (req.config?.logLevel) {
- setGlobalLogLevelFromString(req.config.logLevel);
- }
-
- await migrateMaterializedTransactions(wex);
-
- const resp: InitResponse = {
- versionInfo: await handleGetVersion(wex),
- };
-
- if (req.config?.lazyTaskLoop) {
- logger.trace("lazily starting task loop");
- } else {
- await wex.taskScheduler.ensureRunning();
- }
-
- wex.ws.initCalled = true;
- return resp;
-}
-
-async function handleWithdrawTestkudos(wex: WalletExecutionContext) {
- return await withdrawTestBalance(wex, {
- amount: "TESTKUDOS:10" as AmountString,
- corebankApiBaseUrl: "https://bank.test.taler.net/",
- exchangeBaseUrl: "https://exchange.test.taler.net/",
- });
-}
-
-async function handleWithdrawTestBalance(
- wex: WalletExecutionContext,
- req: WithdrawTestBalanceRequest,
-): Promise<WithdrawTestBalanceResult> {
- return await withdrawTestBalance(wex, req);
-}
-
-async function handleRunIntegrationTest(
- wex: WalletExecutionContext,
- req: IntegrationTestArgs,
-): Promise<EmptyObject> {
- await runIntegrationTest(wex, req);
- return {};
-}
-
-async function handleRunIntegrationTestV2(
- wex: WalletExecutionContext,
- req: IntegrationTestV2Args,
-): Promise<EmptyObject> {
- await runIntegrationTest2(wex, req);
- return {};
-}
-
-async function handleValidateIban(
- wex: WalletExecutionContext,
- req: ValidateIbanRequest,
-): Promise<ValidateIbanResponse> {
- const valRes = validateIban(req.iban);
- const resp: ValidateIbanResponse = {
- valid: valRes.type === "valid",
- };
- return resp;
-}
-
-async function handleAddExchange(
- wex: WalletExecutionContext,
- req: AddExchangeRequest,
-): Promise<AddExchangeResponse> {
- let exchangeBaseUrl: string;
- if (req.exchangeBaseUrl) {
- logger.warn(
- "Deprecated request property: AddExchangeRequest.exchangeBaseUrl",
- );
- exchangeBaseUrl = req.exchangeBaseUrl;
- } else if (req.uri) {
- if (req.uri.startsWith("taler")) {
- const p = Result.orUndefined(TalerUris.parse(req.uri));
- if (p?.type !== TalerUriAction.AddExchange) {
- throw Error("invalid taler://add-exchange/ URI");
- }
- exchangeBaseUrl = p.exchangeBaseUrl;
- } else if (req.allowCompletion) {
- const completeRes = await handleCompleteExchangeBaseUrl(wex, {
- url: req.uri,
- });
- if (completeRes.status != "ok") {
- throw TalerError.fromUncheckedDetail(completeRes.error);
- }
- exchangeBaseUrl = completeRes.completion;
- } else if (req.uri.startsWith("http")) {
- const canonUrl = canonicalizeBaseUrl(req.uri);
- if (req.uri != canonUrl) {
- throw Error("exchange base URL must be canonicalized");
- }
- exchangeBaseUrl = req.uri;
- } else {
- throw Error("AddExchangeRequest.uri must be http(s):// or taler://");
- }
- } else {
- throw Error(
- "AddExchangeRequest must either specify uri or exchangeBaseUrl",
- );
- }
-
- // FIXME: Check /config before adding the exchange entry.
-
- // FIXME: We probably should not wait synchronously here.
- await fetchFreshExchange(wex, exchangeBaseUrl, {});
- // Exchange has been explicitly added upon user request.
- // Thus, we mark it as "used".
- if (!req.ephemeral) {
- await wex.runWalletDbTx(async (tx) => {
- await markExchangeUsed(tx, exchangeBaseUrl);
- });
- }
- return {
- exchangeBaseUrl,
- };
-}
-
-async function handleUpdateExchangeEntry(
- wex: WalletExecutionContext,
- req: UpdateExchangeEntryRequest,
-): Promise<EmptyObject> {
- await startUpdateExchangeEntry(wex, req.exchangeBaseUrl, {
- forceUpdate: !!req.force,
- forceUnavailable: !!req.force,
- });
- return {};
-}
-
-async function handleTestingGetDenomStats(
- wex: WalletExecutionContext,
- req: TestingGetDenomStatsRequest,
-): Promise<TestingGetDenomStatsResponse> {
- const denomStats: TestingGetDenomStatsResponse = {
- numKnown: 0,
- numLost: 0,
- numOffered: 0,
- };
- await wex.runWalletDbTx(async (tx) => {
- const denoms = await tx.getDenominationsByExchange(req.exchangeBaseUrl);
- for (const d of denoms) {
- denomStats.numKnown++;
- if (d.isOffered) {
- denomStats.numOffered++;
- }
- if (d.isLost) {
- denomStats.numLost++;
- }
- }
- });
- return denomStats;
-}
-
-async function handleAddBankAccount(
- wex: WalletExecutionContext,
- req: AddBankAccountRequest,
-): Promise<AddBankAccountResponse> {
- if (Result.isError(Paytos.fromString(req.paytoUri))) {
- throw Error("invalid payto");
- }
- const acctId = await wex.runWalletDbTx(async (tx) => {
- let currencies = req.currencies;
- let myId: string;
- const oldAcct = await tx.getBankAccountByPaytoUri(req.paytoUri);
- if (req.replaceBankAccountId) {
- myId = req.replaceBankAccountId;
- } else if (oldAcct) {
- myId = oldAcct.bankAccountId;
- currencies = [
- ...new Set([...(req.currencies ?? []), ...(oldAcct.currencies ?? [])]),
- ];
- currencies.sort();
- } else {
- // New Account!
- myId = `acct:${encodeCrock(getRandomBytes(32))}`;
- }
- await tx.upsertBankAccount({
- bankAccountId: myId,
- paytoUri: req.paytoUri,
- label: req.label,
- currencies,
- kycCompleted: false,
- });
- return myId;
- });
- wex.ws.notify({
- type: NotificationType.BankAccountChange,
- bankAccountId: acctId,
- });
- return {
- bankAccountId: acctId,
- };
-}
-
-async function handleForgetBankAccount(
- wex: WalletExecutionContext,
- req: ForgetBankAccountRequest,
-): Promise<EmptyObject> {
- await forgetBankAccount(wex, req.bankAccountId);
- return {};
-}
-
-// FIXME: Doesn't have proper type!
-async function handleTestingGetReserveHistory(
- wex: WalletExecutionContext,
- req: TestingGetReserveHistoryRequest,
-): Promise<any> {
- const reserve = await wex.runWalletDbTx(async (tx) => {
- return tx.getReserveByReservePub(req.reservePub);
- });
- if (!reserve) {
- throw Error("no reserve pub found");
- }
- const sigResp = await wex.cryptoApi.signReserveHistoryReq({
- reservePriv: reserve.reservePriv,
- startOffset: 0,
- });
- const exchangeBaseUrl = req.exchangeBaseUrl;
- const url = new URL(`reserves/${req.reservePub}/history`, exchangeBaseUrl);
- const resp = await cancelableFetch(wex, url, {
- headers: { ["Taler-Reserve-History-Signature"]: sigResp.sig },
- });
- const historyJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
- return historyJson;
-}
-
-async function handleAcceptManualWithdrawal(
- wex: WalletExecutionContext,
- req: AcceptManualWithdrawalRequest,
-): Promise<AcceptManualWithdrawalResult> {
- const res = await createManualWithdrawal(wex, {
- amount: Amounts.parseOrThrow(req.amount),
- exchangeBaseUrl: req.exchangeBaseUrl,
- restrictAge: req.restrictAge,
- forceReservePriv: req.forceReservePriv,
- });
- return res;
-}
-
-async function handleGetExchangeTos(
- wex: WalletExecutionContext,
- req: GetExchangeTosRequest,
-): Promise<GetExchangeTosResult> {
- return await withMaybeProgressContext(
- wex,
- "getExchangeTos",
- req.progressToken,
- async (pc) => {
- return getExchangeTos(
- wex,
- req.exchangeBaseUrl,
- req.acceptedFormat,
- req.acceptLanguage,
- pc,
- );
- },
- );
-}
-
-async function handleGetQrCodesForPayto(
- wex: WalletExecutionContext,
- req: GetQrCodesForPaytoRequest,
-): Promise<GetQrCodesForPaytoResponse> {
- return {
- codes: getQrCodesForPayto(req.paytoUri),
- };
-}
-
-async function handleGetBankingChoicesForPayto(
- wex: WalletExecutionContext,
- req: GetBankingChoicesForPaytoRequest,
-): Promise<GetBankingChoicesForPaytoResponse> {
- const parsedPayto = Result.orUndefined(Paytos.fromString(req.paytoUri));
- if (!parsedPayto) {
- throw Error("invalid payto URI");
- }
- const amount = parsedPayto.params["amount"];
- if (!amount) {
- logger.warn("payto URI has no amount");
- return {
- choices: [],
- };
- }
- const currency = Amounts.currencyOf(amount);
- switch (currency) {
- case "KUDOS":
- return {
- choices: [
- {
- label: "Demobank Website",
- type: "link",
- uri: `https://bank.demo.taler.net/webui/#/transfer/${encodeURIComponent(
- req.paytoUri,
- )}`,
- },
- {
- label: "Demobank App",
- type: "link",
- uri: `https://bank.demo.taler.net/app/transfer/${encodeURIComponent(
- req.paytoUri,
- )}`,
- },
- ],
- };
- break;
- default:
- return {
- choices: [],
- };
- }
-}
-
-async function handleGetChoicesForPayment(
- wex: WalletExecutionContext,
- req: GetChoicesForPaymentRequest,
-): Promise<GetChoicesForPaymentResult> {
- return await getChoicesForPayment(wex, req.transactionId, req.forcedCoinSel);
-}
-
-async function handleConfirmPay(
- wex: WalletExecutionContext,
- req: ConfirmPayRequest,
-): Promise<ConfirmPayResult> {
- return await confirmPay(wex, {
- transactionId: req.transactionId,
- choiceIndex: req.choiceIndex,
- forcedCoinSel: undefined,
- sessionIdOverride: req.sessionId,
- useDonau: req.useDonau,
- noWait: req.noWait ?? wex.ws.devExperimentState.flagConfirmPayNoWait,
- });
-}
-
-async function handleListDiscounts(
- wex: WalletExecutionContext,
- req: ListDiscountsRequest,
-): Promise<ListDiscountsResponse> {
- return await listDiscounts(wex, req.tokenIssuePubHash, req.merchantBaseUrl);
-}
-
-async function handleDeleteDiscount(
- wex: WalletExecutionContext,
- req: DeleteDiscountRequest,
-): Promise<EmptyObject> {
- return await deleteDiscount(wex, req.tokenFamilyHash);
-}
-
-async function handleListSubscriptions(
- wex: WalletExecutionContext,
- req: ListSubscriptionsRequest,
-): Promise<ListSubscriptionsResponse> {
- return await listSubscriptions(
- wex,
- req.tokenIssuePubHash,
- req.merchantBaseUrl,
- );
-}
-
-async function handleDeleteSubscription(
- wex: WalletExecutionContext,
- req: DeleteSubscriptionRequest,
-): Promise<EmptyObject> {
- return await deleteSubscription(wex, req.tokenFamilyHash);
-}
-
-async function handleAbortTransaction(
- wex: WalletExecutionContext,
- req: AbortTransactionRequest,
-): Promise<EmptyObject> {
- await abortTransaction(wex, req.transactionId);
- return {};
-}
-
-async function handleSuspendTransaction(
- wex: WalletExecutionContext,
- req: SuspendTransactionRequest,
-): Promise<EmptyObject> {
- await suspendTransaction(wex, req.transactionId);
- return {};
-}
-
-async function handleGetActiveTasks(
- wex: WalletExecutionContext,
- req: EmptyObject,
-): Promise<GetActiveTasksResponse> {
- const allTasksId = (await getActiveTaskIds(wex.ws)).taskIds;
-
- const tasksInfo = await Promise.all(
- allTasksId.map(async (id) => {
- return await wex.runWalletDbTx(async (tx) => {
- return tx.getOperationRetry(id);
- });
- }),
- );
-
- const tasks = allTasksId.map((taskId, i): ActiveTask => {
- const transaction = convertTaskToTransactionId(taskId);
- const d = tasksInfo[i];
-
- const firstTry = !d
- ? undefined
- : timestampAbsoluteFromDb(d.retryInfo.firstTry);
- const nextTry = !d
- ? undefined
- : timestampAbsoluteFromDb(d.retryInfo.nextRetry);
- const counter = d?.retryInfo.retryCounter;
- const lastError = d?.lastError;
-
- return {
- taskId: taskId,
- retryCounter: counter,
- firstTry,
- nextTry,
- lastError,
- transaction,
- };
- });
- return { tasks };
-}
-
-async function handleFailTransaction(
- wex: WalletExecutionContext,
- req: FailTransactionRequest,
-): Promise<EmptyObject> {
- await failTransaction(wex, req.transactionId);
- return {};
-}
-
-async function handleTestingGetSampleTransactions(
- wex: WalletExecutionContext,
- req: EmptyObject,
-): Promise<TransactionsResponse> {
- // FIXME!
- return { transactions: [] };
- // These are out of date!
- //return { transactions: sampleWalletCoreTransactions };
-}
-
-async function handleStartRefundQuery(
- wex: WalletExecutionContext,
- req: StartRefundQueryRequest,
-): Promise<EmptyObject> {
- const txIdParsed = parseTransactionIdentifier(req.transactionId);
- if (!txIdParsed) {
- throw Error("invalid transaction ID");
- }
- if (txIdParsed.tag !== TransactionType.Payment) {
- throw Error("expected payment transaction ID");
- }
- await startQueryRefund(wex, txIdParsed.proposalId);
- return {};
-}
-
-async function handleHintNetworkAvailability(
- wex: WalletExecutionContext,
- req: HintNetworkAvailabilityRequest,
-): Promise<EmptyObject> {
- // If network was already available, don't do anything
- if (wex.ws.networkAvailable === req.isNetworkAvailable) {
- return {};
- }
- wex.ws.networkAvailable = req.isNetworkAvailable;
- // When network becomes available, restart tasks as they're blocked
- // waiting for the network.
- // When network goes down, restart tasks so they notice the network
- // is down and wait.
- await restartAllRunningTasks(wex);
- return {};
-}
-
-async function handleGetDepositWireTypes(
- wex: WalletExecutionContext,
- req: GetDepositWireTypesRequest,
-): Promise<GetDepositWireTypesResponse> {
- const wtSet: Set<string> = new Set();
- const wireTypeDetails: WireTypeDetails[] = [];
- const talerBankHostnames: string[] = [];
- await wex.runWalletDbTx(async (tx) => {
- const exchanges = await tx.getExchanges();
- for (const exchange of exchanges) {
- const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
- if (!det) {
- continue;
- }
- if (req.currency !== undefined && det.currency !== req.currency) {
- continue;
- }
- for (const acc of det.wireInfo.accounts) {
- let usable = true;
- for (const dr of acc.debit_restrictions) {
- if (dr.type === "deny") {
- usable = false;
- break;
- }
- }
- if (!usable) {
- continue;
- }
- const parsedPayto = Result.orUndefined(
- Paytos.fromString(acc.payto_uri),
- );
- if (!parsedPayto) {
- continue;
- }
- let preferredEntryType: "iban" | "bban" | undefined = undefined;
- if (parsedPayto.targetType === PaytoType.IBAN) {
- if (det.currency === "HUF") {
- preferredEntryType = "bban";
- } else {
- preferredEntryType = "iban";
- }
- }
- if (parsedPayto.targetType === PaytoType.TalerBank) {
- if (!talerBankHostnames.includes(parsedPayto.host)) {
- talerBankHostnames.push(parsedPayto.host);
- }
- }
- if (!wtSet.has(parsedPayto.targetType!)) {
- wtSet.add(parsedPayto.targetType!);
- wireTypeDetails.push({
- paymentTargetType: parsedPayto.targetType!,
- // Will possibly extended later by other exchanges
- // with the same wire type.
- talerBankHostnames,
- preferredEntryType,
- });
- }
- }
- }
- });
- return {
- wireTypeDetails,
- };
-}
-
-async function handleGetDepositWireTypesForCurrency(
- wex: WalletExecutionContext,
- req: GetDepositWireTypesForCurrencyRequest,
-): Promise<GetDepositWireTypesForCurrencyResponse> {
- const wtSet: Set<string> = new Set();
- const wireTypeDetails: WireTypeDetails[] = [];
- const talerBankHostnames: string[] = [];
- await wex.runWalletDbTx(async (tx) => {
- const exchanges = await tx.getExchanges();
- for (const exchange of exchanges) {
- const det = await getExchangeDetailsInTx(tx, exchange.baseUrl);
- if (!det) {
- continue;
- }
- if (det.currency !== req.currency) {
- continue;
- }
- for (const acc of det.wireInfo.accounts) {
- let usable = true;
- for (const dr of acc.debit_restrictions) {
- if (dr.type === "deny") {
- usable = false;
- break;
- }
- }
- if (!usable) {
- continue;
- }
- const parsedPayto = Result.orUndefined(
- Paytos.fromString(acc.payto_uri),
- );
- if (!parsedPayto) {
- continue;
- }
- if (parsedPayto.targetType === PaytoType.TalerBank) {
- if (!talerBankHostnames.includes(parsedPayto.host)) {
- talerBankHostnames.push(parsedPayto.host);
- }
- }
- if (!wtSet.has(parsedPayto.targetType!)) {
- wtSet.add(parsedPayto.targetType!);
- wireTypeDetails.push({
- paymentTargetType: parsedPayto.targetType!,
- // Will possibly extended later by other exchanges
- // with the same wire type.
- talerBankHostnames,
- });
- }
- }
- }
- });
- return {
- wireTypes: [...wtSet],
- wireTypeDetails,
- };
-}
-
-async function handleListGlobalCurrencyExchanges(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<ListGlobalCurrencyExchangesResponse> {
- const resp: ListGlobalCurrencyExchangesResponse = {
- exchanges: [],
- };
- await wex.runWalletDbTx(async (tx) => {
- const gceList = await tx.listGlobalCurrencyExchanges();
- for (const gce of gceList) {
- resp.exchanges.push({
- currency: gce.currency,
- exchangeBaseUrl: gce.exchangeBaseUrl,
- exchangeMasterPub: gce.exchangeMasterPub,
- });
- }
- });
- return resp;
-}
-
-async function handleListGlobalCurrencyAuditors(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<ListGlobalCurrencyAuditorsResponse> {
- const resp: ListGlobalCurrencyAuditorsResponse = {
- auditors: [],
- };
- await wex.runWalletDbTx(async (tx) => {
- const gcaList = await tx.listGlobalCurrencyAuditors();
- for (const gca of gcaList) {
- resp.auditors.push({
- currency: gca.currency,
- auditorBaseUrl: gca.auditorBaseUrl,
- auditorPub: gca.auditorPub,
- });
- }
- });
- return resp;
-}
-
-export async function handleAddGlobalCurrencyExchange(
- wex: WalletExecutionContext,
- req: AddGlobalCurrencyExchangeRequest,
-): Promise<EmptyObject> {
- await wex.runWalletDbTx(async (tx) => {
- const existingRec = await tx.getGlobalCurrencyExchange(
- req.currency,
- req.exchangeBaseUrl,
- req.exchangeMasterPub,
- );
- if (existingRec) {
- return;
- }
- wex.ws.exchangeCache.clear();
- // Re-scope currency info from exchange scope to global scope. The scope
- // string is the primary key, so this writes a new record and leaves the
- // exchange-scoped one, as it did before.
- const infoRec = await tx.getCurrencyInfo({
- type: ScopeType.Exchange,
- currency: req.currency,
- url: req.exchangeBaseUrl,
- });
- if (infoRec) {
- await tx.upsertCurrencyInfo({
- scopeInfo: { type: ScopeType.Global, currency: req.currency },
- currencySpec: infoRec.currencySpec,
- source: infoRec.source,
- });
- }
- await tx.addGlobalCurrencyExchange({
- currency: req.currency,
- exchangeBaseUrl: req.exchangeBaseUrl,
- exchangeMasterPub: req.exchangeMasterPub,
- });
- });
- return {};
-}
-
-async function handleRemoveGlobalCurrencyAuditor(
- wex: WalletExecutionContext,
- req: RemoveGlobalCurrencyAuditorRequest,
-): Promise<EmptyObject> {
- await wex.runWalletDbTx(async (tx) => {
- const existingRec = await tx.getGlobalCurrencyAuditor(
- req.currency,
- req.auditorBaseUrl,
- req.auditorPub,
- );
- if (!existingRec) {
- return;
- }
- checkDbInvariant(
- !!existingRec.id,
- `no global currency for ${req.currency}`,
- );
- await tx.deleteGlobalCurrencyAuditor(existingRec.id);
- wex.ws.exchangeCache.clear();
- });
- return {};
-}
-
-export async function handleRemoveGlobalCurrencyExchange(
- wex: WalletExecutionContext,
- req: RemoveGlobalCurrencyExchangeRequest,
-): Promise<EmptyObject> {
- await wex.runWalletDbTx(async (tx) => {
- const existingRec = await tx.getGlobalCurrencyExchange(
- req.currency,
- req.exchangeBaseUrl,
- req.exchangeMasterPub,
- );
- if (!existingRec) {
- return;
- }
- wex.ws.exchangeCache.clear();
- checkDbInvariant(
- !!existingRec.id,
- `no global exchange for ${req.currency}`,
- );
- await tx.deleteCurrencyInfo({
- type: ScopeType.Global,
- currency: req.currency,
- });
- await tx.deleteGlobalCurrencyExchange(existingRec.id);
- });
- return {};
-}
-
-async function handleAddGlobalCurrencyAuditor(
- wex: WalletExecutionContext,
- req: AddGlobalCurrencyAuditorRequest,
-): Promise<EmptyObject> {
- await wex.runWalletDbTx(async (tx) => {
- const existingRec = await tx.getGlobalCurrencyAuditor(
- req.currency,
- req.auditorBaseUrl,
- req.auditorPub,
- );
- if (existingRec) {
- return;
- }
- await tx.addGlobalCurrencyAuditor({
- currency: req.currency,
- auditorBaseUrl: req.auditorBaseUrl,
- auditorPub: req.auditorPub,
- });
- wex.ws.exchangeCache.clear();
- });
- return {};
-}
-
-async function handleShutdown(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<EmptyObject> {
- logger.info(`Shutdown requested`);
- wex.ws.stop();
- return {};
-}
-
-async function handleTestingSetTimetravel(
- wex: WalletExecutionContext,
- req: TestingSetTimetravelRequest,
-): Promise<EmptyObject> {
- setDangerousTimetravel(req.offsetMs);
- await wex.taskScheduler.reload();
- return {};
-}
-
-async function handleCanonicalizeBaseUrl(
- _wex: WalletExecutionContext,
- req: CanonicalizeBaseUrlRequest,
-): Promise<CanonicalizeBaseUrlResponse> {
- return {
- url: canonicalizeBaseUrl(req.url),
- };
-}
-
-async function handleDeleteExchange(
- wex: WalletExecutionContext,
- req: DeleteExchangeRequest,
-): Promise<EmptyObject> {
- await deleteExchange(wex, req);
- return {};
-}
-
-async function handleCreateStoredBackup(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<CreateStoredBackupResponse> {
- return await createStoredBackup(wex);
-}
-
-async function handleExportDbToFile(
- wex: WalletExecutionContext,
- req: ExportDbToFileRequest,
-): Promise<ExportDbToFileResponse> {
- const res = await wex.ws.dbImplementation.exportToFile(
- req.directory,
- req.stem,
- req.forceFormat,
- );
- return {
- path: res.path,
- };
-}
-
-async function handleImportDb(
- wex: WalletExecutionContext,
- req: ImportDbRequest,
-): Promise<EmptyObject> {
- // FIXME: This should atomically re-materialize transactions!
- await importDb(wex.db.idbHandle(), req.dump);
- await wex.runLegacyWalletDbTx(async (tx) => {
- await rematerializeTransactions(wex, tx.wtx);
- // Clear fixups. Okay since they are idempotent.
- const fixups = await tx.fixups.getAll();
- for (const f of fixups) {
- await tx.fixups.delete(f.fixupName);
- }
- });
- return {};
-}
-
-async function handleImportDbFromFile(
- wex: WalletExecutionContext,
- req: ImportDbFromFileRequest,
-): Promise<EmptyObject> {
- if (req.path.endsWith(".json")) {
- const dump = await wex.ws.dbImplementation.readBackupJson(req.path);
- return await handleImportDb(wex, {
- dump,
- });
- } else {
- throw Error("DB file import only supports .json files at the moment");
- }
-}
-
-async function handleAcceptBankIntegratedWithdrawal(
- wex: WalletExecutionContext,
- req: AcceptBankIntegratedWithdrawalRequest,
-): Promise<AcceptWithdrawalResponse> {
- return await acceptBankIntegratedWithdrawal(wex, {
- selectedExchange: req.exchangeBaseUrl,
- talerWithdrawUri: req.talerWithdrawUri,
- forcedDenomSel: req.forcedDenomSel,
- restrictAge: req.restrictAge,
- amount: req.amount,
- });
-}
-
-async function handleGetCurrencySpecification(
- wex: WalletExecutionContext,
- req: GetCurrencySpecificationRequest,
-): Promise<GetCurrencySpecificationResponse> {
- const spec = await wex.runWalletDbTx(async (tx) => {
- return tx.getCurrencyInfo(req.scope);
- });
- if (spec) {
- if (
- wex.ws.devExperimentState.fakeDemoShortcuts != null &&
- req.scope.type === ScopeType.Exchange &&
- req.scope.url === "https://exchange.demo.taler.net/"
- ) {
- spec.currencySpec.common_amounts =
- wex.ws.devExperimentState.fakeDemoShortcuts;
- }
- return {
- currencySpecification: spec.currencySpec,
- };
- }
- // Hard-coded mock for KUDOS and TESTKUDOS
- if (req.scope.currency === "KUDOS") {
- const kudosResp: GetCurrencySpecificationResponse = {
- currencySpecification: {
- name: "Kudos (Taler Demonstrator)",
- num_fractional_input_digits: 2,
- num_fractional_normal_digits: 2,
- num_fractional_trailing_zero_digits: 2,
- alt_unit_names: {
- "0": "ク",
- },
- },
- };
- return kudosResp;
- } else if (req.scope.currency === "TESTKUDOS") {
- const testkudosResp: GetCurrencySpecificationResponse = {
- currencySpecification: {
- name: "Test (Taler Unstable Demonstrator)",
- num_fractional_input_digits: 0,
- num_fractional_normal_digits: 0,
- num_fractional_trailing_zero_digits: 0,
- alt_unit_names: {
- "0": "テ",
- },
- },
- };
- return testkudosResp;
- }
- const defaultResp: GetCurrencySpecificationResponse = {
- currencySpecification: {
- name: req.scope.currency,
- num_fractional_input_digits: 2,
- num_fractional_normal_digits: 2,
- num_fractional_trailing_zero_digits: 2,
- alt_unit_names: {
- "0": req.scope.currency,
- },
- },
- };
- return defaultResp;
+ readonly cryptoApi: TalerCryptoInterface;
+ readonly cancellationToken: CancellationToken;
+ readonly http: HttpRequestLibrary;
+ readonly db: DbAccess<typeof WalletIndexedDbStoresV1>;
+ readonly oc: ObservabilityContext;
+ readonly cts: CancellationToken.Source | undefined;
+ readonly taskScheduler: TaskScheduler;
+ readonly dbRetryState: DbRetryState;
}
-export async function handleHintApplicationResumed(
- wex: WalletExecutionContext,
- req: EmptyObject,
-): Promise<EmptyObject> {
- logger.info("handling hintApplicationResumed");
- await restartAllRunningTasks(wex);
- return {};
+export interface DbRetryState {
+ retriedExchangeUpdate?: Set<string>;
}
-export async function handleTestingRunFixup(
+export function walletExchangeClient(
+ baseUrl: string,
wex: WalletExecutionContext,
- req: RunFixupRequest,
-): Promise<EmptyObject> {
- for (const fixup of walletDbFixups) {
- if (fixup.name != req.id) {
- continue;
- }
- await wex.runLegacyWalletDbTx(async (tx) => {
- await fixup.fn(tx);
- await rematerializeTransactions(wex, tx.wtx);
- });
- return {};
- }
- throw Error("fixup not found");
+): TalerExchangeHttpClient {
+ return new TalerExchangeHttpClient(baseUrl, {
+ httpClient: wex.http,
+ cancelationToken: wex.cancellationToken,
+ longPollQueue: wex.ws.longpollQueue,
+ });
}
-async function handleGetVersion(
+export function walletMerchantClient(
+ baseUrl: string,
wex: WalletExecutionContext,
-): Promise<WalletCoreVersion> {
- const result: WalletCoreVersion = {
- implementationSemver: walletCoreBuildInfo.implementationSemver,
- implementationGitHash: walletCoreBuildInfo.implementationGitHash,
- hash: undefined,
- version: WALLET_CORE_API_PROTOCOL_VERSION,
- exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
- merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
- bankConversionApiRange: WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION,
- bankIntegrationApiRange: TalerBankIntegrationHttpClient.PROTOCOL_VERSION,
- corebankApiRange: WALLET_COREBANK_API_PROTOCOL_VERSION,
- bank: TalerBankIntegrationHttpClient.PROTOCOL_VERSION,
- devMode: wex.ws.config.testing.devModeActive,
- };
- return result;
+): TalerMerchantInstanceHttpClient {
+ return new TalerMerchantInstanceHttpClient(
+ baseUrl,
+ wex.http,
+ undefined,
+ wex.cancellationToken,
+ );
}
-export async function handleConvertIbanAccountFieldToPayto(
- wex: WalletExecutionContext,
- req: ConvertIbanAccountFieldToPaytoRequest,
-): Promise<ConvertIbanAccountFieldToPaytoResponse> {
- const strippedInput = req.value.replace(/[- ]/g, "");
- if (req.currency === "HUF") {
- const iban = convertHUF_BBANtoIBAN(strippedInput);
- if (Result.isOk(iban)) {
- return {
- ok: true,
- paytoUri: `payto://iban/${iban.value}`,
- type: "iban",
- };
- } else {
- return {
- ok: false,
- };
- }
- }
- if (wex.ws.devExperimentState.fakeChfBban && req.currency === "CHF") {
- const iban = convertCHF_BBANtoIBAN(strippedInput);
- if (Result.isOk(iban)) {
- return {
- ok: true,
- paytoUri: `payto://iban/${iban.value}`,
- type: "iban",
- };
- } else {
- return {
- ok: false,
- };
- }
- }
- const parsedIban = parseIban(strippedInput);
- if (Result.isOk(parsedIban)) {
- return {
- ok: true,
- paytoUri: `payto://iban/${strippedInput}`,
- type: "iban",
- };
- } else {
- return {
- ok: false,
- };
- }
-}
+export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
+export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
-export async function handleConvertIbanPaytoToAccountField(
- wex: WalletExecutionContext,
- req: ConvertIbanPaytoToAccountFieldRequest,
-): Promise<ConvertIbanPaytoToAccountFieldResponse> {
- const payto = Result.unpack(Paytos.fromString(req.paytoUri));
- const iban = payto.normalizedPath;
- if (iban.startsWith("HU")) {
- let bban = iban.slice(4);
- if (bban.endsWith("00000000")) {
- bban = bban.slice(0, bban.length - 8);
- }
- return {
- type: "bban",
- value: bban,
- };
- } else if (wex.ws.devExperimentState.fakeChfBban && iban.startsWith("CH")) {
- return {
- type: "bban",
- value: iban.slice(4),
- };
- }
- return {
- type: "iban",
- value: iban,
- };
-}
+export type NotificationListener = (n: WalletNotification) => void;
-export async function handleGetFlightRecords(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<TestingGetFlightRecordsResponse> {
- return { flightRecords: wex.ws.flightRecords };
-}
+type CancelFn = () => void;
-export async function handleGetDefaultExchanges(
- wex: WalletExecutionContext,
- _req: EmptyObject,
-): Promise<GetDefaultExchangesResponse> {
- const defaultExchanges: GetDefaultExchangesResponse["defaultExchanges"] = [];
- const myExchanges = await listExchanges(wex, {
- filterByType: "prod",
- });
- for (const exch of myExchanges.exchanges) {
- switch (exch.exchangeEntryStatus) {
- case ExchangeEntryStatus.Ephemeral:
- continue;
- }
- if (exch.currency === "UNKNOWN") {
- continue;
- }
- defaultExchanges.push({
- currency: exch.currency,
- currencySpec: exch.currencySpec,
- talerUri: TalerUris.stringify({
- type: TalerUriAction.WithdrawExchange,
- exchangeBaseUrl: exch.exchangeBaseUrl as HostPortPath,
- }),
- });
- }
- if (wex.ws.devExperimentState.fakeDefaultExchangeDemo) {
- defaultExchanges.push({
- talerUri: "taler://withdraw-exchange/exchange.demo.taler.net/",
- currency: "KUDOS",
- currencySpec: {
- name: "Kudos",
- common_amounts: ["KUDOS:5", "KUDOS:10", "KUDOS:25", "KUDOS:50"],
- num_fractional_input_digits: 2,
- num_fractional_normal_digits: 2,
- num_fractional_trailing_zero_digits: 2,
- alt_unit_names: {
- "0": "ク",
- },
- },
- });
- }
- return {
- defaultExchanges,
- };
-}
+/**
+ * Incremented each time we want to re-materialize transactions.
+ *
+ * Bumped to 4 for BUG-084: after the status-enum digit fixup
+ * (fixup20260718StatusEnumDigits) rewrites the raw records, transactionsMeta
+ * and its byStatus index must be rebuilt from the corrected values.
+ */
+const MATERIALIZED_TRANSACTIONS_VERSION = 4;
-export async function handleTestingCorruptWithdrawalCoinSel(
+export async function migrateMaterializedTransactions(
wex: WalletExecutionContext,
- req: TestingCorruptWithdrawalCoinSelRequest,
-): Promise<EmptyObject> {
- const txId = parseTransactionIdentifier(req.transactionId);
- if (txId?.tag !== TransactionType.Withdrawal) {
- throw Error("expected withdrawal transaction ID");
- }
+): Promise<void> {
await wex.runWalletDbTx(async (tx) => {
- const wg = await tx.getWithdrawalGroup(txId.withdrawalGroupId);
- if (!wg) {
- return;
- }
- if (wg.denomsSel && (wg.denomsSel.selectedDenoms.length ?? 0) > 0) {
- wg.denomsSel.selectedDenoms[0].denomPubHash = encodeCrock(
- getRandomBytes(64),
- );
- await tx.upsertWithdrawalGroup(wg);
- }
- });
- return {};
-}
-
-export async function handleGetDiagnostics(
- wex: WalletExecutionContext,
- req: EmptyObject,
-): Promise<TestingGetDiagnosticsResponse> {
- const cnt: Record<string, number> = {};
- const exchangeEntries: TestingGetDiagnosticsResponse["exchangeEntries"] = [];
- await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
- cnt["coinAvailability"] = await tx.coinAvailability.count();
- cnt["coins"] = await tx.coins.count();
- cnt["denominationFamilies"] = await tx.denominationFamilies.count();
- cnt["denominations"] = await tx.denominations.count();
- cnt["exchangeDetails"] = await tx.exchangeDetails.count();
- cnt["exchangeSignKeys"] = await tx.exchangeSignKeys.count();
- cnt["exchanges"] = await tx.exchanges.count();
- for (const exch of await tx.exchanges.getAll()) {
- const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
- exch.baseUrl,
- );
- let numWithdrawableDenoms = 0;
- let numCandidateWithdrawableDenoms = 0;
- for (let i = 0; i < denoms.length; i++) {
- const d = denoms[i];
- if (isWithdrawableDenom(d)) {
- numWithdrawableDenoms++;
- }
- if (isCandidateWithdrawableDenomRec(d)) {
- numCandidateWithdrawableDenoms++;
- }
+ const ver = await tx.getConfig(
+ ConfigRecordKey.MaterializedTransactionsVersion,
+ );
+ if (ver) {
+ if (ver.value == MATERIALIZED_TRANSACTIONS_VERSION) {
+ return;
+ }
+ if (ver.value > MATERIALIZED_TRANSACTIONS_VERSION) {
+ logger.error(
+ "database is newer than code (materializedTransactionsVersion)",
+ );
+ return;
}
- exchangeEntries.push({
- exchangeBaseUrl: exch.baseUrl,
- numDenoms: denoms.length,
- numCandidateWithdrawableDenoms,
- numWithdrawableDenoms,
- });
}
- });
- return {
- version: 0,
- idbObjectStoreCounts: cnt,
- exchangeEntries,
- };
-}
-export async function handleTestingWaitExchangeReady(
- wex: WalletExecutionContext,
- req: TestingWaitExchangeReadyRequest,
-): Promise<EmptyObject> {
- await waitReadyExchange(wex, req.exchangeBaseUrl, {
- noBail: req.noBail,
- forceUpdate: req.forceUpdate,
- waitAutoRefresh: req.waitAutoRefresh,
- });
- return {};
-}
+ await rematerializeTransactions(wex, tx);
-export async function handleTestingWaitBalance(
- wex: WalletExecutionContext,
- req: TestingWaitBalanceRequest,
-): Promise<EmptyObject> {
- await testingWaitBalance(wex, req);
- return {};
+ await tx.upsertConfig({
+ key: ConfigRecordKey.MaterializedTransactionsVersion,
+ value: MATERIALIZED_TRANSACTIONS_VERSION,
+ });
+ });
}
-export async function handleGetPerformanceStats(
+export async function getDenomInfo(
wex: WalletExecutionContext,
- req: GetPerformanceStatsRequest,
-): Promise<GetPerformanceStatsResponse> {
- return {
- stats: PerformanceTable.limit(wex.ws.performanceStats, req.limit),
- };
-}
-
-interface HandlerWithValidator<Tag extends WalletApiOperation> {
- codec: Codec<WalletCoreRequestType<Tag>>;
- handler: (
- wex: WalletExecutionContext,
- req: WalletCoreRequestType<Tag>,
- ) => Promise<WalletCoreResponseType<Tag>>;
+ tx: WalletDbTransaction | LegacyWalletTxHandle,
+ exchangeBaseUrl: string,
+ denomPubHash: string,
+): Promise<DenominationInfo | undefined> {
+ const key = `${exchangeBaseUrl}:${denomPubHash}`;
+ return wex.ws.denomInfoCache.getOrPut(key, async () => {
+ const d =
+ "getDenomination" in tx
+ ? await tx.getDenomination(exchangeBaseUrl, denomPubHash)
+ : await tx.wtx.getDenomination(exchangeBaseUrl, denomPubHash);
+ if (d != null) {
+ return WalletDenomination.toDenomInfo(d);
+ } else {
+ return undefined;
+ }
+ });
}
-const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
- [WalletApiOperation.CancelProgressToken]: {
- codec: codecForCancelProgressToken(),
- handler: handleCancelProgressToken,
- },
- [WalletApiOperation.RetryProgressTokenNow]: {
- codec: codecForRetryProgressTokenNowRequest(),
- handler: handleRetryProgressTokenNow,
- },
- [WalletApiOperation.TestingWaitExchangeReady]: {
- codec: codecForTestingWaitExchangeReadyRequest(),
- handler: handleTestingWaitExchangeReady,
- },
- [WalletApiOperation.TestingWaitBalance]: {
- codec: codecForTestingWaitBalanceRequest(),
- handler: handleTestingWaitBalance,
- },
- [WalletApiOperation.TestingCorruptWithdrawalCoinSel]: {
- codec: codecForTestingCorruptWithdrawalCoinSelRequest(),
- handler: handleTestingCorruptWithdrawalCoinSel,
- },
- [WalletApiOperation.GetDefaultExchanges]: {
- codec: codecForEmptyObject(),
- handler: handleGetDefaultExchanges,
- },
- [WalletApiOperation.TestingGetFlightRecords]: {
- codec: codecForEmptyObject(),
- handler: handleGetFlightRecords,
- },
- [WalletApiOperation.GetDiagnostics]: {
- codec: codecForEmptyObject(),
- handler: handleGetDiagnostics,
- },
- [WalletApiOperation.ConvertIbanAccountFieldToPayto]: {
- codec: codecForConvertIbanAccountFieldToPaytoRequest(),
- handler: handleConvertIbanAccountFieldToPayto,
- },
- [WalletApiOperation.ConvertIbanPaytoToAccountField]: {
- codec: codecForConvertIbanPaytoToAccountFieldRequest(),
- handler: handleConvertIbanPaytoToAccountField,
- },
- [WalletApiOperation.TestingGetPerformanceStats]: {
- codec: codecForGetPerformanceStatsRequest(),
- handler: handleGetPerformanceStats,
- },
- [WalletApiOperation.TestingWaitExchangeState]: {
- codec: codecForAny(),
- handler: handleTestingWaitExchangeState,
- },
- [WalletApiOperation.GetDonauStatements]: {
- codec: codecForGetDonauStatementsRequest(),
- handler: handleGetDonauStatements,
- },
- [WalletApiOperation.GetDonau]: {
- codec: codecForEmptyObject(),
- handler: handleGetDonau,
- },
- [WalletApiOperation.SetDonau]: {
- codec: codecForSetDonauRequest(),
- handler: handleSetDonau,
- },
- [WalletApiOperation.AddContact]: {
- codec: codecForAddContactRequest(),
- handler: addContact,
- },
- [WalletApiOperation.DeleteContact]: {
- codec: codecForDeleteContactRequest(),
- handler: deleteContact,
- },
- [WalletApiOperation.GetContacts]: {
- codec: codecForEmptyObject(),
- handler: listContacts,
- },
- [WalletApiOperation.RegisterAlias]: {
- codec: codecForTaldirRegistrationRequest(),
- handler: registerAlias,
- },
- [WalletApiOperation.CompleteRegisterAlias]: {
- codec: codecForTaldirRegistrationCompletionRequest(),
- handler: completeAliasRegistration,
- },
- [WalletApiOperation.LookupAlias]: {
- codec: codecForTaldirLookupRequest(),
- handler: lookupAlias,
- },
- [WalletApiOperation.InitializeMailbox]: {
- codec: codecForMailboxBaseUrl(),
- handler: createNewMailbox,
- },
- [WalletApiOperation.GetMailbox]: {
- codec: codecForMailboxBaseUrl(),
- handler: getMailbox,
- },
- [WalletApiOperation.SendTalerUriMailboxMessage]: {
- codec: codecForSendTalerUriMailboxMessageRequest(),
- handler: sendTalerUriMessage,
- },
- [WalletApiOperation.AddMailboxMessage]: {
- codec: codecForAddMailboxMessageRequest(),
- handler: addMailboxMessage,
- },
- [WalletApiOperation.DeleteMailboxMessage]: {
- codec: codecForDeleteMailboxMessageRequest(),
- handler: deleteMailboxMessage,
- },
- [WalletApiOperation.GetMailboxMessages]: {
- codec: codecForEmptyObject(),
- handler: listMailboxMessages,
- },
- [WalletApiOperation.RefreshMailbox]: {
- codec: codecForMailboxConfiguration(),
- handler: refreshMailbox,
- },
- [WalletApiOperation.TestingGetDbStats]: {
- codec: codecForEmptyObject(),
- handler: async (wex) => {
- return wex.ws.dbImplementation.getStats();
- },
- },
- [WalletApiOperation.ExportDbToFile]: {
- codec: codecForExportDbToFileRequest(),
- handler: handleExportDbToFile,
- },
- [WalletApiOperation.HintApplicationResumed]: {
- codec: codecForEmptyObject(),
- handler: handleHintApplicationResumed,
- },
- [WalletApiOperation.TestingRunFixup]: {
- codec: codecForRunFixupRequest(),
- handler: handleTestingRunFixup,
- },
- [WalletApiOperation.AbortTransaction]: {
- codec: codecForAbortTransaction(),
- handler: handleAbortTransaction,
- },
- [WalletApiOperation.CreateStoredBackup]: {
- codec: codecForEmptyObject(),
- handler: handleCreateStoredBackup,
- },
- [WalletApiOperation.DeleteStoredBackup]: {
- codec: codecForDeleteStoredBackupRequest(),
- handler: handleDeleteStoredBackup,
- },
- [WalletApiOperation.ListStoredBackups]: {
- codec: codecForEmptyObject(),
- handler: listStoredBackups,
- },
- [WalletApiOperation.CompleteExchangeBaseUrl]: {
- codec: codecForCompleteBaseUrlRequest(),
- handler: handleCompleteExchangeBaseUrl,
- },
- [WalletApiOperation.SetWalletRunConfig]: {
- codec: codecForInitRequest(),
- handler: handleSetWalletRunConfig,
- },
- // Alias for SetWalletRunConfig
- [WalletApiOperation.InitWallet]: {
- codec: codecForInitRequest(),
- handler: handleSetWalletRunConfig,
- },
- [WalletApiOperation.RecoverStoredBackup]: {
- codec: codecForRecoverStoredBackupRequest(),
- handler: handleRecoverStoredBackup,
- },
- [WalletApiOperation.WithdrawTestkudos]: {
- codec: codecForEmptyObject(),
- handler: handleWithdrawTestkudos,
- },
- [WalletApiOperation.WithdrawTestBalance]: {
- codec: codecForWithdrawTestBalance(),
- handler: handleWithdrawTestBalance,
- },
- [WalletApiOperation.RunIntegrationTest]: {
- codec: codecForIntegrationTestArgs(),
- handler: handleRunIntegrationTest,
- },
- [WalletApiOperation.RunIntegrationTestV2]: {
- codec: codecForIntegrationTestV2Args(),
- handler: handleRunIntegrationTestV2,
- },
- [WalletApiOperation.ValidateIban]: {
- codec: codecForValidateIbanRequest(),
- handler: handleValidateIban,
- },
- [WalletApiOperation.TestPay]: {
- codec: codecForTestPayArgs(),
- handler: testPay,
- },
- [WalletApiOperation.GetTransactions]: {
- codec: codecForTransactionsRequest(),
- handler: getTransactions,
- },
- [WalletApiOperation.GetTransactionsV2]: {
- codec: codecForGetTransactionsV2Request(),
- handler: getTransactionsV2,
- },
- [WalletApiOperation.GetTransactionById]: {
- codec: codecForTransactionByIdRequest(),
- handler: getTransactionById,
- },
- [WalletApiOperation.AddExchange]: {
- codec: codecForAddExchangeRequest(),
- handler: handleAddExchange,
- },
- [WalletApiOperation.TestingPing]: {
- codec: codecForEmptyObject(),
- handler: async () => ({}),
- },
- [WalletApiOperation.UpdateExchangeEntry]: {
- codec: codecForUpdateExchangeEntryRequest(),
- handler: handleUpdateExchangeEntry,
- },
- [WalletApiOperation.TestingGetDenomStats]: {
- codec: codecForTestingGetDenomStatsRequest(),
- handler: handleTestingGetDenomStats,
- },
- [WalletApiOperation.ListExchanges]: {
- codec: codecForListExchangesRequest(),
- handler: listExchanges,
- },
- [WalletApiOperation.GetExchangeEntryByUrl]: {
- codec: codecForGetExchangeEntryByUrlRequest(),
- handler: lookupExchangeByUri,
- },
- [WalletApiOperation.GetExchangeDetailedInfo]: {
- codec: codecForGetExchangeEntryByUrlRequest(),
- handler: (wex, req) => getExchangeDetailedInfo(wex, req.exchangeBaseUrl),
- },
- [WalletApiOperation.ListBankAccounts]: {
- codec: codecForListBankAccounts(),
- handler: handleListBankAccounts,
- },
- [WalletApiOperation.GetBankAccountById]: {
- codec: codecForGetBankAccountByIdRequest(),
- handler: handleGetBankAccountById,
- },
- [WalletApiOperation.AddBankAccount]: {
- codec: codecForAddBankAccountRequest(),
- handler: handleAddBankAccount,
- },
- [WalletApiOperation.ForgetBankAccount]: {
- codec: codecForForgetBankAccount(),
- handler: handleForgetBankAccount,
- },
- [WalletApiOperation.GetWithdrawalDetailsForUri]: {
- codec: codecForGetWithdrawalDetailsForUri(),
- handler: (wex, req) =>
- getWithdrawalDetailsForUri(wex, req.talerWithdrawUri),
- },
- [WalletApiOperation.TestingGetReserveHistory]: {
- codec: codecForTestingGetReserveHistoryRequest(),
- handler: handleTestingGetReserveHistory,
- },
- [WalletApiOperation.AcceptManualWithdrawal]: {
- codec: codecForAcceptManualWithdrawalRequest(),
- handler: handleAcceptManualWithdrawal,
- },
- [WalletApiOperation.GetWithdrawalDetailsForAmount]: {
- codec: codecForGetWithdrawalDetailsForAmountRequest(),
- handler: getWithdrawalDetailsForAmount,
- },
- [WalletApiOperation.GetBalances]: {
- codec: codecForEmptyObject(),
- handler: getBalances,
- },
- [WalletApiOperation.GetBalanceDetail]: {
- codec: codecForGetBalanceDetailRequest(),
- handler: getBalanceDetail,
- },
- [WalletApiOperation.SetExchangeTosAccepted]: {
- codec: codecForAcceptExchangeTosRequest(),
- handler: async (wex, req) => {
- await acceptExchangeTermsOfService(wex, req.exchangeBaseUrl);
- return {};
- },
- },
- [WalletApiOperation.SetExchangeTosForgotten]: {
- codec: codecForAcceptExchangeTosRequest(),
- handler: async (wex, req) => {
- await forgetExchangeTermsOfService(wex, req.exchangeBaseUrl);
- return {};
- },
- },
- [WalletApiOperation.AcceptBankIntegratedWithdrawal]: {
- codec: codecForAcceptBankIntegratedWithdrawalRequest(),
- handler: handleAcceptBankIntegratedWithdrawal,
- },
- [WalletApiOperation.ConfirmWithdrawal]: {
- codec: codecForConfirmWithdrawalRequestRequest(),
- handler: confirmWithdrawal,
- },
- [WalletApiOperation.PrepareBankIntegratedWithdrawal]: {
- codec: codecForPrepareBankIntegratedWithdrawalRequest(),
- handler: prepareBankIntegratedWithdrawal,
- },
- [WalletApiOperation.GetExchangeTos]: {
- codec: codecForGetExchangeTosRequest(),
- handler: handleGetExchangeTos,
- },
- [WalletApiOperation.SharePayment]: {
- codec: codecForSharePaymentRequest(),
- handler: handleSharePayment,
- },
- [WalletApiOperation.PrepareWithdrawExchange]: {
- codec: codecForPrepareWithdrawExchangeRequest(),
- handler: handlePrepareWithdrawExchange,
- },
- [WalletApiOperation.CheckPayForTemplate]: {
- codec: codecForCheckPayTemplateRequest(),
- handler: checkPayForTemplate,
- },
- [WalletApiOperation.PreparePayForUri]: {
- codec: codecForPreparePayRequest(),
- handler: (wex, req) => preparePayForUri(wex, req.talerPayUri),
- },
- [WalletApiOperation.PreparePayForTemplate]: {
- codec: codecForPreparePayTemplateRequest(),
- handler: preparePayForTemplate,
- },
- [WalletApiOperation.PreparePayForUriV2]: {
- codec: codecForPreparePayRequest(),
- handler: (wex, req) => preparePayForUriV2(wex, req.talerPayUri),
- },
- [WalletApiOperation.PreparePayForTemplateV2]: {
- codec: codecForPreparePayTemplateRequest(),
- handler: preparePayForTemplateV2,
- },
- [WalletApiOperation.GetQrCodesForPayto]: {
- codec: codecForGetQrCodesForPaytoRequest(),
- handler: handleGetQrCodesForPayto,
- },
- [WalletApiOperation.GetChoicesForPayment]: {
- codec: codecForGetChoicesForPaymentRequest(),
- handler: handleGetChoicesForPayment,
- },
- [WalletApiOperation.ConfirmPay]: {
- codec: codecForConfirmPayRequest(),
- handler: handleConfirmPay,
- },
- [WalletApiOperation.ListDiscounts]: {
- codec: codecForListDiscountsRequest(),
- handler: handleListDiscounts,
- },
- [WalletApiOperation.DeleteDiscount]: {
- codec: codecForDeleteDiscountRequest(),
- handler: handleDeleteDiscount,
- },
- [WalletApiOperation.ListSubscriptions]: {
- codec: codecForListSubscriptionsRequest(),
- handler: handleListSubscriptions,
- },
- [WalletApiOperation.DeleteSubscription]: {
- codec: codecForDeleteSubscriptionRequest(),
- handler: handleDeleteSubscription,
- },
- [WalletApiOperation.SuspendTransaction]: {
- codec: codecForSuspendTransaction(),
- handler: handleSuspendTransaction,
- },
- [WalletApiOperation.GetActiveTasks]: {
- codec: codecForEmptyObject(),
- handler: handleGetActiveTasks,
- },
- [WalletApiOperation.FailTransaction]: {
- codec: codecForFailTransactionRequest(),
- handler: handleFailTransaction,
- },
- [WalletApiOperation.ResumeTransaction]: {
- codec: codecForResumeTransaction(),
- handler: async (wex, req) => {
- await resumeTransaction(wex, req.transactionId);
- return {};
- },
- },
- [WalletApiOperation.DumpCoins]: {
- codec: codecForEmptyObject(),
- handler: dumpCoins,
- },
- [WalletApiOperation.SetCoinSuspended]: {
- codec: codecForSetCoinSuspendedRequest(),
- handler: async (wex, req) => {
- await setCoinSuspended(wex, req.coinPub, req.suspended);
- return {};
- },
- },
- [WalletApiOperation.TestingGetSampleTransactions]: {
- codec: codecForEmptyObject(),
- handler: handleTestingGetSampleTransactions,
- },
- [WalletApiOperation.StartRefundQueryForUri]: {
- codec: codecForPrepareRefundRequest(),
- handler: (wex, req) => startRefundQueryForUri(wex, req.talerRefundUri),
- },
- [WalletApiOperation.StartRefundQuery]: {
- codec: codecForStartRefundQueryRequest(),
- handler: handleStartRefundQuery,
- },
- [WalletApiOperation.TestingWaitTransactionState]: {
- codec: codecForAny(),
- handler: async (wex, req) => {
- await waitTransactionState(wex, req);
- return {};
- },
- },
- [WalletApiOperation.GetCurrencySpecification]: {
- codec: codecForGetCurrencyInfoRequest(),
- handler: handleGetCurrencySpecification,
- },
- [WalletApiOperation.HintNetworkAvailability]: {
- codec: codecForHintNetworkAvailabilityRequest(),
- handler: handleHintNetworkAvailability,
- },
- [WalletApiOperation.ConvertDepositAmount]: {
- codec: codecForConvertAmountRequest,
- handler: convertDepositAmount,
- },
- [WalletApiOperation.GetMaxDepositAmount]: {
- codec: codecForGetMaxDepositAmountRequest(),
- handler: getMaxDepositAmount,
- },
- [WalletApiOperation.GetMaxPeerPushDebitAmount]: {
- codec: codecForGetMaxPeerPushDebitAmountRequest(),
- handler: getMaxPeerPushDebitAmount,
- },
- [WalletApiOperation.CheckDeposit]: {
- codec: codecForCheckDepositRequest(),
- handler: checkDepositGroup,
- },
- [WalletApiOperation.CreateDepositGroup]: {
- codec: codecForCreateDepositGroupRequest(),
- handler: createDepositGroup,
- },
- [WalletApiOperation.DeleteTransaction]: {
- codec: codecForDeleteTransactionRequest(),
- handler: async (wex, req) => {
- await deleteTransaction(wex, req.transactionId);
- return {};
- },
- },
- [WalletApiOperation.RetryTransaction]: {
- codec: codecForRetryTransactionRequest(),
- handler: async (wex, req) => {
- await retryTransaction(wex, req.transactionId);
- return {};
- },
- },
- [WalletApiOperation.TestCrypto]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- return await wex.cryptoApi.hashString({ str: "hello world" });
- },
- },
- [WalletApiOperation.ClearDb]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- await clearDatabase(wex.db.idbHandle());
- wex.ws.flightRecords = [];
- wex.ws.clearAllCaches();
- await wex.taskScheduler.reload();
- return {};
- },
- },
- [WalletApiOperation.Recycle]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- throw Error("not implemented");
- },
- },
- [WalletApiOperation.ExportDb]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- const dbDump = await exportDb(wex.ws.idbFactory);
- return dbDump;
- },
- },
- [WalletApiOperation.GetDepositWireTypes]: {
- codec: codecForGetDepositWireTypesRequest(),
- handler: handleGetDepositWireTypes,
- },
- [WalletApiOperation.GetDepositWireTypesForCurrency]: {
- codec: codecForGetDepositWireTypesForCurrencyRequest(),
- handler: handleGetDepositWireTypesForCurrency,
- },
- [WalletApiOperation.ListGlobalCurrencyExchanges]: {
- codec: codecForEmptyObject(),
- handler: handleListGlobalCurrencyExchanges,
- },
- [WalletApiOperation.ListGlobalCurrencyAuditors]: {
- codec: codecForEmptyObject(),
- handler: handleListGlobalCurrencyAuditors,
- },
- [WalletApiOperation.AddGlobalCurrencyExchange]: {
- codec: codecForAddGlobalCurrencyExchangeRequest(),
- handler: handleAddGlobalCurrencyExchange,
- },
- [WalletApiOperation.RemoveGlobalCurrencyExchange]: {
- codec: codecForRemoveGlobalCurrencyExchangeRequest(),
- handler: handleRemoveGlobalCurrencyExchange,
- },
- [WalletApiOperation.AddGlobalCurrencyAuditor]: {
- codec: codecForAddGlobalCurrencyAuditorRequest(),
- handler: handleAddGlobalCurrencyAuditor,
- },
- [WalletApiOperation.TestingWaitTasksDone]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- await waitTasksDone(wex);
- return {};
- },
- },
- [WalletApiOperation.TestingResetAllRetries]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- await retryAll(wex);
- return {};
- },
- },
- [WalletApiOperation.RemoveGlobalCurrencyAuditor]: {
- codec: codecForRemoveGlobalCurrencyAuditorRequest(),
- handler: handleRemoveGlobalCurrencyAuditor,
- },
- [WalletApiOperation.ImportDb]: {
- codec: codecForImportDbRequest(),
- handler: handleImportDb,
- },
- [WalletApiOperation.ImportDbFromFile]: {
- codec: codecForImportDbFromFileRequest(),
- handler: handleImportDbFromFile,
- },
- [WalletApiOperation.CheckPeerPushDebit]: {
- codec: codecForCheckPeerPushDebitRequest(),
- handler: checkPeerPushDebit,
- },
- [WalletApiOperation.CheckPeerPushDebitV2]: {
- codec: codecForCheckPeerPushDebitRequest(),
- handler: checkPeerPushDebitV2,
- },
- [WalletApiOperation.InitiatePeerPushDebit]: {
- codec: codecForInitiatePeerPushDebitRequest(),
- handler: initiatePeerPushDebit,
- },
- [WalletApiOperation.PreparePeerPushCredit]: {
- codec: codecForPreparePeerPushCreditRequest(),
- handler: preparePeerPushCredit,
- },
- [WalletApiOperation.ConfirmPeerPushCredit]: {
- codec: codecForConfirmPeerPushPaymentRequest(),
- handler: confirmPeerPushCredit,
- },
- [WalletApiOperation.CheckPeerPullCredit]: {
- codec: codecForPreparePeerPullPaymentRequest(),
- handler: checkPeerPullCredit,
- },
- [WalletApiOperation.InitiatePeerPullCredit]: {
- codec: codecForInitiatePeerPullPaymentRequest(),
- handler: initiatePeerPullPayment,
- },
- [WalletApiOperation.PreparePeerPullDebit]: {
- codec: codecForCheckPeerPullPaymentRequest(),
- handler: preparePeerPullDebit,
- },
- [WalletApiOperation.ConfirmPeerPullDebit]: {
- codec: codecForAcceptPeerPullPaymentRequest(),
- handler: confirmPeerPullDebit,
- },
- [WalletApiOperation.ApplyDevExperiment]: {
- codec: codecForApplyDevExperiment(),
- handler: async (wex, req) => {
- await applyDevExperiment(wex, req.devExperimentUri);
- return {};
- },
- },
- [WalletApiOperation.Shutdown]: {
- codec: codecForEmptyObject(),
- handler: handleShutdown,
- },
- [WalletApiOperation.GetVersion]: {
- codec: codecForEmptyObject(),
- handler: handleGetVersion,
- },
- [WalletApiOperation.TestingWaitTransactionsFinal]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- await waitUntilAllTransactionsFinal(wex);
- return {};
- },
- },
- [WalletApiOperation.TestingWaitRefreshesFinal]: {
- codec: codecForEmptyObject(),
- handler: async (wex, req) => {
- await waitUntilRefreshesDone(wex);
- return {};
- },
- },
- [WalletApiOperation.TestingSetTimetravel]: {
- codec: codecForTestingSetTimetravelRequest(),
- handler: async (wex, req) => {
- await handleTestingSetTimetravel(wex, req);
- return {};
- },
- },
- [WalletApiOperation.DeleteExchange]: {
- codec: codecForDeleteExchangeRequest(),
- handler: handleDeleteExchange,
- },
- [WalletApiOperation.GetExchangeResources]: {
- codec: codecForGetExchangeResourcesRequest(),
- handler: async (wex, req) => {
- return await getExchangeResources(wex, req.exchangeBaseUrl);
- },
- },
- [WalletApiOperation.CanonicalizeBaseUrl]: {
- codec: codecForCanonicalizeBaseUrlRequest(),
- handler: handleCanonicalizeBaseUrl,
- },
- [WalletApiOperation.ForceRefresh]: {
- codec: codecForForceRefreshRequest(),
- handler: async (wex, req) => {
- await forceRefresh(wex, req);
- return {};
- },
- },
- [WalletApiOperation.ListAssociatedRefreshes]: {
- codec: codecForAny(),
- handler: async (wex, req) => {
- throw Error("not implemented");
- },
- },
- [WalletApiOperation.GetBankingChoicesForPayto]: {
- codec: codecForGetBankingChoicesForPaytoRequest(),
- handler: handleGetBankingChoicesForPayto,
- },
- [WalletApiOperation.StartExchangeWalletKyc]: {
- codec: codecForStartExchangeWalletKycRequest(),
- handler: handleStartExchangeWalletKyc,
- },
- [WalletApiOperation.TestingWaitExchangeWalletKyc]: {
- codec: codecForTestingWaitWalletKycRequest(),
- handler: handleTestingWaitExchangeWalletKyc,
- },
- [WalletApiOperation.TestingPlanMigrateExchangeBaseUrl]: {
- codec: codecForTestingPlanMigrateExchangeBaseUrlRequest(),
- handler: handleTestingPlanMigrateExchangeBaseUrl,
- },
-};
-
/**
- * Implementation of the "wallet-core" API.
+ * Get an API client from an internal wallet state object.
*/
-async function dispatchRequestInternal(
- wex: WalletExecutionContext,
- operation: WalletApiOperation,
- payload: unknown,
-): Promise<WalletCoreResponseType<typeof operation>> {
- if (!wex.ws.initCalled && operation !== WalletApiOperation.InitWallet) {
- throw Error(
- `wallet must be initialized before running operation ${operation}`,
- );
- }
-
- const h: HandlerWithValidator<any> = handlers[operation];
-
- if (!h) {
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
- {
- operation,
- },
- "unknown operation",
- );
- }
-
- const req = h.codec.decode(payload);
- return await h.handler(wex, req);
+let id = 0;
+async function getClientFromWalletState(
+ ws: InternalWalletState,
+): Promise<WalletCoreApiClient> {
+ const client: WalletCoreApiClient = {
+ async call(op, payload): Promise<any> {
+ id = (id + 1) % (Number.MAX_SAFE_INTEGER - 100);
+ const res = await dispatchWalletCoreApiRequest(
+ ws,
+ op,
+ String(id),
+ payload,
+ );
+ switch (res.type) {
+ case "error":
+ throw TalerError.fromUncheckedDetail(res.error);
+ case "response":
+ return res.result;
+ }
+ },
+ callForResult: async function <Op extends keyof WalletOperations>(
+ op: Op,
+ payload: WalletCoreRequestType<Op>,
+ ): Promise<Result<WalletCoreResponseType<Op>, WalletCoreErrorType<Op>>> {
+ id = (id + 1) % (Number.MAX_SAFE_INTEGER - 100);
+ const res = await dispatchWalletCoreApiRequest(
+ ws,
+ op,
+ String(id),
+ payload,
+ );
+ switch (res.type) {
+ case "error": {
+ if (op in walletApiExpectedErrors) {
+ const errs = (
+ walletApiExpectedErrors as {
+ [x: string]: readonly TalerErrorCode[];
+ }
+ )[op];
+ if (errs.includes(res.error.code)) {
+ return Result.errorWithDetail(
+ res.error.code as WalletCoreErrorType<Op>,
+ res.error,
+ );
+ }
+ }
+ throw TalerError.fromUncheckedDetail(res.error);
+ }
+ case "response":
+ return Result.of(res.result as WalletCoreResponseType<Op>);
+ default:
+ assertUnreachable(res);
+ }
+ },
+ };
+ return client;
}
export function getObservedWalletExecutionContext(
@@ -3149,7 +548,9 @@ async function dispatchWalletCoreApiRequest(
}
}
-function applyRunConfigDefaults(wcp?: PartialWalletRunConfig): WalletRunConfig {
+export function applyRunConfigDefaults(
+ wcp?: PartialWalletRunConfig,
+): WalletRunConfig {
if (wcp?.features?.allowHttp != null) {
logger.warn(`allowHttp flag not supported anymore`);
}
@@ -3240,65 +641,6 @@ export class Wallet {
}
}
-export class Cache<T> {
- private map: Map<string, [AbsoluteTime, T]> = new Map();
-
- constructor(
- private maxCapacity: number,
- private cacheDuration: Duration,
- ) {}
-
- get(key: string): T | undefined {
- const r = this.map.get(key);
- if (!r) {
- return undefined;
- }
-
- if (AbsoluteTime.isExpired(r[0])) {
- this.map.delete(key);
- return undefined;
- }
-
- return r[1];
- }
-
- async getOrPut(key: string, lambda: () => Promise<T>): Promise<T>;
- async getOrPut(
- key: string,
- lambda: () => Promise<T | undefined>,
- ): Promise<T | undefined>;
- async getOrPut(
- key: string,
- lambda: () => Promise<T | undefined>,
- ): Promise<T | undefined> {
- const cached = this.get(key);
- if (cached != null) {
- return cached;
- } else {
- const computed = await lambda();
- if (computed != null) {
- this.put(key, computed);
- }
- return computed;
- }
- }
-
- clear(): void {
- this.map.clear();
- }
-
- put(key: string, value: T): void {
- if (this.map.size > this.maxCapacity) {
- this.map.clear();
- }
- const expiry = AbsoluteTime.addDuration(
- AbsoluteTime.now(),
- this.cacheDuration,
- );
- this.map.set(key, [expiry, value]);
- }
-}
-
/**
* Implementation of triggers for the wallet DB.
*/