2015-12-25 22:42:14 +01:00
|
|
|
/*
|
2019-12-02 00:42:40 +01:00
|
|
|
This file is part of GNU Taler
|
2019-11-30 00:36:20 +01:00
|
|
|
(C) 2015-2019 GNUnet e.V.
|
2015-12-25 22:42:14 +01:00
|
|
|
|
2019-12-02 00:42:40 +01:00
|
|
|
GNU Taler is free software; you can redistribute it and/or modify it under the
|
2015-12-25 22:42:14 +01:00
|
|
|
terms of the GNU General Public License as published by the Free Software
|
|
|
|
Foundation; either version 3, or (at your option) any later version.
|
|
|
|
|
2019-12-02 00:42:40 +01:00
|
|
|
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
|
2015-12-25 22:42:14 +01:00
|
|
|
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
|
2019-12-02 00:42:40 +01:00
|
|
|
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
2015-12-25 22:42:14 +01:00
|
|
|
*/
|
|
|
|
|
2016-01-05 15:42:46 +01:00
|
|
|
/**
|
|
|
|
* High-level wallet operations that should be indepentent from the underlying
|
|
|
|
* browser extension interface.
|
|
|
|
*/
|
|
|
|
|
2017-05-24 16:52:00 +02:00
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
2021-05-12 16:18:32 +02:00
|
|
|
import {
|
2022-06-10 13:03:47 +02:00
|
|
|
AbsoluteTime,
|
2022-03-10 16:30:24 +01:00
|
|
|
Amounts,
|
|
|
|
codecForAbortPayWithRefundRequest,
|
2022-01-16 21:47:43 +01:00
|
|
|
codecForAcceptBankIntegratedWithdrawalRequest,
|
|
|
|
codecForAcceptExchangeTosRequest,
|
|
|
|
codecForAcceptManualWithdrawalRequet,
|
2022-08-23 11:29:45 +02:00
|
|
|
codecForAcceptPeerPullPaymentRequest,
|
2022-07-12 17:41:14 +02:00
|
|
|
codecForAcceptPeerPushPaymentRequest,
|
2022-01-16 21:47:43 +01:00
|
|
|
codecForAcceptTipRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForAddExchangeRequest,
|
2022-10-08 20:56:57 +02:00
|
|
|
codecForAddKnownBankAccounts,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForAny,
|
2022-10-17 01:03:36 +02:00
|
|
|
codecForApplyDevExperiment,
|
2022-05-29 06:23:15 +02:00
|
|
|
codecForApplyRefundFromPurchaseIdRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForApplyRefundRequest,
|
2022-08-24 11:11:02 +02:00
|
|
|
codecForCheckPeerPullPaymentRequest,
|
2022-07-12 17:41:14 +02:00
|
|
|
codecForCheckPeerPushPaymentRequest,
|
2022-01-16 21:47:43 +01:00
|
|
|
codecForConfirmPayRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForCreateDepositGroupRequest,
|
|
|
|
codecForDeleteTransactionRequest,
|
|
|
|
codecForForceRefreshRequest,
|
2022-10-08 20:56:57 +02:00
|
|
|
codecForForgetKnownBankAccounts,
|
2022-08-08 18:53:04 +02:00
|
|
|
codecForGetContractTermsDetails,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForGetExchangeTosRequest,
|
|
|
|
codecForGetFeeForDeposit,
|
|
|
|
codecForGetWithdrawalDetailsForAmountRequest,
|
|
|
|
codecForGetWithdrawalDetailsForUri,
|
|
|
|
codecForImportDbRequest,
|
2022-08-23 11:29:45 +02:00
|
|
|
codecForInitiatePeerPullPaymentRequest,
|
2022-06-21 12:40:12 +02:00
|
|
|
codecForInitiatePeerPushPaymentRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForIntegrationTestArgs,
|
|
|
|
codecForListKnownBankAccounts,
|
2022-05-03 05:16:03 +02:00
|
|
|
codecForPrepareDepositRequest,
|
2022-06-10 13:03:47 +02:00
|
|
|
codecForPreparePayRequest,
|
2022-11-08 17:00:34 +01:00
|
|
|
codecForPreparePeerPullPaymentRequest,
|
|
|
|
codecForPreparePeerPushPaymentRequest,
|
2022-06-10 13:03:47 +02:00
|
|
|
codecForPrepareRefundRequest,
|
|
|
|
codecForPrepareTipRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForRetryTransactionRequest,
|
|
|
|
codecForSetCoinSuspendedRequest,
|
2022-10-17 01:03:36 +02:00
|
|
|
codecForSetDevModeRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForSetWalletDeviceIdRequest,
|
|
|
|
codecForTestPayArgs,
|
|
|
|
codecForTrackDepositGroupRequest,
|
2022-09-23 18:56:21 +02:00
|
|
|
codecForTransactionByIdRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForTransactionsRequest,
|
2023-01-11 14:19:24 +01:00
|
|
|
codecForUserAttentionByIdRequest,
|
|
|
|
codecForUserAttentionsRequest,
|
2022-03-10 16:30:24 +01:00
|
|
|
codecForWithdrawFakebankRequest,
|
|
|
|
codecForWithdrawTestBalance,
|
|
|
|
CoinDumpJson,
|
2022-10-17 01:03:36 +02:00
|
|
|
CoinRefreshRequest,
|
|
|
|
CoinStatus,
|
2022-06-10 13:03:47 +02:00
|
|
|
CoreApiResponse,
|
2022-09-23 18:56:21 +02:00
|
|
|
DenominationInfo,
|
2022-10-12 20:58:10 +02:00
|
|
|
DenomOperationMap,
|
2022-06-10 13:03:47 +02:00
|
|
|
Duration,
|
|
|
|
durationFromSpec,
|
2022-03-10 16:30:24 +01:00
|
|
|
durationMin,
|
2022-10-25 17:23:08 +02:00
|
|
|
ExchangeDetailedResponse,
|
2022-09-23 18:56:21 +02:00
|
|
|
ExchangeListItem,
|
2022-09-06 22:17:44 +02:00
|
|
|
ExchangesListResponse,
|
2022-10-17 01:03:36 +02:00
|
|
|
ExchangeTosStatusDetails,
|
2022-09-23 18:56:21 +02:00
|
|
|
FeeDescription,
|
2022-03-10 16:30:24 +01:00
|
|
|
GetExchangeTosResult,
|
2022-10-23 19:05:46 +02:00
|
|
|
InitResponse,
|
2022-03-10 16:30:24 +01:00
|
|
|
j2s,
|
|
|
|
KnownBankAccounts,
|
2022-10-08 20:56:57 +02:00
|
|
|
KnownBankAccountsInfo,
|
2022-03-10 16:30:24 +01:00
|
|
|
Logger,
|
2023-01-11 14:19:24 +01:00
|
|
|
ManualWithdrawalDetails,
|
2022-03-10 16:30:24 +01:00
|
|
|
NotificationType,
|
|
|
|
parsePaytoUri,
|
|
|
|
RefreshReason,
|
2022-06-10 13:03:47 +02:00
|
|
|
TalerErrorCode,
|
2022-09-23 18:56:21 +02:00
|
|
|
URL,
|
|
|
|
WalletCoreVersion,
|
|
|
|
WalletNotification,
|
2021-05-12 16:18:32 +02:00
|
|
|
} from "@gnu-taler/taler-util";
|
2022-05-03 00:21:13 +02:00
|
|
|
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
2022-03-23 21:24:23 +01:00
|
|
|
import {
|
|
|
|
CryptoDispatcher,
|
2022-06-10 13:03:47 +02:00
|
|
|
CryptoWorkerFactory,
|
2023-01-04 13:24:19 +01:00
|
|
|
} from "./crypto/workers/crypto-dispatcher.js";
|
2022-01-16 21:47:43 +01:00
|
|
|
import {
|
|
|
|
AuditorTrustRecord,
|
2023-01-11 14:19:24 +01:00
|
|
|
clearDatabase,
|
2022-01-16 21:47:43 +01:00
|
|
|
CoinSourceType,
|
2022-10-12 22:27:50 +02:00
|
|
|
ConfigRecordKey,
|
2022-09-14 21:27:03 +02:00
|
|
|
DenominationRecord,
|
2022-10-14 22:10:03 +02:00
|
|
|
ExchangeDetailsRecord,
|
2022-01-16 21:47:43 +01:00
|
|
|
exportDb,
|
|
|
|
importDb,
|
2022-06-10 13:03:47 +02:00
|
|
|
WalletStoresV1,
|
2022-01-16 21:47:43 +01:00
|
|
|
} from "./db.js";
|
2022-10-14 18:40:04 +02:00
|
|
|
import {
|
|
|
|
applyDevExperiment,
|
|
|
|
maybeInitDevMode,
|
|
|
|
setDevMode,
|
|
|
|
} from "./dev-experiments.js";
|
2022-10-12 20:58:10 +02:00
|
|
|
import { getErrorDetailFromException, TalerError } from "./errors.js";
|
2022-05-03 00:21:13 +02:00
|
|
|
import {
|
2022-09-23 18:56:21 +02:00
|
|
|
ActiveLongpollInfo,
|
2022-05-03 00:21:13 +02:00
|
|
|
ExchangeOperations,
|
|
|
|
InternalWalletState,
|
|
|
|
MerchantInfo,
|
|
|
|
MerchantOperations,
|
|
|
|
NotificationListener,
|
|
|
|
RecoupOperations,
|
2022-10-08 20:56:57 +02:00
|
|
|
RefreshOperations,
|
2022-05-03 00:21:13 +02:00
|
|
|
} from "./internal-wallet-state.js";
|
2023-01-11 14:19:24 +01:00
|
|
|
import {
|
|
|
|
getUserAttentions,
|
|
|
|
getUserAttentionsUnreadCount,
|
|
|
|
markAttentionRequestAsRead,
|
|
|
|
} from "./operations/attention.js";
|
2022-01-16 21:47:43 +01:00
|
|
|
import { exportBackup } from "./operations/backup/export.js";
|
2019-12-02 00:42:40 +01:00
|
|
|
import {
|
2021-02-08 15:38:34 +01:00
|
|
|
addBackupProvider,
|
|
|
|
codecForAddBackupProviderRequest,
|
2021-07-15 20:03:45 +02:00
|
|
|
codecForRemoveBackupProvider,
|
2021-07-07 18:28:02 +02:00
|
|
|
codecForRunBackupCycle,
|
2021-02-08 15:38:34 +01:00
|
|
|
getBackupInfo,
|
|
|
|
getBackupRecovery,
|
2022-09-20 21:44:21 +02:00
|
|
|
importBackupPlain,
|
2021-02-08 15:38:34 +01:00
|
|
|
loadBackupRecovery,
|
2021-06-25 13:27:06 +02:00
|
|
|
processBackupForProvider,
|
2021-07-15 20:03:45 +02:00
|
|
|
removeBackupProvider,
|
2022-06-10 13:03:47 +02:00
|
|
|
runBackupCycle,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/backup/index.js";
|
2022-01-16 21:47:43 +01:00
|
|
|
import { setWalletDeviceId } from "./operations/backup/state.js";
|
2021-06-17 17:40:42 +02:00
|
|
|
import { getBalances } from "./operations/balance.js";
|
2022-10-15 21:26:36 +02:00
|
|
|
import {
|
|
|
|
getExchangeTosStatus,
|
|
|
|
makeExchangeListItem,
|
|
|
|
runOperationWithErrorReporting,
|
|
|
|
} from "./operations/common.js";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
createDepositGroup,
|
2021-12-23 19:17:36 +01:00
|
|
|
getFeeForDeposit,
|
2022-05-03 05:16:03 +02:00
|
|
|
prepareDepositGroup,
|
2021-02-08 15:38:34 +01:00
|
|
|
processDepositGroup,
|
2022-06-10 13:03:47 +02:00
|
|
|
trackDepositGroup,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/deposits.js";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
acceptExchangeTermsOfService,
|
2022-01-24 18:12:12 +01:00
|
|
|
downloadTosFromAcceptedFormat,
|
2021-06-09 15:14:17 +02:00
|
|
|
getExchangeDetails,
|
2022-01-24 18:12:12 +01:00
|
|
|
getExchangeRequestTimeout,
|
2021-06-17 21:06:45 +02:00
|
|
|
getExchangeTrust,
|
2022-10-05 18:31:56 +02:00
|
|
|
provideExchangeRecordInTx,
|
2022-01-24 18:12:12 +01:00
|
|
|
updateExchangeFromUrl,
|
2022-09-05 18:12:30 +02:00
|
|
|
updateExchangeFromUrlHandler,
|
2022-06-10 13:03:47 +02:00
|
|
|
updateExchangeTermsOfService,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/exchanges.js";
|
2022-01-16 21:47:43 +01:00
|
|
|
import { getMerchantInfo } from "./operations/merchants.js";
|
2019-12-02 00:42:40 +01:00
|
|
|
import {
|
2022-10-08 20:56:57 +02:00
|
|
|
abortFailedPayWithRefund,
|
|
|
|
applyRefund,
|
|
|
|
applyRefundFromPurchaseId,
|
2019-12-02 00:42:40 +01:00
|
|
|
confirmPay,
|
2022-08-08 18:53:04 +02:00
|
|
|
getContractTermsDetails,
|
2021-02-08 15:38:34 +01:00
|
|
|
preparePayForUri,
|
2022-10-08 20:56:57 +02:00
|
|
|
prepareRefund,
|
|
|
|
processPurchase,
|
|
|
|
} from "./operations/pay-merchant.js";
|
2022-07-12 17:41:14 +02:00
|
|
|
import {
|
2022-08-24 11:11:02 +02:00
|
|
|
acceptPeerPullPayment,
|
2022-07-12 17:41:14 +02:00
|
|
|
acceptPeerPushPayment,
|
2022-08-24 11:11:02 +02:00
|
|
|
checkPeerPullPayment,
|
2022-07-12 17:41:14 +02:00
|
|
|
checkPeerPushPayment,
|
2022-11-02 17:02:42 +01:00
|
|
|
initiatePeerPullPayment,
|
2022-07-12 17:41:14 +02:00
|
|
|
initiatePeerToPeerPush,
|
2022-11-08 17:00:34 +01:00
|
|
|
preparePeerPullPayment,
|
|
|
|
preparePeerPushPayment,
|
2022-10-08 20:56:57 +02:00
|
|
|
} from "./operations/pay-peer.js";
|
2021-06-17 17:40:42 +02:00
|
|
|
import { getPendingOperations } from "./operations/pending.js";
|
2022-09-05 18:12:30 +02:00
|
|
|
import {
|
|
|
|
createRecoupGroup,
|
|
|
|
processRecoupGroup,
|
|
|
|
processRecoupGroupHandler,
|
|
|
|
} from "./operations/recoup.js";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
autoRefresh,
|
|
|
|
createRefreshGroup,
|
2022-06-10 13:03:47 +02:00
|
|
|
processRefreshGroup,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/refresh.js";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
runIntegrationTest,
|
|
|
|
testPay,
|
2022-06-10 13:03:47 +02:00
|
|
|
withdrawTestBalance,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/testing.js";
|
|
|
|
import { acceptTip, prepareTip, processTip } from "./operations/tip.js";
|
2021-06-15 18:52:43 +02:00
|
|
|
import {
|
|
|
|
deleteTransaction,
|
2022-09-16 16:06:55 +02:00
|
|
|
getTransactionById,
|
2021-06-15 18:52:43 +02:00
|
|
|
getTransactions,
|
2022-06-10 13:03:47 +02:00
|
|
|
retryTransaction,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/transactions.js";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
2022-08-09 15:00:45 +02:00
|
|
|
acceptWithdrawalFromUri,
|
|
|
|
createManualWithdrawal,
|
2021-02-08 15:38:34 +01:00
|
|
|
getExchangeWithdrawalInfo,
|
|
|
|
getWithdrawalDetailsForUri,
|
2022-08-24 19:44:24 +02:00
|
|
|
processWithdrawalGroup,
|
2021-06-17 17:40:42 +02:00
|
|
|
} from "./operations/withdraw.js";
|
2022-10-08 20:56:57 +02:00
|
|
|
import { PendingTaskInfo, PendingTaskType } from "./pending-types.js";
|
2021-06-17 17:40:42 +02:00
|
|
|
import { assertUnreachable } from "./util/assertUnreachable.js";
|
2022-10-12 20:58:10 +02:00
|
|
|
import {
|
|
|
|
createTimeline,
|
|
|
|
selectBestForOverlappingDenominations,
|
|
|
|
selectMinimumFee,
|
|
|
|
} from "./util/denominations.js";
|
2022-01-16 21:47:43 +01:00
|
|
|
import {
|
|
|
|
HttpRequestLibrary,
|
2022-06-10 13:03:47 +02:00
|
|
|
readSuccessResponseJsonOrThrow,
|
2022-01-16 21:47:43 +01:00
|
|
|
} from "./util/http.js";
|
2022-10-12 20:58:10 +02:00
|
|
|
import { checkDbInvariant } from "./util/invariants.js";
|
2021-06-17 21:06:45 +02:00
|
|
|
import {
|
|
|
|
AsyncCondition,
|
|
|
|
OpenedPromise,
|
2022-06-10 13:03:47 +02:00
|
|
|
openPromise,
|
2021-06-17 21:06:45 +02:00
|
|
|
} from "./util/promiseUtils.js";
|
2022-10-14 22:10:03 +02:00
|
|
|
import {
|
|
|
|
DbAccess,
|
|
|
|
GetReadOnlyAccess,
|
|
|
|
GetReadWriteAccess,
|
|
|
|
} from "./util/query.js";
|
2022-11-02 14:23:26 +01:00
|
|
|
import { OperationAttemptResult, RetryTags } from "./util/retries.js";
|
2022-04-11 20:10:16 +02:00
|
|
|
import { TimerAPI, TimerGroup } from "./util/timer.js";
|
2022-09-05 21:09:28 +02:00
|
|
|
import {
|
|
|
|
WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
|
|
|
WALLET_EXCHANGE_PROTOCOL_VERSION,
|
|
|
|
WALLET_MERCHANT_PROTOCOL_VERSION,
|
|
|
|
} from "./versions.js";
|
2022-10-17 01:03:36 +02:00
|
|
|
import {
|
|
|
|
WalletApiOperation,
|
|
|
|
WalletCoreApiClient,
|
|
|
|
WalletCoreResponseType,
|
|
|
|
} from "./wallet-api-types.js";
|
2017-12-12 21:54:14 +01:00
|
|
|
|
2021-05-20 13:14:47 +02:00
|
|
|
const builtinAuditors: AuditorTrustRecord[] = [
|
2017-03-24 16:59:23 +01:00
|
|
|
{
|
2021-05-20 13:14:47 +02:00
|
|
|
currency: "KUDOS",
|
|
|
|
auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0",
|
|
|
|
auditorBaseUrl: "https://auditor.demo.taler.net/",
|
|
|
|
uids: ["5P25XF8TVQP9AW6VYGY2KV47WT5Y3ZXFSJAA570GJPX5SVJXKBVG"],
|
2017-03-24 16:59:23 +01:00
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2022-08-16 17:59:17 +02:00
|
|
|
const builtinExchanges: string[] = ["https://exchange.demo.taler.net/"];
|
|
|
|
|
2019-11-21 23:09:43 +01:00
|
|
|
const logger = new Logger("wallet.ts");
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
2022-09-05 18:12:30 +02:00
|
|
|
* Call the right handler for a pending operation without doing
|
|
|
|
* any special error handling.
|
2021-06-15 18:52:43 +02:00
|
|
|
*/
|
2022-09-05 18:12:30 +02:00
|
|
|
async function callOperationHandler(
|
2021-06-15 18:52:43 +02:00
|
|
|
ws: InternalWalletState,
|
2021-06-25 13:27:06 +02:00
|
|
|
pending: PendingTaskInfo,
|
2021-06-15 18:52:43 +02:00
|
|
|
forceNow = false,
|
2022-09-16 17:21:54 +02:00
|
|
|
): Promise<OperationAttemptResult> {
|
2021-06-15 18:52:43 +02:00
|
|
|
switch (pending.type) {
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.ExchangeUpdate:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await updateExchangeFromUrlHandler(ws, pending.exchangeBaseUrl, {
|
2021-10-14 11:36:43 +02:00
|
|
|
forceNow,
|
2022-03-29 13:47:32 +02:00
|
|
|
});
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.Refresh:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await processRefreshGroup(ws, pending.refreshGroupId, {
|
|
|
|
forceNow,
|
|
|
|
});
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.Withdraw:
|
2022-09-13 15:28:34 +02:00
|
|
|
return await processWithdrawalGroup(ws, pending.withdrawalGroupId, {
|
|
|
|
forceNow,
|
|
|
|
});
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.TipPickup:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await processTip(ws, pending.tipId, { forceNow });
|
2022-10-08 20:56:57 +02:00
|
|
|
case PendingTaskType.Purchase:
|
|
|
|
return await processPurchase(ws, pending.proposalId, { forceNow });
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.Recoup:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await processRecoupGroupHandler(ws, pending.recoupGroupId, {
|
|
|
|
forceNow,
|
|
|
|
});
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.ExchangeCheckRefresh:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await autoRefresh(ws, pending.exchangeBaseUrl);
|
2022-03-28 23:59:16 +02:00
|
|
|
case PendingTaskType.Deposit: {
|
2022-09-05 18:12:30 +02:00
|
|
|
return await processDepositGroup(ws, pending.depositGroupId, {
|
2022-03-29 13:47:32 +02:00
|
|
|
forceNow,
|
|
|
|
});
|
2022-03-28 23:59:16 +02:00
|
|
|
}
|
2021-06-25 13:27:06 +02:00
|
|
|
case PendingTaskType.Backup:
|
2022-09-05 18:12:30 +02:00
|
|
|
return await processBackupForProvider(ws, pending.backupProviderBaseUrl);
|
2021-06-15 18:52:43 +02:00
|
|
|
default:
|
2022-09-05 18:12:30 +02:00
|
|
|
return assertUnreachable(pending);
|
|
|
|
}
|
2022-09-13 15:28:34 +02:00
|
|
|
throw Error(`not reached ${pending.type}`);
|
2022-09-05 18:12:30 +02:00
|
|
|
}
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Process pending operations.
|
|
|
|
*/
|
|
|
|
export async function runPending(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
forceNow = false,
|
|
|
|
): Promise<void> {
|
|
|
|
const pendingOpsResponse = await getPendingOperations(ws);
|
|
|
|
for (const p of pendingOpsResponse.pendingOperations) {
|
2022-03-18 15:32:41 +01:00
|
|
|
if (!forceNow && !AbsoluteTime.isExpired(p.timestampDue)) {
|
2021-06-15 18:52:43 +02:00
|
|
|
continue;
|
2020-07-11 09:56:07 +02:00
|
|
|
}
|
2022-09-23 18:56:21 +02:00
|
|
|
await runOperationWithErrorReporting(ws, p.id, async () => {
|
|
|
|
logger.trace(`running pending ${JSON.stringify(p, undefined, 2)}`);
|
|
|
|
return await callOperationHandler(ws, p, forceNow);
|
|
|
|
});
|
2019-11-30 00:36:20 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2019-11-21 23:09:43 +01:00
|
|
|
|
2021-06-22 13:52:28 +02:00
|
|
|
export interface RetryLoopOpts {
|
|
|
|
/**
|
|
|
|
* Stop when the number of retries is exceeded for any pending
|
|
|
|
* operation.
|
|
|
|
*/
|
|
|
|
maxRetries?: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop the retry loop when all lifeness-giving pending operations
|
|
|
|
* are done.
|
|
|
|
*
|
|
|
|
* Defaults to false.
|
|
|
|
*/
|
|
|
|
stopWhenDone?: boolean;
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2019-12-05 19:38:19 +01:00
|
|
|
|
2022-09-21 19:53:38 +02:00
|
|
|
export interface TaskLoopResult {
|
|
|
|
/**
|
|
|
|
* Was the maximum number of retries exceeded in a task?
|
|
|
|
*/
|
|
|
|
retriesExceeded: boolean;
|
|
|
|
}
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
2021-06-22 13:52:28 +02:00
|
|
|
* Main retry loop of the wallet.
|
|
|
|
*
|
|
|
|
* Looks up pending operations from the wallet, runs them, repeat.
|
2021-06-15 18:52:43 +02:00
|
|
|
*/
|
2021-06-22 13:52:28 +02:00
|
|
|
async function runTaskLoop(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
opts: RetryLoopOpts = {},
|
2022-09-21 19:53:38 +02:00
|
|
|
): Promise<TaskLoopResult> {
|
2022-09-23 21:47:38 +02:00
|
|
|
logger.info(`running task loop opts=${j2s(opts)}`);
|
2022-09-21 19:53:38 +02:00
|
|
|
let retriesExceeded = false;
|
2021-06-15 18:52:43 +02:00
|
|
|
for (let iteration = 0; !ws.stopped; iteration++) {
|
|
|
|
const pending = await getPendingOperations(ws);
|
|
|
|
logger.trace(`pending operations: ${j2s(pending)}`);
|
|
|
|
let numGivingLiveness = 0;
|
|
|
|
let numDue = 0;
|
2022-03-18 15:32:41 +01:00
|
|
|
let minDue: AbsoluteTime = AbsoluteTime.never();
|
2022-05-18 19:41:51 +02:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
for (const p of pending.pendingOperations) {
|
2021-06-22 13:52:28 +02:00
|
|
|
const maxRetries = opts.maxRetries;
|
|
|
|
|
|
|
|
if (maxRetries && p.retryInfo && p.retryInfo.retryCounter > maxRetries) {
|
2022-09-21 19:53:38 +02:00
|
|
|
retriesExceeded = true;
|
2021-06-22 13:52:28 +02:00
|
|
|
logger.warn(
|
2022-05-18 19:38:27 +02:00
|
|
|
`skipping, as ${maxRetries} retries are exceeded in an operation of type ${p.type}`,
|
2021-06-22 13:52:28 +02:00
|
|
|
);
|
2022-05-18 19:38:27 +02:00
|
|
|
continue;
|
2021-06-22 13:52:28 +02:00
|
|
|
}
|
2022-05-18 19:43:48 +02:00
|
|
|
if (p.givesLifeness) {
|
|
|
|
numGivingLiveness++;
|
|
|
|
}
|
2022-10-07 14:23:23 +02:00
|
|
|
if (!p.isDue) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
minDue = AbsoluteTime.min(minDue, p.timestampDue);
|
|
|
|
numDue++;
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2021-06-22 13:52:28 +02:00
|
|
|
|
2021-08-19 15:12:33 +02:00
|
|
|
if (opts.stopWhenDone && numGivingLiveness === 0 && iteration !== 0) {
|
2021-06-22 13:52:28 +02:00
|
|
|
logger.warn(`stopping, as no pending operations have lifeness`);
|
2022-09-21 19:53:38 +02:00
|
|
|
return {
|
|
|
|
retriesExceeded,
|
|
|
|
};
|
2021-06-22 13:52:28 +02:00
|
|
|
}
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
// Make sure that we run tasks that don't give lifeness at least
|
|
|
|
// one time.
|
|
|
|
if (iteration !== 0 && numDue === 0) {
|
|
|
|
// We've executed pending, due operations at least one.
|
|
|
|
// Now we don't have any more operations available,
|
|
|
|
// and need to wait.
|
|
|
|
|
|
|
|
// Wait for at most 5 seconds to the next check.
|
|
|
|
const dt = durationMin(
|
|
|
|
durationFromSpec({
|
|
|
|
seconds: 5,
|
|
|
|
}),
|
2022-03-18 15:32:41 +01:00
|
|
|
Duration.getRemaining(minDue),
|
2021-06-15 18:52:43 +02:00
|
|
|
);
|
|
|
|
logger.trace(`waiting for at most ${dt.d_ms} ms`);
|
|
|
|
const timeout = ws.timerGroup.resolveAfter(dt);
|
|
|
|
ws.notify({
|
|
|
|
type: NotificationType.WaitingForRetry,
|
|
|
|
numGivingLiveness,
|
2022-10-23 19:05:46 +02:00
|
|
|
numDue,
|
2021-06-15 18:52:43 +02:00
|
|
|
numPending: pending.pendingOperations.length,
|
|
|
|
});
|
|
|
|
// Wait until either the timeout, or we are notified (via the latch)
|
|
|
|
// that more work might be available.
|
|
|
|
await Promise.race([timeout, ws.latch.wait()]);
|
|
|
|
} else {
|
|
|
|
logger.trace(
|
|
|
|
`running ${pending.pendingOperations.length} pending operations`,
|
|
|
|
);
|
2020-09-03 17:08:26 +02:00
|
|
|
for (const p of pending.pendingOperations) {
|
2022-03-18 15:32:41 +01:00
|
|
|
if (!AbsoluteTime.isExpired(p.timestampDue)) {
|
2021-06-15 18:52:43 +02:00
|
|
|
continue;
|
2020-09-03 17:08:26 +02:00
|
|
|
}
|
2022-09-23 18:56:21 +02:00
|
|
|
await runOperationWithErrorReporting(ws, p.id, async () => {
|
|
|
|
logger.trace(`running pending ${JSON.stringify(p, undefined, 2)}`);
|
|
|
|
return await callOperationHandler(ws, p);
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
ws.notify({
|
|
|
|
type: NotificationType.PendingOperationProcessed,
|
2022-10-23 19:05:46 +02:00
|
|
|
id: p.id,
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
2019-11-21 23:09:43 +01:00
|
|
|
}
|
|
|
|
}
|
2016-05-24 01:18:23 +02:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
logger.trace("exiting wallet retry loop");
|
2022-09-21 19:53:38 +02:00
|
|
|
return {
|
|
|
|
retriesExceeded,
|
|
|
|
};
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2016-05-24 01:18:23 +02:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Insert the hard-coded defaults for exchanges, coins and
|
|
|
|
* auditors into the database, unless these defaults have
|
|
|
|
* already been applied.
|
|
|
|
*/
|
|
|
|
async function fillDefaults(ws: InternalWalletState): Promise<void> {
|
|
|
|
await ws.db
|
2022-10-05 18:31:56 +02:00
|
|
|
.mktx((x) => [x.config, x.auditorTrust, x.exchanges, x.exchangeDetails])
|
2021-06-15 18:52:43 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2022-10-05 18:31:56 +02:00
|
|
|
const appliedRec = await tx.config.get("currencyDefaultsApplied");
|
|
|
|
let alreadyApplied = appliedRec ? !!appliedRec.value : false;
|
|
|
|
if (alreadyApplied) {
|
2022-10-07 12:43:17 +02:00
|
|
|
logger.trace("defaults already applied");
|
2022-10-05 18:31:56 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-10-07 12:43:17 +02:00
|
|
|
logger.info("importing default exchanges and auditors");
|
2022-10-05 18:31:56 +02:00
|
|
|
for (const c of builtinAuditors) {
|
|
|
|
await tx.auditorTrust.put(c);
|
|
|
|
}
|
|
|
|
for (const baseUrl of builtinExchanges) {
|
|
|
|
const now = AbsoluteTime.now();
|
|
|
|
provideExchangeRecordInTx(ws, tx, baseUrl, now);
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-05 18:31:56 +02:00
|
|
|
await tx.config.put({
|
2022-10-12 22:27:50 +02:00
|
|
|
key: ConfigRecordKey.CurrencyDefaultsApplied,
|
2022-10-05 18:31:56 +02:00
|
|
|
value: true,
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
|
|
|
}
|
2017-03-24 16:59:23 +01:00
|
|
|
|
2022-10-14 22:10:03 +02:00
|
|
|
/**
|
|
|
|
* Get the exchange ToS in the requested format.
|
|
|
|
* Try to download in the accepted format not cached.
|
|
|
|
*/
|
2021-06-15 18:52:43 +02:00
|
|
|
async function getExchangeTos(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
exchangeBaseUrl: string,
|
2021-10-13 19:26:18 +02:00
|
|
|
acceptedFormat?: string[],
|
2021-06-15 18:52:43 +02:00
|
|
|
): Promise<GetExchangeTosResult> {
|
2022-03-29 13:47:32 +02:00
|
|
|
// FIXME: download ToS in acceptable format if passed!
|
|
|
|
const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
|
2022-10-14 22:10:03 +02:00
|
|
|
const tosDetails = await ws.db
|
|
|
|
.mktx((x) => [x.exchangeTos])
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return await getExchangeTosStatusDetails(tx, exchangeDetails);
|
|
|
|
});
|
|
|
|
const content = tosDetails.content;
|
|
|
|
const currentEtag = tosDetails.currentVersion;
|
|
|
|
const contentType = tosDetails.contentType;
|
2021-10-14 11:36:43 +02:00
|
|
|
if (
|
|
|
|
content === undefined ||
|
|
|
|
currentEtag === undefined ||
|
|
|
|
contentType === undefined
|
|
|
|
) {
|
2021-06-15 18:52:43 +02:00
|
|
|
throw Error("exchange is in invalid state");
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|
2022-03-10 16:30:24 +01:00
|
|
|
if (
|
|
|
|
acceptedFormat &&
|
|
|
|
acceptedFormat.findIndex((f) => f === contentType) !== -1
|
|
|
|
) {
|
2022-01-24 18:12:12 +01:00
|
|
|
return {
|
2022-10-14 22:10:03 +02:00
|
|
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
2022-01-24 18:12:12 +01:00
|
|
|
currentEtag,
|
|
|
|
content,
|
|
|
|
contentType,
|
2022-10-15 12:59:26 +02:00
|
|
|
tosStatus: getExchangeTosStatus(exchangeDetails),
|
2022-01-24 18:12:12 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2022-03-10 16:30:24 +01:00
|
|
|
const tosDownload = await downloadTosFromAcceptedFormat(
|
|
|
|
ws,
|
|
|
|
exchangeBaseUrl,
|
|
|
|
getExchangeRequestTimeout(),
|
|
|
|
acceptedFormat,
|
|
|
|
);
|
2022-01-24 18:12:12 +01:00
|
|
|
|
|
|
|
if (tosDownload.tosContentType === contentType) {
|
|
|
|
return {
|
2022-10-14 22:10:03 +02:00
|
|
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
2022-01-24 18:12:12 +01:00
|
|
|
currentEtag,
|
|
|
|
content,
|
|
|
|
contentType,
|
2022-10-15 12:59:26 +02:00
|
|
|
tosStatus: getExchangeTosStatus(exchangeDetails),
|
2022-01-24 18:12:12 +01:00
|
|
|
};
|
|
|
|
}
|
2022-10-14 22:10:03 +02:00
|
|
|
|
2022-03-10 16:30:24 +01:00
|
|
|
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
|
2022-01-24 18:12:12 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
return {
|
2022-10-14 22:10:03 +02:00
|
|
|
acceptedEtag: exchangeDetails.tosAccepted?.etag,
|
2022-01-24 18:12:12 +01:00
|
|
|
currentEtag: tosDownload.tosEtag,
|
|
|
|
content: tosDownload.tosText,
|
|
|
|
contentType: tosDownload.tosContentType,
|
2022-10-15 12:59:26 +02:00
|
|
|
tosStatus: getExchangeTosStatus(exchangeDetails),
|
2021-06-15 18:52:43 +02:00
|
|
|
};
|
|
|
|
}
|
2019-11-30 00:36:20 +01:00
|
|
|
|
2022-08-09 15:00:45 +02:00
|
|
|
/**
|
|
|
|
* List bank accounts known to the wallet from
|
|
|
|
* previous withdrawals.
|
|
|
|
*/
|
2021-12-23 19:17:36 +01:00
|
|
|
async function listKnownBankAccounts(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
currency?: string,
|
|
|
|
): Promise<KnownBankAccounts> {
|
2022-09-23 20:17:29 +02:00
|
|
|
const accounts: KnownBankAccountsInfo[] = [];
|
2021-12-23 19:17:36 +01:00
|
|
|
await ws.db
|
2022-09-23 20:17:29 +02:00
|
|
|
.mktx((x) => [x.bankAccounts])
|
2021-12-23 19:17:36 +01:00
|
|
|
.runReadOnly(async (tx) => {
|
2022-09-23 20:17:29 +02:00
|
|
|
const knownAccounts = await tx.bankAccounts.iter().toArray();
|
|
|
|
for (const r of knownAccounts) {
|
|
|
|
if (currency && currency !== r.currency) {
|
2022-01-13 12:08:31 +01:00
|
|
|
continue;
|
2021-12-23 19:17:36 +01:00
|
|
|
}
|
2022-09-23 20:17:29 +02:00
|
|
|
const payto = parsePaytoUri(r.uri);
|
|
|
|
if (payto) {
|
|
|
|
accounts.push({
|
|
|
|
uri: payto,
|
|
|
|
alias: r.alias,
|
2022-09-23 21:00:51 +02:00
|
|
|
kyc_completed: r.kycCompleted,
|
2022-09-23 20:17:29 +02:00
|
|
|
currency: r.currency,
|
|
|
|
});
|
2021-12-23 19:17:36 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-13 12:08:31 +01:00
|
|
|
});
|
|
|
|
return { accounts };
|
2021-12-23 19:17:36 +01:00
|
|
|
}
|
|
|
|
|
2022-09-23 20:17:29 +02:00
|
|
|
/**
|
|
|
|
*/
|
|
|
|
async function addKnownBankAccounts(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
payto: string,
|
|
|
|
alias: string,
|
|
|
|
currency: string,
|
|
|
|
): Promise<void> {
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.bankAccounts])
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
tx.bankAccounts.put({
|
|
|
|
uri: payto,
|
|
|
|
alias: alias,
|
|
|
|
currency: currency,
|
2022-09-23 21:00:51 +02:00
|
|
|
kycCompleted: false,
|
2022-09-23 20:17:29 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*/
|
|
|
|
async function forgetKnownBankAccounts(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
payto: string,
|
|
|
|
): Promise<void> {
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => [x.bankAccounts])
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const account = await tx.bankAccounts.get(payto);
|
|
|
|
if (!account) {
|
|
|
|
throw Error(`account not found: ${payto}`);
|
|
|
|
}
|
|
|
|
tx.bankAccounts.delete(account.uri);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-10-14 22:10:03 +02:00
|
|
|
async function getExchangeTosStatusDetails(
|
|
|
|
tx: GetReadOnlyAccess<{ exchangeTos: typeof WalletStoresV1.exchangeTos }>,
|
|
|
|
exchangeDetails: ExchangeDetailsRecord,
|
|
|
|
): Promise<ExchangeTosStatusDetails> {
|
|
|
|
let exchangeTos = await tx.exchangeTos.get([
|
|
|
|
exchangeDetails.exchangeBaseUrl,
|
|
|
|
exchangeDetails.tosCurrentEtag,
|
|
|
|
]);
|
|
|
|
|
|
|
|
if (!exchangeTos) {
|
|
|
|
exchangeTos = {
|
|
|
|
etag: "not-available",
|
|
|
|
termsOfServiceContentType: "text/plain",
|
|
|
|
termsOfServiceText: "terms of service unavailable",
|
|
|
|
exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
acceptedVersion: exchangeDetails.tosAccepted?.etag,
|
2022-10-16 20:15:55 +02:00
|
|
|
content: exchangeTos.termsOfServiceText,
|
2022-10-14 22:10:03 +02:00
|
|
|
contentType: exchangeTos.termsOfServiceContentType,
|
|
|
|
currentVersion: exchangeTos.etag,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
async function getExchanges(
|
|
|
|
ws: InternalWalletState,
|
2022-09-06 22:17:44 +02:00
|
|
|
): Promise<ExchangesListResponse> {
|
2021-06-15 18:52:43 +02:00
|
|
|
const exchanges: ExchangeListItem[] = [];
|
|
|
|
await ws.db
|
2022-10-14 22:10:03 +02:00
|
|
|
.mktx((x) => [
|
|
|
|
x.exchanges,
|
|
|
|
x.exchangeDetails,
|
|
|
|
x.exchangeTos,
|
|
|
|
x.denominations,
|
2022-11-02 14:23:26 +01:00
|
|
|
x.operationRetries,
|
2022-10-14 22:10:03 +02:00
|
|
|
])
|
2021-06-15 18:52:43 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
const exchangeRecords = await tx.exchanges.iter().toArray();
|
|
|
|
for (const r of exchangeRecords) {
|
|
|
|
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
|
2022-11-02 14:23:26 +01:00
|
|
|
const opRetryRecord = await tx.operationRetries.get(
|
|
|
|
RetryTags.forExchangeUpdate(r),
|
|
|
|
);
|
2022-11-08 17:00:34 +01:00
|
|
|
exchanges.push(
|
|
|
|
makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
|
|
|
|
);
|
2022-09-05 21:09:28 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return { exchanges };
|
|
|
|
}
|
|
|
|
|
2022-09-06 22:17:44 +02:00
|
|
|
async function getExchangeDetailedInfo(
|
2022-09-05 21:09:28 +02:00
|
|
|
ws: InternalWalletState,
|
2022-09-06 22:17:44 +02:00
|
|
|
exchangeBaseurl: string,
|
2022-10-25 17:23:08 +02:00
|
|
|
): Promise<ExchangeDetailedResponse> {
|
2022-09-06 22:17:44 +02:00
|
|
|
//TODO: should we use the forceUpdate parameter?
|
|
|
|
const exchange = await ws.db
|
2022-10-14 22:10:03 +02:00
|
|
|
.mktx((x) => [
|
|
|
|
x.exchanges,
|
|
|
|
x.exchangeTos,
|
|
|
|
x.exchangeDetails,
|
|
|
|
x.denominations,
|
|
|
|
])
|
2022-09-05 21:09:28 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
2022-09-05 18:12:30 +02:00
|
|
|
const ex = await tx.exchanges.get(exchangeBaseurl);
|
2022-09-06 22:17:44 +02:00
|
|
|
const dp = ex?.detailsPointer;
|
|
|
|
if (!dp) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const { currency } = dp;
|
|
|
|
const exchangeDetails = await getExchangeDetails(tx, ex.baseUrl);
|
|
|
|
if (!exchangeDetails) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-05 21:09:28 +02:00
|
|
|
|
2022-09-14 21:27:03 +02:00
|
|
|
const denominationRecords =
|
|
|
|
await tx.denominations.indexes.byExchangeBaseUrl
|
|
|
|
.iter(ex.baseUrl)
|
|
|
|
.toArray();
|
2022-09-05 21:09:28 +02:00
|
|
|
|
2022-09-14 21:27:03 +02:00
|
|
|
if (!denominationRecords) {
|
2022-09-06 22:17:44 +02:00
|
|
|
return;
|
|
|
|
}
|
2022-09-05 21:09:28 +02:00
|
|
|
|
2022-10-14 22:10:03 +02:00
|
|
|
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
|
|
|
|
|
2022-09-16 17:35:06 +02:00
|
|
|
const denominations: DenominationInfo[] = denominationRecords.map((x) =>
|
2022-09-15 20:16:42 +02:00
|
|
|
DenominationRecord.toDenomInfo(x),
|
|
|
|
);
|
2022-09-14 21:27:03 +02:00
|
|
|
|
2022-09-06 22:17:44 +02:00
|
|
|
return {
|
2022-09-12 15:57:13 +02:00
|
|
|
info: {
|
|
|
|
exchangeBaseUrl: ex.baseUrl,
|
|
|
|
currency,
|
2022-10-14 22:10:03 +02:00
|
|
|
tos,
|
2022-09-12 15:57:13 +02:00
|
|
|
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
|
|
|
auditors: exchangeDetails.auditors,
|
|
|
|
wireInfo: exchangeDetails.wireInfo,
|
2022-10-12 20:58:10 +02:00
|
|
|
globalFees: exchangeDetails.globalFees,
|
2022-09-06 22:17:44 +02:00
|
|
|
},
|
2022-09-14 21:27:03 +02:00
|
|
|
denominations,
|
2022-09-05 18:12:30 +02:00
|
|
|
};
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-09-06 22:17:44 +02:00
|
|
|
if (!exchange) {
|
2022-09-05 18:12:30 +02:00
|
|
|
throw Error(`exchange with base url "${exchangeBaseurl}" not found`);
|
2022-09-06 22:17:44 +02:00
|
|
|
}
|
2022-09-12 15:57:13 +02:00
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
const denoms = exchange.denominations.map((d) => ({
|
|
|
|
...d,
|
|
|
|
group: Amounts.stringifyValue(d.value),
|
|
|
|
}));
|
|
|
|
const denomFees: DenomOperationMap<FeeDescription[]> = {
|
|
|
|
deposit: createTimeline(
|
|
|
|
denoms,
|
|
|
|
"denomPubHash",
|
|
|
|
"stampStart",
|
2022-09-12 15:57:13 +02:00
|
|
|
"stampExpireDeposit",
|
|
|
|
"feeDeposit",
|
2022-10-12 20:58:10 +02:00
|
|
|
"group",
|
|
|
|
selectBestForOverlappingDenominations,
|
2022-09-12 15:57:13 +02:00
|
|
|
),
|
2022-10-12 20:58:10 +02:00
|
|
|
refresh: createTimeline(
|
|
|
|
denoms,
|
|
|
|
"denomPubHash",
|
|
|
|
"stampStart",
|
2022-09-12 15:57:13 +02:00
|
|
|
"stampExpireWithdraw",
|
|
|
|
"feeRefresh",
|
2022-10-12 20:58:10 +02:00
|
|
|
"group",
|
|
|
|
selectBestForOverlappingDenominations,
|
2022-09-12 15:57:13 +02:00
|
|
|
),
|
2022-10-12 20:58:10 +02:00
|
|
|
refund: createTimeline(
|
|
|
|
denoms,
|
|
|
|
"denomPubHash",
|
|
|
|
"stampStart",
|
2022-09-12 15:57:13 +02:00
|
|
|
"stampExpireWithdraw",
|
|
|
|
"feeRefund",
|
2022-10-12 20:58:10 +02:00
|
|
|
"group",
|
|
|
|
selectBestForOverlappingDenominations,
|
2022-09-12 15:57:13 +02:00
|
|
|
),
|
2022-10-12 20:58:10 +02:00
|
|
|
withdraw: createTimeline(
|
|
|
|
denoms,
|
|
|
|
"denomPubHash",
|
|
|
|
"stampStart",
|
2022-09-12 15:57:13 +02:00
|
|
|
"stampExpireWithdraw",
|
|
|
|
"feeWithdraw",
|
2022-10-12 20:58:10 +02:00
|
|
|
"group",
|
|
|
|
selectBestForOverlappingDenominations,
|
2022-09-12 15:57:13 +02:00
|
|
|
),
|
|
|
|
};
|
|
|
|
|
2022-10-12 20:58:10 +02:00
|
|
|
const transferFees = Object.entries(
|
|
|
|
exchange.info.wireInfo.feesForType,
|
|
|
|
).reduce((prev, [wireType, infoForType]) => {
|
|
|
|
const feesByGroup = [
|
|
|
|
...infoForType.map((w) => ({
|
|
|
|
...w,
|
2022-11-02 17:42:14 +01:00
|
|
|
fee: Amounts.stringify(w.closingFee),
|
2022-10-12 20:58:10 +02:00
|
|
|
group: "closing",
|
|
|
|
})),
|
|
|
|
...infoForType.map((w) => ({ ...w, fee: w.wireFee, group: "wire" })),
|
|
|
|
];
|
|
|
|
prev[wireType] = createTimeline(
|
|
|
|
feesByGroup,
|
|
|
|
"sig",
|
|
|
|
"startStamp",
|
|
|
|
"endStamp",
|
|
|
|
"fee",
|
|
|
|
"group",
|
|
|
|
selectMinimumFee,
|
|
|
|
);
|
|
|
|
return prev;
|
|
|
|
}, {} as Record<string, FeeDescription[]>);
|
|
|
|
|
|
|
|
const globalFeesByGroup = [
|
|
|
|
...exchange.info.globalFees.map((w) => ({
|
|
|
|
...w,
|
|
|
|
fee: w.accountFee,
|
|
|
|
group: "account",
|
|
|
|
})),
|
|
|
|
...exchange.info.globalFees.map((w) => ({
|
|
|
|
...w,
|
|
|
|
fee: w.historyFee,
|
|
|
|
group: "history",
|
|
|
|
})),
|
|
|
|
...exchange.info.globalFees.map((w) => ({
|
|
|
|
...w,
|
|
|
|
fee: w.purseFee,
|
|
|
|
group: "purse",
|
|
|
|
})),
|
|
|
|
];
|
|
|
|
|
|
|
|
const globalFees = createTimeline(
|
|
|
|
globalFeesByGroup,
|
|
|
|
"signature",
|
|
|
|
"startDate",
|
|
|
|
"endDate",
|
|
|
|
"fee",
|
|
|
|
"group",
|
|
|
|
selectMinimumFee,
|
|
|
|
);
|
|
|
|
|
2022-09-12 15:57:13 +02:00
|
|
|
return {
|
2022-10-25 17:23:08 +02:00
|
|
|
exchange: {
|
|
|
|
...exchange.info,
|
|
|
|
denomFees,
|
|
|
|
transferFees,
|
|
|
|
globalFees,
|
|
|
|
},
|
2022-09-12 15:57:13 +02:00
|
|
|
};
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function setCoinSuspended(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
coinPub: string,
|
|
|
|
suspended: boolean,
|
|
|
|
): Promise<void> {
|
|
|
|
await ws.db
|
2022-09-16 16:20:47 +02:00
|
|
|
.mktx((x) => [x.coins, x.coinAvailability])
|
2021-06-15 18:52:43 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const c = await tx.coins.get(coinPub);
|
|
|
|
if (!c) {
|
|
|
|
logger.warn(`coin ${coinPub} not found, won't suspend`);
|
|
|
|
return;
|
|
|
|
}
|
2022-09-16 16:20:47 +02:00
|
|
|
const coinAvailability = await tx.coinAvailability.get([
|
2022-09-14 20:34:37 +02:00
|
|
|
c.exchangeBaseUrl,
|
|
|
|
c.denomPubHash,
|
2022-09-16 16:20:47 +02:00
|
|
|
c.maxAge,
|
2022-09-14 20:34:37 +02:00
|
|
|
]);
|
2022-09-16 16:20:47 +02:00
|
|
|
checkDbInvariant(!!coinAvailability);
|
2022-09-14 20:34:37 +02:00
|
|
|
if (suspended) {
|
|
|
|
if (c.status !== CoinStatus.Fresh) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-19 14:41:50 +02:00
|
|
|
if (coinAvailability.freshCoinCount === 0) {
|
2022-09-16 16:20:47 +02:00
|
|
|
throw Error(
|
|
|
|
`invalid coin count ${coinAvailability.freshCoinCount} in DB`,
|
|
|
|
);
|
2022-09-14 20:34:37 +02:00
|
|
|
}
|
2022-09-16 16:20:47 +02:00
|
|
|
coinAvailability.freshCoinCount--;
|
2022-09-14 20:34:37 +02:00
|
|
|
c.status = CoinStatus.FreshSuspended;
|
|
|
|
} else {
|
|
|
|
if (c.status == CoinStatus.Dormant) {
|
|
|
|
return;
|
|
|
|
}
|
2022-09-16 16:20:47 +02:00
|
|
|
coinAvailability.freshCoinCount++;
|
2022-09-14 20:34:37 +02:00
|
|
|
c.status = CoinStatus.Fresh;
|
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
await tx.coins.put(c);
|
2022-09-16 16:20:47 +02:00
|
|
|
await tx.coinAvailability.put(coinAvailability);
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
|
|
|
}
|
2019-11-30 00:36:20 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Dump the public information of coins we have in an easy-to-process format.
|
|
|
|
*/
|
|
|
|
async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
|
|
|
|
const coinsJson: CoinDumpJson = { coins: [] };
|
2022-01-13 22:01:14 +01:00
|
|
|
logger.info("dumping coins");
|
2021-06-15 18:52:43 +02:00
|
|
|
await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [x.coins, x.denominations, x.withdrawalGroups])
|
2021-06-15 18:52:43 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
const coins = await tx.coins.iter().toArray();
|
|
|
|
for (const c of coins) {
|
|
|
|
const denom = await tx.denominations.get([
|
|
|
|
c.exchangeBaseUrl,
|
|
|
|
c.denomPubHash,
|
|
|
|
]);
|
|
|
|
if (!denom) {
|
|
|
|
console.error("no denom session 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) {
|
|
|
|
const ws = await tx.withdrawalGroups.get(cs.withdrawalGroupId);
|
|
|
|
if (!ws) {
|
|
|
|
console.error("no withdrawal session found for coin");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
withdrawalReservePub = ws.reservePub;
|
|
|
|
}
|
2022-03-10 16:30:24 +01:00
|
|
|
const denomInfo = await ws.getDenomInfo(
|
|
|
|
ws,
|
|
|
|
tx,
|
|
|
|
c.exchangeBaseUrl,
|
|
|
|
c.denomPubHash,
|
|
|
|
);
|
2022-05-18 19:41:51 +02:00
|
|
|
if (!denomInfo) {
|
2022-06-10 13:03:47 +02:00
|
|
|
console.error("no denomination found for coin");
|
2022-05-18 19:41:51 +02:00
|
|
|
continue;
|
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
coinsJson.coins.push({
|
|
|
|
coin_pub: c.coinPub,
|
2022-05-18 19:41:51 +02:00
|
|
|
denom_pub: denomInfo.denomPub,
|
2021-06-15 18:52:43 +02:00
|
|
|
denom_pub_hash: c.denomPubHash,
|
2022-09-14 21:27:03 +02:00
|
|
|
denom_value: Amounts.stringify({
|
|
|
|
value: denom.amountVal,
|
|
|
|
currency: denom.currency,
|
|
|
|
fraction: denom.amountFrac,
|
|
|
|
}),
|
2021-06-15 18:52:43 +02:00
|
|
|
exchange_base_url: c.exchangeBaseUrl,
|
|
|
|
refresh_parent_coin_pub: refreshParentCoinPub,
|
|
|
|
withdrawal_reserve_pub: withdrawalReservePub,
|
2022-10-15 11:52:07 +02:00
|
|
|
coin_status: c.status,
|
2022-05-04 21:11:12 +02:00
|
|
|
ageCommitmentProof: c.ageCommitmentProof,
|
2022-10-15 11:52:07 +02:00
|
|
|
spend_allocation: c.spendAllocation
|
|
|
|
? {
|
2022-11-16 20:04:52 +01:00
|
|
|
amount: c.spendAllocation.amount,
|
|
|
|
id: c.spendAllocation.id,
|
|
|
|
}
|
2022-10-15 11:52:07 +02:00
|
|
|
: undefined,
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return coinsJson;
|
|
|
|
}
|
2016-01-06 15:39:22 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Get an API client from an internal wallet state object.
|
|
|
|
*/
|
|
|
|
export async function getClientFromWalletState(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
): Promise<WalletCoreApiClient> {
|
|
|
|
let id = 0;
|
|
|
|
const client: WalletCoreApiClient = {
|
|
|
|
async call(op, payload): Promise<any> {
|
|
|
|
const res = await handleCoreApiRequest(ws, op, `${id++}`, payload);
|
|
|
|
switch (res.type) {
|
|
|
|
case "error":
|
2022-03-22 21:16:38 +01:00
|
|
|
throw TalerError.fromUncheckedDetail(res.error);
|
2021-06-15 18:52:43 +02:00
|
|
|
case "response":
|
|
|
|
return res.result;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return client;
|
|
|
|
}
|
2016-11-16 01:59:39 +01:00
|
|
|
|
2022-09-05 15:04:56 +02:00
|
|
|
declare const __VERSION__: string;
|
|
|
|
declare const __GIT_HASH__: string;
|
|
|
|
|
|
|
|
const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : "dev";
|
|
|
|
const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined;
|
2022-09-05 18:12:30 +02:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Implementation of the "wallet-core" API.
|
|
|
|
*/
|
2022-10-17 01:03:36 +02:00
|
|
|
async function dispatchRequestInternal<Op extends WalletApiOperation>(
|
2021-06-15 18:52:43 +02:00
|
|
|
ws: InternalWalletState,
|
2022-10-17 01:03:36 +02:00
|
|
|
operation: WalletApiOperation,
|
2021-06-15 18:52:43 +02:00
|
|
|
payload: unknown,
|
2022-10-17 01:03:36 +02:00
|
|
|
): Promise<WalletCoreResponseType<typeof operation>> {
|
|
|
|
if (!ws.initCalled && operation !== WalletApiOperation.InitWallet) {
|
2021-06-15 18:52:43 +02:00
|
|
|
throw Error(
|
|
|
|
`wallet must be initialized before running operation ${operation}`,
|
2021-06-02 13:23:51 +02:00
|
|
|
);
|
2020-07-11 10:32:17 +02:00
|
|
|
}
|
2022-09-20 21:44:21 +02:00
|
|
|
// FIXME: Can we make this more type-safe by using the request/response type
|
|
|
|
// definitions we already have?
|
2021-06-15 18:52:43 +02:00
|
|
|
switch (operation) {
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.InitWallet: {
|
2022-10-07 12:43:17 +02:00
|
|
|
logger.trace("initializing wallet");
|
2021-06-15 18:52:43 +02:00
|
|
|
ws.initCalled = true;
|
2022-08-24 11:11:02 +02:00
|
|
|
if (typeof payload === "object" && (payload as any).skipDefaults) {
|
2022-10-07 12:43:17 +02:00
|
|
|
logger.trace("skipping defaults");
|
2022-08-24 11:11:02 +02:00
|
|
|
} else {
|
2022-10-07 12:43:17 +02:00
|
|
|
logger.trace("filling defaults");
|
2022-08-24 11:11:02 +02:00
|
|
|
await fillDefaults(ws);
|
|
|
|
}
|
2022-10-12 22:27:50 +02:00
|
|
|
await maybeInitDevMode(ws);
|
2022-10-23 19:05:46 +02:00
|
|
|
const resp: InitResponse = {
|
|
|
|
versionInfo: getVersion(ws),
|
|
|
|
};
|
|
|
|
return resp;
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.WithdrawTestkudos: {
|
2022-06-10 13:03:47 +02:00
|
|
|
await withdrawTestBalance(ws, {
|
|
|
|
amount: "TESTKUDOS:10",
|
|
|
|
bankBaseUrl: "https://bank.test.taler.net/",
|
2022-08-25 18:34:25 +02:00
|
|
|
bankAccessApiBaseUrl: "https://bank.test.taler.net/",
|
2022-06-10 13:03:47 +02:00
|
|
|
exchangeBaseUrl: "https://exchange.test.taler.net/",
|
|
|
|
});
|
2022-10-19 15:36:57 +02:00
|
|
|
return {
|
|
|
|
versionInfo: getVersion(ws),
|
|
|
|
};
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.WithdrawTestBalance: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForWithdrawTestBalance().decode(payload);
|
2022-06-10 13:03:47 +02:00
|
|
|
await withdrawTestBalance(ws, req);
|
2021-06-15 18:52:43 +02:00
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.RunIntegrationTest: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForIntegrationTestArgs().decode(payload);
|
|
|
|
await runIntegrationTest(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.TestPay: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForTestPayArgs().decode(payload);
|
2022-06-10 13:03:47 +02:00
|
|
|
return await testPay(ws, req);
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetTransactions: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForTransactionsRequest().decode(payload);
|
|
|
|
return await getTransactions(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetTransactionById: {
|
2022-09-16 16:06:55 +02:00
|
|
|
const req = codecForTransactionByIdRequest().decode(payload);
|
2022-09-16 17:21:54 +02:00
|
|
|
return await getTransactionById(ws, req);
|
2022-09-16 16:06:55 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AddExchange: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAddExchangeRequest().decode(payload);
|
2022-03-29 13:47:32 +02:00
|
|
|
await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {
|
|
|
|
forceNow: req.forceUpdate,
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ListExchanges: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return await getExchanges(ws);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetExchangeDetailedInfo: {
|
2022-09-06 22:17:44 +02:00
|
|
|
const req = codecForAddExchangeRequest().decode(payload);
|
|
|
|
return await getExchangeDetailedInfo(ws, req.exchangeBaseUrl);
|
2022-09-05 21:09:28 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ListKnownBankAccounts: {
|
2021-12-23 19:17:36 +01:00
|
|
|
const req = codecForListKnownBankAccounts().decode(payload);
|
|
|
|
return await listKnownBankAccounts(ws, req.currency);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AddKnownBankAccounts: {
|
2022-09-23 20:17:29 +02:00
|
|
|
const req = codecForAddKnownBankAccounts().decode(payload);
|
|
|
|
await addKnownBankAccounts(ws, req.payto, req.alias, req.currency);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ForgetKnownBankAccounts: {
|
2022-09-23 20:17:29 +02:00
|
|
|
const req = codecForForgetKnownBankAccounts().decode(payload);
|
|
|
|
await forgetKnownBankAccounts(ws, req.payto);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetWithdrawalDetailsForUri: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForGetWithdrawalDetailsForUri().decode(payload);
|
|
|
|
return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AcceptManualWithdrawal: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
|
2022-08-09 15:00:45 +02:00
|
|
|
const res = await createManualWithdrawal(ws, {
|
|
|
|
amount: Amounts.parseOrThrow(req.amount),
|
|
|
|
exchangeBaseUrl: req.exchangeBaseUrl,
|
|
|
|
restrictAge: req.restrictAge,
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
return res;
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetWithdrawalDetailsForAmount: {
|
2022-01-13 12:08:31 +01:00
|
|
|
const req =
|
|
|
|
codecForGetWithdrawalDetailsForAmountRequest().decode(payload);
|
2022-10-14 18:40:04 +02:00
|
|
|
const wi = await getExchangeWithdrawalInfo(
|
2021-06-15 18:52:43 +02:00
|
|
|
ws,
|
|
|
|
req.exchangeBaseUrl,
|
|
|
|
Amounts.parseOrThrow(req.amount),
|
2022-09-05 18:12:30 +02:00
|
|
|
req.restrictAge,
|
2021-06-15 18:52:43 +02:00
|
|
|
);
|
2022-11-25 16:12:14 +01:00
|
|
|
const resp: ManualWithdrawalDetails = {
|
2022-10-14 18:40:04 +02:00
|
|
|
amountRaw: req.amount,
|
|
|
|
amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue),
|
|
|
|
paytoUris: wi.exchangePaytoUris,
|
|
|
|
tosAccepted: wi.termsOfServiceAccepted,
|
2022-11-25 16:12:14 +01:00
|
|
|
ageRestrictionOptions: wi.ageRestrictionOptions,
|
2022-10-14 18:40:04 +02:00
|
|
|
};
|
2022-11-25 16:12:14 +01:00
|
|
|
return resp;
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetBalances: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return await getBalances(ws);
|
|
|
|
}
|
2022-11-25 03:16:01 +01:00
|
|
|
case WalletApiOperation.GetUserAttentionRequests: {
|
|
|
|
const req = codecForUserAttentionsRequest().decode(payload);
|
|
|
|
return await getUserAttentions(ws, req);
|
|
|
|
}
|
|
|
|
case WalletApiOperation.MarkAttentionRequestAsRead: {
|
|
|
|
const req = codecForUserAttentionByIdRequest().decode(payload);
|
|
|
|
return await markAttentionRequestAsRead(ws, req);
|
|
|
|
}
|
|
|
|
case WalletApiOperation.GetUserAttentionUnreadCount: {
|
|
|
|
const req = codecForUserAttentionsRequest().decode(payload);
|
|
|
|
return await getUserAttentionsUnreadCount(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetPendingOperations: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return await getPendingOperations(ws);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.SetExchangeTosAccepted: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAcceptExchangeTosRequest().decode(payload);
|
|
|
|
await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl, req.etag);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ApplyRefund: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForApplyRefundRequest().decode(payload);
|
|
|
|
return await applyRefund(ws, req.talerRefundUri);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ApplyRefundFromPurchaseId: {
|
2022-05-29 06:23:15 +02:00
|
|
|
const req = codecForApplyRefundFromPurchaseIdRequest().decode(payload);
|
|
|
|
return await applyRefundFromPurchaseId(ws, req.purchaseId);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AcceptBankIntegratedWithdrawal: {
|
2022-01-13 12:08:31 +01:00
|
|
|
const req =
|
|
|
|
codecForAcceptBankIntegratedWithdrawalRequest().decode(payload);
|
2022-08-09 15:00:45 +02:00
|
|
|
return await acceptWithdrawalFromUri(ws, {
|
|
|
|
selectedExchange: req.exchangeBaseUrl,
|
|
|
|
talerWithdrawUri: req.talerWithdrawUri,
|
|
|
|
forcedDenomSel: req.forcedDenomSel,
|
|
|
|
restrictAge: req.restrictAge,
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetExchangeTos: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForGetExchangeTosRequest().decode(payload);
|
2021-10-14 11:36:43 +02:00
|
|
|
return getExchangeTos(ws, req.exchangeBaseUrl, req.acceptedFormat);
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetContractTermsDetails: {
|
2022-08-08 18:53:04 +02:00
|
|
|
const req = codecForGetContractTermsDetails().decode(payload);
|
|
|
|
return getContractTermsDetails(ws, req.proposalId);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.RetryPendingNow: {
|
2021-06-15 18:52:43 +02:00
|
|
|
await runPending(ws, true);
|
|
|
|
return {};
|
|
|
|
}
|
2021-06-17 13:34:59 +02:00
|
|
|
// FIXME: Deprecate one of the aliases!
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.PreparePayForUri: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForPreparePayRequest().decode(payload);
|
|
|
|
return await preparePayForUri(ws, req.talerPayUri);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ConfirmPay: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForConfirmPayRequest().decode(payload);
|
|
|
|
return await confirmPay(ws, req.proposalId, req.sessionId);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AbortFailedPayWithRefund: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAbortPayWithRefundRequest().decode(payload);
|
|
|
|
await abortFailedPayWithRefund(ws, req.proposalId);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.DumpCoins: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return await dumpCoins(ws);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.SetCoinSuspended: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForSetCoinSuspendedRequest().decode(payload);
|
|
|
|
await setCoinSuspended(ws, req.coinPub, req.suspended);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ForceRefresh: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForForceRefreshRequest().decode(payload);
|
2023-01-05 18:45:49 +01:00
|
|
|
if (req.coinPubList.length == 0) {
|
|
|
|
throw Error("refusing to create empty refresh group");
|
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
const refreshGroupId = await ws.db
|
2022-09-16 16:20:47 +02:00
|
|
|
.mktx((x) => [
|
|
|
|
x.refreshGroups,
|
|
|
|
x.coinAvailability,
|
|
|
|
x.denominations,
|
|
|
|
x.coins,
|
|
|
|
])
|
2021-06-09 15:14:17 +02:00
|
|
|
.runReadWrite(async (tx) => {
|
2022-10-15 11:52:07 +02:00
|
|
|
let coinPubs: CoinRefreshRequest[] = [];
|
|
|
|
for (const c of req.coinPubList) {
|
|
|
|
const coin = await tx.coins.get(c);
|
|
|
|
if (!coin) {
|
|
|
|
throw Error(`coin (pubkey ${c}) not found`);
|
|
|
|
}
|
|
|
|
const denom = await ws.getDenomInfo(
|
|
|
|
ws,
|
|
|
|
tx,
|
|
|
|
coin.exchangeBaseUrl,
|
|
|
|
coin.denomPubHash,
|
|
|
|
);
|
|
|
|
checkDbInvariant(!!denom);
|
|
|
|
coinPubs.push({
|
|
|
|
coinPub: c,
|
|
|
|
amount: denom?.value,
|
|
|
|
});
|
|
|
|
}
|
2019-12-16 12:53:22 +01:00
|
|
|
return await createRefreshGroup(
|
2021-06-15 18:52:43 +02:00
|
|
|
ws,
|
2019-12-16 12:53:22 +01:00
|
|
|
tx,
|
2023-01-05 18:45:49 +01:00
|
|
|
Amounts.currencyOf(coinPubs[0].amount),
|
2021-06-15 18:52:43 +02:00
|
|
|
coinPubs,
|
2019-12-16 12:53:22 +01:00
|
|
|
RefreshReason.Manual,
|
|
|
|
);
|
2021-06-09 15:14:17 +02:00
|
|
|
});
|
2022-03-29 13:47:32 +02:00
|
|
|
processRefreshGroup(ws, refreshGroupId.refreshGroupId, {
|
|
|
|
forceNow: true,
|
|
|
|
}).catch((x) => {
|
|
|
|
logger.error(x);
|
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
return {
|
|
|
|
refreshGroupId,
|
|
|
|
};
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.PrepareTip: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForPrepareTipRequest().decode(payload);
|
|
|
|
return await prepareTip(ws, req.talerTipUri);
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.PrepareRefund: {
|
2022-05-03 00:21:13 +02:00
|
|
|
const req = codecForPrepareRefundRequest().decode(payload);
|
|
|
|
return await prepareRefund(ws, req.talerRefundUri);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AcceptTip: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAcceptTipRequest().decode(payload);
|
2022-09-16 16:06:55 +02:00
|
|
|
return await acceptTip(ws, req.walletTipId);
|
2019-11-30 00:36:20 +01:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ExportBackupPlain: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return exportBackup(ws);
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AddBackupProvider: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAddBackupProviderRequest().decode(payload);
|
2022-11-16 20:04:52 +01:00
|
|
|
return await addBackupProvider(ws, req);
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.RunBackupCycle: {
|
2021-07-07 18:28:02 +02:00
|
|
|
const req = codecForRunBackupCycle().decode(payload);
|
|
|
|
await runBackupCycle(ws, req);
|
2021-06-15 18:52:43 +02:00
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.RemoveBackupProvider: {
|
2021-07-15 20:03:45 +02:00
|
|
|
const req = codecForRemoveBackupProvider().decode(payload);
|
|
|
|
await removeBackupProvider(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ExportBackupRecovery: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const resp = await getBackupRecovery(ws);
|
|
|
|
return resp;
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ImportBackupRecovery: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForAny().decode(payload);
|
|
|
|
await loadBackupRecovery(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetBackupInfo: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const resp = await getBackupInfo(ws);
|
|
|
|
return resp;
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetFeeForDeposit: {
|
2021-12-23 19:17:36 +01:00
|
|
|
const req = codecForGetFeeForDeposit().decode(payload);
|
|
|
|
return await getFeeForDeposit(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.PrepareDeposit: {
|
2022-05-03 05:16:03 +02:00
|
|
|
const req = codecForPrepareDepositRequest().decode(payload);
|
|
|
|
return await prepareDepositGroup(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.CreateDepositGroup: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForCreateDepositGroupRequest().decode(payload);
|
|
|
|
return await createDepositGroup(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.TrackDepositGroup: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForTrackDepositGroupRequest().decode(payload);
|
|
|
|
return trackDepositGroup(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.DeleteTransaction: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForDeleteTransactionRequest().decode(payload);
|
|
|
|
await deleteTransaction(ws, req.transactionId);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.RetryTransaction: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForRetryTransactionRequest().decode(payload);
|
|
|
|
await retryTransaction(ws, req.transactionId);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.SetWalletDeviceId: {
|
2021-06-15 18:52:43 +02:00
|
|
|
const req = codecForSetWalletDeviceIdRequest().decode(payload);
|
|
|
|
await setWalletDeviceId(ws, req.walletDeviceId);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ListCurrencies: {
|
2021-06-15 18:52:43 +02:00
|
|
|
return await ws.db
|
2022-09-13 13:25:41 +02:00
|
|
|
.mktx((x) => [x.auditorTrust, x.exchangeTrust])
|
2021-06-15 18:52:43 +02:00
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
const trustedAuditors = await tx.auditorTrust.iter().toArray();
|
|
|
|
const trustedExchanges = await tx.exchangeTrust.iter().toArray();
|
|
|
|
return {
|
|
|
|
trustedAuditors: trustedAuditors.map((x) => ({
|
|
|
|
currency: x.currency,
|
|
|
|
auditorBaseUrl: x.auditorBaseUrl,
|
|
|
|
auditorPub: x.auditorPub,
|
|
|
|
})),
|
|
|
|
trustedExchanges: trustedExchanges.map((x) => ({
|
|
|
|
currency: x.currency,
|
|
|
|
exchangeBaseUrl: x.exchangeBaseUrl,
|
|
|
|
exchangeMasterPub: x.exchangeMasterPub,
|
|
|
|
})),
|
|
|
|
};
|
2020-08-14 12:23:50 +02:00
|
|
|
});
|
2020-08-14 09:36:42 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.WithdrawFakebank: {
|
2021-10-14 11:36:43 +02:00
|
|
|
const req = codecForWithdrawFakebankRequest().decode(payload);
|
|
|
|
const amount = Amounts.parseOrThrow(req.amount);
|
2022-10-14 18:40:04 +02:00
|
|
|
const details = await getExchangeWithdrawalInfo(
|
2021-10-14 11:36:43 +02:00
|
|
|
ws,
|
|
|
|
req.exchange,
|
|
|
|
amount,
|
2022-09-05 18:12:30 +02:00
|
|
|
undefined,
|
2021-10-14 11:36:43 +02:00
|
|
|
);
|
2022-08-09 15:00:45 +02:00
|
|
|
const wres = await createManualWithdrawal(ws, {
|
|
|
|
amount: amount,
|
|
|
|
exchangeBaseUrl: req.exchange,
|
|
|
|
});
|
2022-10-14 18:40:04 +02:00
|
|
|
const paytoUri = details.exchangePaytoUris[0];
|
2021-10-14 11:36:43 +02:00
|
|
|
const pt = parsePaytoUri(paytoUri);
|
|
|
|
if (!pt) {
|
|
|
|
throw Error("failed to parse payto URI");
|
|
|
|
}
|
|
|
|
const components = pt.targetPath.split("/");
|
|
|
|
const creditorAcct = components[components.length - 1];
|
2021-12-09 10:39:50 +01:00
|
|
|
logger.info(`making testbank transfer to '${creditorAcct}'`);
|
2021-10-14 11:36:43 +02:00
|
|
|
const fbReq = await ws.http.postJson(
|
|
|
|
new URL(`${creditorAcct}/admin/add-incoming`, req.bank).href,
|
|
|
|
{
|
|
|
|
amount: Amounts.stringify(amount),
|
|
|
|
reserve_pub: wres.reservePub,
|
|
|
|
debit_account: "payto://x-taler-bank/localhost/testdebtor",
|
|
|
|
},
|
|
|
|
);
|
|
|
|
const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny());
|
|
|
|
logger.info(`started fakebank withdrawal: ${j2s(fbResp)}`);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.TestCrypto: {
|
2022-10-05 12:52:49 +02:00
|
|
|
return await ws.cryptoApi.hashString({ str: "hello world" });
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ClearDb:
|
2022-09-20 21:44:21 +02:00
|
|
|
await clearDatabase(ws.db.idbHandle());
|
|
|
|
return {};
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.Recycle: {
|
2022-09-20 21:44:21 +02:00
|
|
|
const backup = await exportBackup(ws);
|
|
|
|
await clearDatabase(ws.db.idbHandle());
|
|
|
|
await importBackupPlain(ws, backup);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ExportDb: {
|
2021-12-01 18:16:40 +01:00
|
|
|
const dbDump = await exportDb(ws.db.idbHandle());
|
|
|
|
return dbDump;
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ImportDb: {
|
2022-01-13 05:33:24 +01:00
|
|
|
const req = codecForImportDbRequest().decode(payload);
|
|
|
|
await importDb(ws.db.idbHandle(), req.dump);
|
|
|
|
return [];
|
|
|
|
}
|
2022-11-08 17:00:34 +01:00
|
|
|
case WalletApiOperation.PreparePeerPushPayment: {
|
|
|
|
const req = codecForPreparePeerPushPaymentRequest().decode(payload);
|
|
|
|
return await preparePeerPushPayment(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.InitiatePeerPushPayment: {
|
2022-06-21 12:40:12 +02:00
|
|
|
const req = codecForInitiatePeerPushPaymentRequest().decode(payload);
|
|
|
|
return await initiatePeerToPeerPush(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.CheckPeerPushPayment: {
|
2022-07-12 17:41:14 +02:00
|
|
|
const req = codecForCheckPeerPushPaymentRequest().decode(payload);
|
|
|
|
return await checkPeerPushPayment(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AcceptPeerPushPayment: {
|
2022-07-12 17:41:14 +02:00
|
|
|
const req = codecForAcceptPeerPushPaymentRequest().decode(payload);
|
2022-09-17 23:40:03 +02:00
|
|
|
return await acceptPeerPushPayment(ws, req);
|
2022-07-12 17:41:14 +02:00
|
|
|
}
|
2022-11-08 17:00:34 +01:00
|
|
|
case WalletApiOperation.PreparePeerPullPayment: {
|
|
|
|
const req = codecForPreparePeerPullPaymentRequest().decode(payload);
|
|
|
|
return await preparePeerPullPayment(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.InitiatePeerPullPayment: {
|
2022-08-23 11:29:45 +02:00
|
|
|
const req = codecForInitiatePeerPullPaymentRequest().decode(payload);
|
2022-11-02 17:02:42 +01:00
|
|
|
return await initiatePeerPullPayment(ws, req);
|
2022-08-23 11:29:45 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.CheckPeerPullPayment: {
|
2022-08-24 11:11:02 +02:00
|
|
|
const req = codecForCheckPeerPullPaymentRequest().decode(payload);
|
|
|
|
return await checkPeerPullPayment(ws, req);
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.AcceptPeerPullPayment: {
|
2022-08-24 11:11:02 +02:00
|
|
|
const req = codecForAcceptPeerPullPaymentRequest().decode(payload);
|
2022-09-17 23:40:03 +02:00
|
|
|
return await acceptPeerPullPayment(ws, req);
|
2022-08-24 11:11:02 +02:00
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.ApplyDevExperiment: {
|
2022-10-12 21:19:09 +02:00
|
|
|
const req = codecForApplyDevExperiment().decode(payload);
|
|
|
|
await applyDevExperiment(ws, req.devExperimentUri);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.SetDevMode: {
|
2022-10-13 15:24:47 +02:00
|
|
|
const req = codecForSetDevModeRequest().decode(payload);
|
|
|
|
await setDevMode(ws, req.devModeEnabled);
|
|
|
|
return {};
|
|
|
|
}
|
2022-10-17 01:03:36 +02:00
|
|
|
case WalletApiOperation.GetVersion: {
|
2022-10-19 15:36:57 +02:00
|
|
|
return getVersion(ws);
|
2022-09-05 15:04:56 +02:00
|
|
|
}
|
2020-08-14 09:36:42 +02:00
|
|
|
}
|
2022-03-22 21:16:38 +01:00
|
|
|
throw TalerError.fromDetail(
|
2021-06-15 18:52:43 +02:00
|
|
|
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
|
|
|
|
{
|
|
|
|
operation,
|
|
|
|
},
|
2022-03-22 21:16:38 +01:00
|
|
|
"unknown operation",
|
2021-06-15 18:52:43 +02:00
|
|
|
);
|
|
|
|
}
|
2020-08-14 09:36:42 +02:00
|
|
|
|
2022-10-19 15:36:57 +02:00
|
|
|
export function getVersion(ws: InternalWalletState): WalletCoreVersion {
|
|
|
|
const version: WalletCoreVersion = {
|
|
|
|
hash: GIT_HASH,
|
|
|
|
version: VERSION,
|
|
|
|
exchange: WALLET_EXCHANGE_PROTOCOL_VERSION,
|
|
|
|
merchant: WALLET_MERCHANT_PROTOCOL_VERSION,
|
|
|
|
bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
|
|
|
|
devMode: ws.devModeActive,
|
|
|
|
};
|
|
|
|
return version;
|
|
|
|
}
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Handle a request to the wallet-core API.
|
|
|
|
*/
|
|
|
|
export async function handleCoreApiRequest(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
operation: string,
|
|
|
|
id: string,
|
|
|
|
payload: unknown,
|
|
|
|
): Promise<CoreApiResponse> {
|
|
|
|
try {
|
2022-10-17 01:03:36 +02:00
|
|
|
const result = await dispatchRequestInternal(ws, operation as any, payload);
|
2021-06-15 18:52:43 +02:00
|
|
|
return {
|
|
|
|
type: "response",
|
|
|
|
operation,
|
|
|
|
id,
|
|
|
|
result,
|
|
|
|
};
|
2021-10-14 11:36:43 +02:00
|
|
|
} catch (e: any) {
|
2022-03-22 21:16:38 +01:00
|
|
|
const err = getErrorDetailFromException(e);
|
2022-03-24 01:10:34 +01:00
|
|
|
logger.info(`finished wallet core request with error: ${j2s(err)}`);
|
2022-03-22 21:16:38 +01:00
|
|
|
return {
|
|
|
|
type: "error",
|
|
|
|
operation,
|
|
|
|
id,
|
|
|
|
error: err,
|
|
|
|
};
|
2020-08-14 09:36:42 +02:00
|
|
|
}
|
2016-10-18 01:16:31 +02:00
|
|
|
}
|
2021-06-17 21:06:45 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Public handle to a running wallet.
|
|
|
|
*/
|
|
|
|
export class Wallet {
|
|
|
|
private ws: InternalWalletState;
|
2022-09-16 17:41:13 +02:00
|
|
|
private _client: WalletCoreApiClient | undefined;
|
2021-06-17 21:06:45 +02:00
|
|
|
|
|
|
|
private constructor(
|
|
|
|
db: DbAccess<typeof WalletStoresV1>,
|
|
|
|
http: HttpRequestLibrary,
|
2022-04-11 20:10:16 +02:00
|
|
|
timer: TimerAPI,
|
2021-06-17 21:06:45 +02:00
|
|
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
|
|
|
) {
|
2022-04-11 20:10:16 +02:00
|
|
|
this.ws = new InternalWalletStateImpl(db, http, timer, cryptoWorkerFactory);
|
2021-06-17 21:06:45 +02:00
|
|
|
}
|
|
|
|
|
2022-01-16 21:47:43 +01:00
|
|
|
get client(): WalletCoreApiClient {
|
2022-09-16 17:41:13 +02:00
|
|
|
if (!this._client) {
|
|
|
|
throw Error();
|
|
|
|
}
|
2021-06-17 21:06:45 +02:00
|
|
|
return this._client;
|
|
|
|
}
|
|
|
|
|
2021-12-08 16:23:00 +01:00
|
|
|
/**
|
|
|
|
* Trust the exchange, do not validate signatures.
|
|
|
|
* Only used to benchmark the exchange.
|
|
|
|
*/
|
2022-01-16 21:47:43 +01:00
|
|
|
setInsecureTrustExchange(): void {
|
2021-12-08 16:23:00 +01:00
|
|
|
this.ws.insecureTrustExchange = true;
|
|
|
|
}
|
|
|
|
|
2022-05-03 17:53:32 +02:00
|
|
|
setBatchWithdrawal(enable: boolean): void {
|
|
|
|
this.ws.batchWithdrawal = enable;
|
|
|
|
}
|
|
|
|
|
2021-06-17 21:06:45 +02:00
|
|
|
static async create(
|
|
|
|
db: DbAccess<typeof WalletStoresV1>,
|
|
|
|
http: HttpRequestLibrary,
|
2022-04-11 20:10:16 +02:00
|
|
|
timer: TimerAPI,
|
2021-06-17 21:06:45 +02:00
|
|
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
|
|
|
): Promise<Wallet> {
|
2022-04-11 20:10:16 +02:00
|
|
|
const w = new Wallet(db, http, timer, cryptoWorkerFactory);
|
2021-06-17 21:06:45 +02:00
|
|
|
w._client = await getClientFromWalletState(w.ws);
|
|
|
|
return w;
|
|
|
|
}
|
|
|
|
|
|
|
|
addNotificationListener(f: (n: WalletNotification) => void): void {
|
|
|
|
return this.ws.addNotificationListener(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
stop(): void {
|
|
|
|
this.ws.stop();
|
|
|
|
}
|
|
|
|
|
2022-01-16 21:47:43 +01:00
|
|
|
runPending(forceNow = false): Promise<void> {
|
2021-06-17 21:06:45 +02:00
|
|
|
return runPending(this.ws, forceNow);
|
|
|
|
}
|
|
|
|
|
2022-09-21 19:53:38 +02:00
|
|
|
runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
|
2021-06-22 13:52:28 +02:00
|
|
|
return runTaskLoop(this.ws, opts);
|
2021-06-17 21:06:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
handleCoreApiRequest(
|
|
|
|
operation: string,
|
|
|
|
id: string,
|
|
|
|
payload: unknown,
|
|
|
|
): Promise<CoreApiResponse> {
|
|
|
|
return handleCoreApiRequest(this.ws, operation, id, payload);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internal state of the wallet.
|
|
|
|
*
|
|
|
|
* This ties together all the operation implementations.
|
|
|
|
*/
|
|
|
|
class InternalWalletStateImpl implements InternalWalletState {
|
2022-09-23 18:56:21 +02:00
|
|
|
/**
|
|
|
|
* @see {@link InternalWalletState.activeLongpoll}
|
|
|
|
*/
|
|
|
|
activeLongpoll: ActiveLongpollInfo = {};
|
2022-03-29 13:47:32 +02:00
|
|
|
|
2022-03-23 21:24:23 +01:00
|
|
|
cryptoApi: TalerCryptoInterface;
|
|
|
|
cryptoDispatcher: CryptoDispatcher;
|
2021-06-17 21:06:45 +02:00
|
|
|
|
2021-11-23 23:51:12 +01:00
|
|
|
merchantInfoCache: Record<string, MerchantInfo> = {};
|
|
|
|
|
2022-01-16 21:47:43 +01:00
|
|
|
insecureTrustExchange = false;
|
2021-12-08 16:23:00 +01:00
|
|
|
|
2022-05-03 17:53:32 +02:00
|
|
|
batchWithdrawal = false;
|
|
|
|
|
2022-04-11 20:10:16 +02:00
|
|
|
readonly timerGroup: TimerGroup;
|
2021-06-17 21:06:45 +02:00
|
|
|
latch = new AsyncCondition();
|
|
|
|
stopped = false;
|
|
|
|
|
|
|
|
listeners: NotificationListener[] = [];
|
|
|
|
|
2022-01-16 21:47:43 +01:00
|
|
|
initCalled = false;
|
2021-06-17 21:06:45 +02:00
|
|
|
|
2022-10-12 22:27:50 +02:00
|
|
|
devModeActive = false;
|
|
|
|
|
2021-06-17 21:06:45 +02:00
|
|
|
exchangeOps: ExchangeOperations = {
|
|
|
|
getExchangeDetails,
|
|
|
|
getExchangeTrust,
|
|
|
|
updateExchangeFromUrl,
|
|
|
|
};
|
|
|
|
|
2021-06-22 12:18:12 +02:00
|
|
|
recoupOps: RecoupOperations = {
|
2022-01-16 21:47:43 +01:00
|
|
|
createRecoupGroup,
|
|
|
|
processRecoupGroup,
|
2021-06-22 12:18:12 +02:00
|
|
|
};
|
|
|
|
|
2021-11-23 23:51:12 +01:00
|
|
|
merchantOps: MerchantOperations = {
|
2022-01-16 21:47:43 +01:00
|
|
|
getMerchantInfo,
|
2021-11-23 23:51:12 +01:00
|
|
|
};
|
|
|
|
|
2022-10-08 20:56:57 +02:00
|
|
|
refreshOps: RefreshOperations = {
|
|
|
|
createRefreshGroup,
|
|
|
|
};
|
|
|
|
|
2022-01-13 12:08:31 +01:00
|
|
|
// FIXME: Use an LRU cache here.
|
2022-09-16 17:35:06 +02:00
|
|
|
private denomCache: Record<string, DenominationInfo> = {};
|
2021-12-13 11:28:15 +01:00
|
|
|
|
2021-06-17 21:06:45 +02:00
|
|
|
/**
|
|
|
|
* Promises that are waiting for a particular resource.
|
|
|
|
*/
|
|
|
|
private resourceWaiters: Record<string, OpenedPromise<void>[]> = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resources that are currently locked.
|
|
|
|
*/
|
|
|
|
private resourceLocks: Set<string> = new Set();
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
// FIXME: Make this a getter and make
|
|
|
|
// the actual value nullable.
|
|
|
|
// Check if we are in a DB migration / garbage collection
|
|
|
|
// and throw an error in that case.
|
|
|
|
public db: DbAccess<typeof WalletStoresV1>,
|
|
|
|
public http: HttpRequestLibrary,
|
2022-04-11 20:10:16 +02:00
|
|
|
public timer: TimerAPI,
|
2021-06-17 21:06:45 +02:00
|
|
|
cryptoWorkerFactory: CryptoWorkerFactory,
|
|
|
|
) {
|
2022-03-23 21:24:23 +01:00
|
|
|
this.cryptoDispatcher = new CryptoDispatcher(cryptoWorkerFactory);
|
|
|
|
this.cryptoApi = this.cryptoDispatcher.cryptoApi;
|
2022-04-19 17:12:43 +02:00
|
|
|
this.timerGroup = new TimerGroup(timer);
|
2021-06-17 21:06:45 +02:00
|
|
|
}
|
|
|
|
|
2022-01-13 12:08:31 +01:00
|
|
|
async getDenomInfo(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
tx: GetReadWriteAccess<{
|
|
|
|
denominations: typeof WalletStoresV1.denominations;
|
|
|
|
}>,
|
|
|
|
exchangeBaseUrl: string,
|
|
|
|
denomPubHash: string,
|
2022-09-16 17:35:06 +02:00
|
|
|
): Promise<DenominationInfo | undefined> {
|
2022-01-13 12:08:31 +01:00
|
|
|
const key = `${exchangeBaseUrl}:${denomPubHash}`;
|
|
|
|
const cached = this.denomCache[key];
|
|
|
|
if (cached) {
|
|
|
|
return cached;
|
|
|
|
}
|
2022-01-13 22:01:14 +01:00
|
|
|
const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
|
|
|
|
if (d) {
|
2022-09-15 20:16:42 +02:00
|
|
|
return DenominationRecord.toDenomInfo(d);
|
2022-01-13 22:01:14 +01:00
|
|
|
}
|
2022-09-14 21:27:03 +02:00
|
|
|
return undefined;
|
2022-01-13 12:08:31 +01:00
|
|
|
}
|
|
|
|
|
2021-06-17 21:06:45 +02:00
|
|
|
notify(n: WalletNotification): void {
|
2023-01-03 10:51:05 +01:00
|
|
|
logger.trace("Notification", j2s(n));
|
2021-06-17 21:06:45 +02:00
|
|
|
for (const l of this.listeners) {
|
|
|
|
const nc = JSON.parse(JSON.stringify(n));
|
|
|
|
setTimeout(() => {
|
|
|
|
l(nc);
|
|
|
|
}, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addNotificationListener(f: (n: WalletNotification) => void): void {
|
|
|
|
this.listeners.push(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop ongoing processing.
|
|
|
|
*/
|
|
|
|
stop(): void {
|
2022-10-05 18:31:56 +02:00
|
|
|
logger.trace("stopping (at internal wallet state)");
|
2021-06-17 21:06:45 +02:00
|
|
|
this.stopped = true;
|
|
|
|
this.timerGroup.stopCurrentAndFutureTimers();
|
2022-03-23 21:24:23 +01:00
|
|
|
this.cryptoDispatcher.stop();
|
2022-09-23 18:56:21 +02:00
|
|
|
for (const key of Object.keys(this.activeLongpoll)) {
|
2022-10-05 18:31:56 +02:00
|
|
|
logger.trace(`cancelling active longpoll ${key}`);
|
2022-09-23 18:56:21 +02:00
|
|
|
this.activeLongpoll[key].cancel();
|
|
|
|
}
|
2021-06-17 21:06:45 +02:00
|
|
|
}
|
|
|
|
|
2021-06-22 12:18:12 +02:00
|
|
|
async runUntilDone(
|
|
|
|
req: {
|
|
|
|
maxRetries?: number;
|
|
|
|
} = {},
|
|
|
|
): Promise<void> {
|
2021-06-22 13:52:28 +02:00
|
|
|
await runTaskLoop(this, { ...req, stopWhenDone: true });
|
2021-06-22 12:18:12 +02:00
|
|
|
}
|
|
|
|
|
2021-06-17 21:06:45 +02:00
|
|
|
/**
|
|
|
|
* Run an async function after acquiring a list of locks, identified
|
|
|
|
* by string tokens.
|
|
|
|
*/
|
2022-03-10 16:30:24 +01:00
|
|
|
async runSequentialized<T>(
|
|
|
|
tokens: string[],
|
|
|
|
f: () => Promise<T>,
|
|
|
|
): Promise<T> {
|
2021-06-17 21:06:45 +02:00
|
|
|
// Make sure locks are always acquired in the same order
|
|
|
|
tokens = [...tokens].sort();
|
|
|
|
|
|
|
|
for (const token of tokens) {
|
|
|
|
if (this.resourceLocks.has(token)) {
|
|
|
|
const p = openPromise<void>();
|
|
|
|
let waitList = this.resourceWaiters[token];
|
|
|
|
if (!waitList) {
|
|
|
|
waitList = this.resourceWaiters[token] = [];
|
|
|
|
}
|
|
|
|
waitList.push(p);
|
|
|
|
await p.promise;
|
|
|
|
}
|
|
|
|
this.resourceLocks.add(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`);
|
|
|
|
const result = await f();
|
|
|
|
logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`);
|
|
|
|
return result;
|
|
|
|
} finally {
|
|
|
|
for (const token of tokens) {
|
|
|
|
this.resourceLocks.delete(token);
|
|
|
|
let waiter = (this.resourceWaiters[token] ?? []).shift();
|
|
|
|
if (waiter) {
|
|
|
|
waiter.resolve();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|