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 {
|
|
|
|
codecForAny,
|
2021-05-20 18:27:35 +02:00
|
|
|
codecForDeleteTransactionRequest,
|
2021-06-14 19:37:35 +02:00
|
|
|
codecForRetryTransactionRequest,
|
2021-06-15 18:52:43 +02:00
|
|
|
codecForSetWalletDeviceIdRequest,
|
2021-06-10 16:32:37 +02:00
|
|
|
durationFromSpec,
|
|
|
|
durationMin,
|
|
|
|
getDurationRemaining,
|
|
|
|
isTimestampExpired,
|
|
|
|
j2s,
|
2021-05-12 16:18:32 +02:00
|
|
|
TalerErrorCode,
|
2021-06-10 16:32:37 +02:00
|
|
|
Timestamp,
|
|
|
|
timestampMin,
|
2021-05-12 16:18:32 +02:00
|
|
|
} from "@gnu-taler/taler-util";
|
2019-12-02 00:42:40 +01:00
|
|
|
import {
|
2021-02-08 15:38:34 +01:00
|
|
|
addBackupProvider,
|
|
|
|
codecForAddBackupProviderRequest,
|
|
|
|
getBackupInfo,
|
|
|
|
getBackupRecovery,
|
|
|
|
loadBackupRecovery,
|
|
|
|
runBackupCycle,
|
|
|
|
} from "./operations/backup";
|
2021-03-10 12:00:30 +01:00
|
|
|
import { exportBackup } from "./operations/backup/export";
|
2021-02-08 15:38:34 +01:00
|
|
|
import { getBalances } from "./operations/balance";
|
|
|
|
import {
|
|
|
|
createDepositGroup,
|
|
|
|
processDepositGroup,
|
|
|
|
trackDepositGroup,
|
|
|
|
} from "./operations/deposits";
|
|
|
|
import {
|
|
|
|
makeErrorDetails,
|
|
|
|
OperationFailedAndReportedError,
|
|
|
|
OperationFailedError,
|
|
|
|
} from "./operations/errors";
|
|
|
|
import {
|
|
|
|
acceptExchangeTermsOfService,
|
2021-06-09 15:14:17 +02:00
|
|
|
getExchangeDetails,
|
2021-02-08 15:38:34 +01:00
|
|
|
updateExchangeFromUrl,
|
|
|
|
} from "./operations/exchanges";
|
2019-12-02 00:42:40 +01:00
|
|
|
import {
|
|
|
|
confirmPay,
|
2021-02-08 15:38:34 +01:00
|
|
|
preparePayForUri,
|
2019-12-03 00:52:15 +01:00
|
|
|
processDownloadProposal,
|
2019-12-06 00:24:34 +01:00
|
|
|
processPurchasePay,
|
2019-12-12 20:53:15 +01:00
|
|
|
} from "./operations/pay";
|
2021-02-08 15:38:34 +01:00
|
|
|
import { getPendingOperations } from "./operations/pending";
|
|
|
|
import { processRecoupGroup } from "./operations/recoup";
|
|
|
|
import {
|
|
|
|
autoRefresh,
|
|
|
|
createRefreshGroup,
|
|
|
|
processRefreshGroup,
|
|
|
|
} from "./operations/refresh";
|
|
|
|
import {
|
|
|
|
abortFailedPayWithRefund,
|
|
|
|
applyRefund,
|
|
|
|
processPurchaseQueryRefund,
|
|
|
|
} from "./operations/refund";
|
|
|
|
import {
|
|
|
|
createReserve,
|
|
|
|
createTalerWithdrawReserve,
|
|
|
|
getFundingPaytoUris,
|
|
|
|
processReserve,
|
|
|
|
} from "./operations/reserves";
|
|
|
|
import { InternalWalletState } from "./operations/state";
|
|
|
|
import {
|
|
|
|
runIntegrationTest,
|
|
|
|
testPay,
|
|
|
|
withdrawTestBalance,
|
|
|
|
} from "./operations/testing";
|
|
|
|
import { acceptTip, prepareTip, processTip } from "./operations/tip";
|
2021-06-15 18:52:43 +02:00
|
|
|
import {
|
|
|
|
deleteTransaction,
|
|
|
|
getTransactions,
|
|
|
|
retryTransaction,
|
|
|
|
} from "./operations/transactions";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
getExchangeWithdrawalInfo,
|
|
|
|
getWithdrawalDetailsForUri,
|
|
|
|
processWithdrawGroup,
|
|
|
|
} from "./operations/withdraw";
|
2016-05-24 01:53:56 +02:00
|
|
|
import {
|
2021-05-20 13:14:47 +02:00
|
|
|
AuditorTrustRecord,
|
2021-02-08 15:38:34 +01:00
|
|
|
CoinSourceType,
|
2019-11-21 23:09:43 +01:00
|
|
|
ReserveRecordStatus,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "./db.js";
|
2021-06-15 18:52:43 +02:00
|
|
|
import { NotificationType } from "@gnu-taler/taler-util";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
PendingOperationInfo,
|
|
|
|
PendingOperationType,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "./pending-types.js";
|
|
|
|
import { CoinDumpJson } from "@gnu-taler/taler-util";
|
2021-02-08 15:38:34 +01:00
|
|
|
import {
|
|
|
|
codecForTransactionsRequest,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "@gnu-taler/taler-util";
|
2018-01-03 14:42:06 +01:00
|
|
|
import {
|
2020-07-16 19:22:56 +02:00
|
|
|
AcceptManualWithdrawalResult,
|
2021-02-08 15:38:34 +01:00
|
|
|
AcceptWithdrawalResponse,
|
|
|
|
codecForAbortPayWithRefundRequest,
|
|
|
|
codecForAcceptBankIntegratedWithdrawalRequest,
|
2020-08-14 09:36:42 +02:00
|
|
|
codecForAcceptExchangeTosRequest,
|
2021-02-08 15:38:34 +01:00
|
|
|
codecForAcceptManualWithdrawalRequet,
|
|
|
|
codecForAcceptTipRequest,
|
|
|
|
codecForAddExchangeRequest,
|
2020-08-14 09:36:42 +02:00
|
|
|
codecForApplyRefundRequest,
|
|
|
|
codecForConfirmPayRequest,
|
2021-02-08 15:38:34 +01:00
|
|
|
codecForCreateDepositGroupRequest,
|
2020-09-03 22:50:20 +02:00
|
|
|
codecForForceRefreshRequest,
|
2021-02-08 15:38:34 +01:00
|
|
|
codecForGetExchangeTosRequest,
|
|
|
|
codecForGetWithdrawalDetailsForAmountRequest,
|
|
|
|
codecForGetWithdrawalDetailsForUri,
|
|
|
|
codecForIntegrationTestArgs,
|
|
|
|
codecForPreparePayRequest,
|
2020-09-08 14:10:47 +02:00
|
|
|
codecForPrepareTipRequest,
|
2021-02-08 15:38:34 +01:00
|
|
|
codecForSetCoinSuspendedRequest,
|
|
|
|
codecForTestPayArgs,
|
|
|
|
codecForTrackDepositGroupRequest,
|
|
|
|
codecForWithdrawTestBalance,
|
|
|
|
CoreApiResponse,
|
|
|
|
ExchangeListItem,
|
|
|
|
ExchangesListRespose,
|
|
|
|
GetExchangeTosResult,
|
|
|
|
ManualWithdrawalDetails,
|
|
|
|
RefreshReason,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "@gnu-taler/taler-util";
|
|
|
|
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
|
2019-12-02 00:42:40 +01:00
|
|
|
import { assertUnreachable } from "./util/assertUnreachable";
|
2021-06-08 20:58:13 +02:00
|
|
|
import { Logger } from "@gnu-taler/taler-util";
|
2021-06-14 11:21:29 +02:00
|
|
|
import { setWalletDeviceId } from "./operations/backup/state.js";
|
2021-06-15 18:58:11 +02:00
|
|
|
import { WalletCoreApiClient } 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
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2019-11-21 23:09:43 +01:00
|
|
|
const logger = new Logger("wallet.ts");
|
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
async function getWithdrawalDetailsForAmount(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
exchangeBaseUrl: string,
|
|
|
|
amount: AmountJson,
|
|
|
|
): Promise<ManualWithdrawalDetails> {
|
|
|
|
const wi = await getExchangeWithdrawalInfo(ws, exchangeBaseUrl, amount);
|
|
|
|
const paytoUris = wi.exchangeDetails.wireInfo.accounts.map(
|
|
|
|
(x) => x.payto_uri,
|
|
|
|
);
|
|
|
|
if (!paytoUris) {
|
|
|
|
throw Error("exchange is in invalid state");
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
return {
|
|
|
|
amountRaw: Amounts.stringify(amount),
|
|
|
|
amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue),
|
|
|
|
paytoUris,
|
|
|
|
tosAccepted: wi.termsOfServiceAccepted,
|
|
|
|
};
|
|
|
|
}
|
2019-12-02 00:42:40 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Execute one operation based on the pending operation info record.
|
|
|
|
*/
|
|
|
|
async function processOnePendingOperation(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
pending: PendingOperationInfo,
|
|
|
|
forceNow = false,
|
|
|
|
): Promise<void> {
|
|
|
|
logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`);
|
|
|
|
switch (pending.type) {
|
|
|
|
case PendingOperationType.ExchangeUpdate:
|
|
|
|
await updateExchangeFromUrl(ws, pending.exchangeBaseUrl, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Refresh:
|
|
|
|
await processRefreshGroup(ws, pending.refreshGroupId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Reserve:
|
|
|
|
await processReserve(ws, pending.reservePub, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Withdraw:
|
|
|
|
await processWithdrawGroup(ws, pending.withdrawalGroupId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.ProposalDownload:
|
|
|
|
await processDownloadProposal(ws, pending.proposalId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.TipPickup:
|
|
|
|
await processTip(ws, pending.tipId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Pay:
|
|
|
|
await processPurchasePay(ws, pending.proposalId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.RefundQuery:
|
|
|
|
await processPurchaseQueryRefund(ws, pending.proposalId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Recoup:
|
|
|
|
await processRecoupGroup(ws, pending.recoupGroupId, forceNow);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.ExchangeCheckRefresh:
|
|
|
|
await autoRefresh(ws, pending.exchangeBaseUrl);
|
|
|
|
break;
|
|
|
|
case PendingOperationType.Deposit:
|
|
|
|
await processDepositGroup(ws, pending.depositGroupId);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assertUnreachable(pending);
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2019-12-02 00:42:40 +01: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) {
|
|
|
|
if (!forceNow && !isTimestampExpired(p.timestampDue)) {
|
|
|
|
continue;
|
2020-07-11 09:56:07 +02:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
try {
|
|
|
|
await processOnePendingOperation(ws, p, forceNow);
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof OperationFailedAndReportedError) {
|
|
|
|
console.error(
|
|
|
|
"Operation failed:",
|
|
|
|
JSON.stringify(e.operationError, undefined, 2),
|
2019-12-09 19:59:08 +01:00
|
|
|
);
|
2021-06-15 18:52:43 +02:00
|
|
|
} else {
|
|
|
|
console.error(e);
|
|
|
|
}
|
2019-11-21 23:09:43 +01:00
|
|
|
}
|
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-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Run the wallet until there are no more pending operations that give
|
|
|
|
* liveness left. The wallet will be in a stopped state when this function
|
|
|
|
* returns without resolving to an exception.
|
|
|
|
*/
|
|
|
|
export async function runUntilDone(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
req: {
|
|
|
|
maxRetries?: number;
|
|
|
|
} = {},
|
|
|
|
): Promise<void> {
|
|
|
|
let done = false;
|
|
|
|
const p = new Promise<void>((resolve, reject) => {
|
|
|
|
// Monitor for conditions that means we're done or we
|
|
|
|
// should quit with an error (due to exceeded retries).
|
|
|
|
ws.addNotificationListener((n) => {
|
|
|
|
if (done) {
|
|
|
|
return;
|
2021-06-10 16:32:37 +02:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
if (
|
|
|
|
n.type === NotificationType.WaitingForRetry &&
|
|
|
|
n.numGivingLiveness == 0
|
|
|
|
) {
|
|
|
|
done = true;
|
|
|
|
logger.trace("no liveness-giving operations left");
|
|
|
|
resolve();
|
2019-11-30 00:36:20 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
const maxRetries = req.maxRetries;
|
|
|
|
if (!maxRetries) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
getPendingOperations(ws)
|
|
|
|
.then((pending) => {
|
|
|
|
for (const p of pending.pendingOperations) {
|
|
|
|
if (p.retryInfo && p.retryInfo.retryCounter > maxRetries) {
|
|
|
|
console.warn(
|
|
|
|
`stopping, as ${maxRetries} retries are exceeded in an operation of type ${p.type}`,
|
|
|
|
);
|
|
|
|
ws.stop();
|
|
|
|
done = true;
|
|
|
|
resolve();
|
2020-09-01 14:30:46 +02:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.catch((e) => {
|
|
|
|
logger.error(e);
|
|
|
|
reject(e);
|
|
|
|
});
|
2020-01-18 23:32:03 +01:00
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
// Run this asynchronously
|
|
|
|
runRetryLoop(ws).catch((e) => {
|
|
|
|
logger.error("exception in wallet retry loop");
|
|
|
|
reject(e);
|
2019-12-05 19:38:19 +01:00
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
});
|
|
|
|
await p;
|
|
|
|
}
|
2019-12-05 19:38:19 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Process pending operations and wait for scheduled operations in
|
|
|
|
* a loop until the wallet is stopped explicitly.
|
|
|
|
*/
|
|
|
|
export async function runRetryLoop(ws: InternalWalletState): Promise<void> {
|
|
|
|
// Make sure we only run one main loop at a time.
|
|
|
|
return ws.memoRunRetryLoop.memo(async () => {
|
|
|
|
try {
|
|
|
|
await runRetryLoopImpl(ws);
|
|
|
|
} catch (e) {
|
|
|
|
console.error("error during retry loop execution", e);
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function runRetryLoopImpl(ws: InternalWalletState): Promise<void> {
|
|
|
|
for (let iteration = 0; !ws.stopped; iteration++) {
|
|
|
|
const pending = await getPendingOperations(ws);
|
|
|
|
logger.trace(`pending operations: ${j2s(pending)}`);
|
|
|
|
let numGivingLiveness = 0;
|
|
|
|
let numDue = 0;
|
|
|
|
let minDue: Timestamp = { t_ms: "never" };
|
|
|
|
for (const p of pending.pendingOperations) {
|
|
|
|
minDue = timestampMin(minDue, p.timestampDue);
|
|
|
|
if (isTimestampExpired(p.timestampDue)) {
|
|
|
|
numDue++;
|
|
|
|
}
|
|
|
|
if (p.givesLifeness) {
|
|
|
|
numGivingLiveness++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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,
|
|
|
|
}),
|
|
|
|
getDurationRemaining(minDue),
|
|
|
|
);
|
|
|
|
logger.trace(`waiting for at most ${dt.d_ms} ms`);
|
|
|
|
const timeout = ws.timerGroup.resolveAfter(dt);
|
|
|
|
ws.notify({
|
|
|
|
type: NotificationType.WaitingForRetry,
|
|
|
|
numGivingLiveness,
|
|
|
|
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) {
|
2021-06-15 18:52:43 +02:00
|
|
|
if (!isTimestampExpired(p.timestampDue)) {
|
|
|
|
continue;
|
2020-09-03 17:08:26 +02:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
try {
|
|
|
|
await processOnePendingOperation(ws, p);
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof OperationFailedAndReportedError) {
|
|
|
|
logger.warn("operation processed resulted in reported error");
|
|
|
|
} else {
|
|
|
|
logger.error("Uncaught exception", e);
|
|
|
|
ws.notify({
|
|
|
|
type: NotificationType.InternalError,
|
|
|
|
message: "uncaught exception",
|
|
|
|
exception: e,
|
|
|
|
});
|
2019-12-05 19:38:19 +01:00
|
|
|
}
|
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
ws.notify({
|
|
|
|
type: NotificationType.PendingOperationProcessed,
|
|
|
|
});
|
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");
|
|
|
|
}
|
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
|
|
|
|
.mktx((x) => ({ config: x.config, auditorTrustStore: x.auditorTrust }))
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
let applied = false;
|
|
|
|
await tx.config.iter().forEach((x) => {
|
|
|
|
if (x.key == "currencyDefaultsApplied" && x.value == true) {
|
|
|
|
applied = true;
|
2019-11-20 19:48:43 +01:00
|
|
|
}
|
2021-06-09 15:14:17 +02:00
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
if (!applied) {
|
|
|
|
for (const c of builtinAuditors) {
|
|
|
|
await tx.auditorTrustStore.put(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2017-03-24 16:59:23 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Create a reserve, but do not flag it as confirmed yet.
|
|
|
|
*
|
|
|
|
* Adds the corresponding exchange as a trusted exchange if it is neither
|
|
|
|
* audited nor trusted already.
|
|
|
|
*/
|
|
|
|
async function acceptManualWithdrawal(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
exchangeBaseUrl: string,
|
|
|
|
amount: AmountJson,
|
|
|
|
): Promise<AcceptManualWithdrawalResult> {
|
|
|
|
try {
|
|
|
|
const resp = await createReserve(ws, {
|
|
|
|
amount,
|
|
|
|
exchange: exchangeBaseUrl,
|
|
|
|
});
|
|
|
|
const exchangePaytoUris = await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
exchanges: x.exchanges,
|
|
|
|
exchangeDetails: x.exchangeDetails,
|
|
|
|
reserves: x.reserves,
|
|
|
|
}))
|
|
|
|
.runReadWrite((tx) => getFundingPaytoUris(tx, resp.reservePub));
|
|
|
|
return {
|
|
|
|
reservePub: resp.reservePub,
|
|
|
|
exchangePaytoUris,
|
|
|
|
};
|
|
|
|
} finally {
|
|
|
|
ws.latch.trigger();
|
2018-01-17 03:49:54 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2018-01-17 03:49:54 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
async function getExchangeTos(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
exchangeBaseUrl: string,
|
|
|
|
): Promise<GetExchangeTosResult> {
|
|
|
|
const { exchange, exchangeDetails } = await updateExchangeFromUrl(
|
|
|
|
ws,
|
|
|
|
exchangeBaseUrl,
|
|
|
|
);
|
|
|
|
const tos = exchangeDetails.termsOfServiceText;
|
|
|
|
const currentEtag = exchangeDetails.termsOfServiceLastEtag;
|
|
|
|
if (!tos || !currentEtag) {
|
|
|
|
throw Error("exchange is in invalid state");
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
return {
|
|
|
|
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
|
|
|
|
currentEtag,
|
|
|
|
tos,
|
|
|
|
};
|
|
|
|
}
|
2019-11-30 00:36:20 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
async function getExchanges(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
): Promise<ExchangesListRespose> {
|
|
|
|
const exchanges: ExchangeListItem[] = [];
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
exchanges: x.exchanges,
|
|
|
|
exchangeDetails: x.exchangeDetails,
|
|
|
|
}))
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
const exchangeRecords = await tx.exchanges.iter().toArray();
|
|
|
|
for (const r of exchangeRecords) {
|
|
|
|
const dp = r.detailsPointer;
|
|
|
|
if (!dp) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const { currency, masterPublicKey } = dp;
|
|
|
|
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
|
|
|
|
if (!exchangeDetails) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
exchanges.push({
|
|
|
|
exchangeBaseUrl: r.baseUrl,
|
|
|
|
currency,
|
|
|
|
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return { exchanges };
|
|
|
|
}
|
|
|
|
|
|
|
|
async function acceptWithdrawal(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
talerWithdrawUri: string,
|
|
|
|
selectedExchange: string,
|
|
|
|
): Promise<AcceptWithdrawalResponse> {
|
|
|
|
try {
|
|
|
|
return createTalerWithdrawReserve(ws, talerWithdrawUri, selectedExchange);
|
|
|
|
} finally {
|
|
|
|
ws.latch.trigger();
|
2016-01-24 19:57:09 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2016-01-24 19:57:09 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Inform the wallet that the status of a reserve has changed (e.g. due to a
|
|
|
|
* confirmation from the bank.).
|
|
|
|
*/
|
|
|
|
export async function handleNotifyReserve(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
): Promise<void> {
|
|
|
|
const reserves = await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
reserves: x.reserves,
|
|
|
|
}))
|
|
|
|
.runReadOnly(async (tx) => {
|
|
|
|
return tx.reserves.iter().toArray();
|
|
|
|
});
|
|
|
|
for (const r of reserves) {
|
|
|
|
if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) {
|
|
|
|
try {
|
|
|
|
processReserve(ws, r.reservePub);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
2019-12-02 17:35:47 +01:00
|
|
|
}
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
}
|
2016-09-28 18:54:48 +02:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
async function setCoinSuspended(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
coinPub: string,
|
|
|
|
suspended: boolean,
|
|
|
|
): Promise<void> {
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
coins: x.coins,
|
|
|
|
}))
|
|
|
|
.runReadWrite(async (tx) => {
|
|
|
|
const c = await tx.coins.get(coinPub);
|
|
|
|
if (!c) {
|
|
|
|
logger.warn(`coin ${coinPub} not found, won't suspend`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
c.suspended = suspended;
|
|
|
|
await tx.coins.put(c);
|
|
|
|
});
|
|
|
|
}
|
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: [] };
|
|
|
|
await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
coins: x.coins,
|
|
|
|
denominations: x.denominations,
|
|
|
|
withdrawalGroups: x.withdrawalGroups,
|
|
|
|
}))
|
|
|
|
.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;
|
|
|
|
}
|
|
|
|
coinsJson.coins.push({
|
|
|
|
coin_pub: c.coinPub,
|
|
|
|
denom_pub: c.denomPub,
|
|
|
|
denom_pub_hash: c.denomPubHash,
|
|
|
|
denom_value: Amounts.stringify(denom.value),
|
|
|
|
exchange_base_url: c.exchangeBaseUrl,
|
|
|
|
refresh_parent_coin_pub: refreshParentCoinPub,
|
|
|
|
remaining_value: Amounts.stringify(c.currentAmount),
|
|
|
|
withdrawal_reserve_pub: withdrawalReservePub,
|
|
|
|
coin_suspended: c.suspended,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return coinsJson;
|
|
|
|
}
|
2016-01-06 15:39:22 +01:00
|
|
|
|
2021-06-14 11:21:29 +02: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":
|
|
|
|
throw new OperationFailedError(res.error);
|
|
|
|
case "response":
|
|
|
|
return res.result;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
return client;
|
|
|
|
}
|
2016-11-16 01:59:39 +01:00
|
|
|
|
2021-06-15 18:52:43 +02:00
|
|
|
/**
|
|
|
|
* Implementation of the "wallet-core" API.
|
|
|
|
*/
|
|
|
|
async function dispatchRequestInternal(
|
|
|
|
ws: InternalWalletState,
|
|
|
|
operation: string,
|
|
|
|
payload: unknown,
|
|
|
|
): Promise<Record<string, any>> {
|
|
|
|
if (ws.initCalled && operation !== "initWallet") {
|
|
|
|
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
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
switch (operation) {
|
|
|
|
case "initWallet": {
|
|
|
|
ws.initCalled = true;
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "withdrawTestkudos": {
|
|
|
|
await withdrawTestBalance(
|
|
|
|
ws,
|
|
|
|
"TESTKUDOS:10",
|
|
|
|
"https://bank.test.taler.net/",
|
|
|
|
"https://exchange.test.taler.net/",
|
|
|
|
);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "withdrawTestBalance": {
|
|
|
|
const req = codecForWithdrawTestBalance().decode(payload);
|
|
|
|
await withdrawTestBalance(
|
|
|
|
ws,
|
|
|
|
req.amount,
|
|
|
|
req.bankBaseUrl,
|
|
|
|
req.exchangeBaseUrl,
|
|
|
|
);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "runIntegrationTest": {
|
|
|
|
const req = codecForIntegrationTestArgs().decode(payload);
|
|
|
|
await runIntegrationTest(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "testPay": {
|
|
|
|
const req = codecForTestPayArgs().decode(payload);
|
|
|
|
await testPay(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "getTransactions": {
|
|
|
|
const req = codecForTransactionsRequest().decode(payload);
|
|
|
|
return await getTransactions(ws, req);
|
|
|
|
}
|
|
|
|
case "addExchange": {
|
|
|
|
const req = codecForAddExchangeRequest().decode(payload);
|
|
|
|
await updateExchangeFromUrl(ws, req.exchangeBaseUrl, req.forceUpdate);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "listExchanges": {
|
|
|
|
return await getExchanges(ws);
|
|
|
|
}
|
|
|
|
case "getWithdrawalDetailsForUri": {
|
|
|
|
const req = codecForGetWithdrawalDetailsForUri().decode(payload);
|
|
|
|
return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
|
|
|
|
}
|
|
|
|
case "acceptManualWithdrawal": {
|
|
|
|
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
|
|
|
|
const res = await acceptManualWithdrawal(
|
|
|
|
ws,
|
|
|
|
req.exchangeBaseUrl,
|
|
|
|
Amounts.parseOrThrow(req.amount),
|
|
|
|
);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
case "getWithdrawalDetailsForAmount": {
|
|
|
|
const req = codecForGetWithdrawalDetailsForAmountRequest().decode(
|
|
|
|
payload,
|
|
|
|
);
|
|
|
|
return await getWithdrawalDetailsForAmount(
|
|
|
|
ws,
|
|
|
|
req.exchangeBaseUrl,
|
|
|
|
Amounts.parseOrThrow(req.amount),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
case "getBalances": {
|
|
|
|
return await getBalances(ws);
|
|
|
|
}
|
|
|
|
case "getPendingOperations": {
|
|
|
|
return await getPendingOperations(ws);
|
|
|
|
}
|
|
|
|
case "setExchangeTosAccepted": {
|
|
|
|
const req = codecForAcceptExchangeTosRequest().decode(payload);
|
|
|
|
await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl, req.etag);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "applyRefund": {
|
|
|
|
const req = codecForApplyRefundRequest().decode(payload);
|
|
|
|
return await applyRefund(ws, req.talerRefundUri);
|
|
|
|
}
|
|
|
|
case "acceptBankIntegratedWithdrawal": {
|
|
|
|
const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(
|
|
|
|
payload,
|
|
|
|
);
|
|
|
|
return await acceptWithdrawal(
|
|
|
|
ws,
|
|
|
|
req.talerWithdrawUri,
|
|
|
|
req.exchangeBaseUrl,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
case "getExchangeTos": {
|
|
|
|
const req = codecForGetExchangeTosRequest().decode(payload);
|
|
|
|
return getExchangeTos(ws, req.exchangeBaseUrl);
|
|
|
|
}
|
|
|
|
case "retryPendingNow": {
|
|
|
|
await runPending(ws, true);
|
|
|
|
return {};
|
|
|
|
}
|
2021-06-17 13:34:59 +02:00
|
|
|
// FIXME: Deprecate one of the aliases!
|
|
|
|
case "preparePayForUri":
|
2021-06-15 18:52:43 +02:00
|
|
|
case "preparePay": {
|
|
|
|
const req = codecForPreparePayRequest().decode(payload);
|
|
|
|
return await preparePayForUri(ws, req.talerPayUri);
|
|
|
|
}
|
|
|
|
case "confirmPay": {
|
|
|
|
const req = codecForConfirmPayRequest().decode(payload);
|
|
|
|
return await confirmPay(ws, req.proposalId, req.sessionId);
|
|
|
|
}
|
|
|
|
case "abortFailedPayWithRefund": {
|
|
|
|
const req = codecForAbortPayWithRefundRequest().decode(payload);
|
|
|
|
await abortFailedPayWithRefund(ws, req.proposalId);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "dumpCoins": {
|
|
|
|
return await dumpCoins(ws);
|
|
|
|
}
|
|
|
|
case "setCoinSuspended": {
|
|
|
|
const req = codecForSetCoinSuspendedRequest().decode(payload);
|
|
|
|
await setCoinSuspended(ws, req.coinPub, req.suspended);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "forceRefresh": {
|
|
|
|
const req = codecForForceRefreshRequest().decode(payload);
|
|
|
|
const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
|
|
|
|
const refreshGroupId = await ws.db
|
2021-06-09 15:14:17 +02:00
|
|
|
.mktx((x) => ({
|
|
|
|
refreshGroups: x.refreshGroups,
|
|
|
|
denominations: x.denominations,
|
|
|
|
coins: x.coins,
|
|
|
|
}))
|
|
|
|
.runReadWrite(async (tx) => {
|
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,
|
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
|
|
|
});
|
2021-06-15 18:52:43 +02:00
|
|
|
processRefreshGroup(ws, refreshGroupId.refreshGroupId, true).catch(
|
|
|
|
(x) => {
|
|
|
|
logger.error(x);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
refreshGroupId,
|
|
|
|
};
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
case "prepareTip": {
|
|
|
|
const req = codecForPrepareTipRequest().decode(payload);
|
|
|
|
return await prepareTip(ws, req.talerTipUri);
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
case "acceptTip": {
|
|
|
|
const req = codecForAcceptTipRequest().decode(payload);
|
|
|
|
await acceptTip(ws, req.walletTipId);
|
|
|
|
return {};
|
2019-11-30 00:36:20 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
case "exportBackupPlain": {
|
|
|
|
return exportBackup(ws);
|
2019-12-06 11:01:39 +01:00
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
case "addBackupProvider": {
|
|
|
|
const req = codecForAddBackupProviderRequest().decode(payload);
|
|
|
|
await addBackupProvider(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "runBackupCycle": {
|
|
|
|
await runBackupCycle(ws);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "exportBackupRecovery": {
|
|
|
|
const resp = await getBackupRecovery(ws);
|
|
|
|
return resp;
|
|
|
|
}
|
|
|
|
case "importBackupRecovery": {
|
|
|
|
const req = codecForAny().decode(payload);
|
|
|
|
await loadBackupRecovery(ws, req);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "getBackupInfo": {
|
|
|
|
const resp = await getBackupInfo(ws);
|
|
|
|
return resp;
|
|
|
|
}
|
|
|
|
case "createDepositGroup": {
|
|
|
|
const req = codecForCreateDepositGroupRequest().decode(payload);
|
|
|
|
return await createDepositGroup(ws, req);
|
|
|
|
}
|
|
|
|
case "trackDepositGroup": {
|
|
|
|
const req = codecForTrackDepositGroupRequest().decode(payload);
|
|
|
|
return trackDepositGroup(ws, req);
|
|
|
|
}
|
|
|
|
case "deleteTransaction": {
|
|
|
|
const req = codecForDeleteTransactionRequest().decode(payload);
|
|
|
|
await deleteTransaction(ws, req.transactionId);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "retryTransaction": {
|
|
|
|
const req = codecForRetryTransactionRequest().decode(payload);
|
|
|
|
await retryTransaction(ws, req.transactionId);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "setWalletDeviceId": {
|
|
|
|
const req = codecForSetWalletDeviceIdRequest().decode(payload);
|
|
|
|
await setWalletDeviceId(ws, req.walletDeviceId);
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
case "listCurrencies": {
|
|
|
|
return await ws.db
|
|
|
|
.mktx((x) => ({
|
|
|
|
auditorTrust: x.auditorTrust,
|
|
|
|
exchangeTrust: x.exchangeTrust,
|
|
|
|
}))
|
|
|
|
.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
|
|
|
}
|
|
|
|
}
|
2021-06-15 18:52:43 +02:00
|
|
|
throw OperationFailedError.fromCode(
|
|
|
|
TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN,
|
|
|
|
"unknown operation",
|
|
|
|
{
|
|
|
|
operation,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2020-08-14 09:36:42 +02:00
|
|
|
|
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 {
|
|
|
|
const result = await dispatchRequestInternal(ws, operation, payload);
|
|
|
|
return {
|
|
|
|
type: "response",
|
|
|
|
operation,
|
|
|
|
id,
|
|
|
|
result,
|
|
|
|
};
|
|
|
|
} catch (e) {
|
|
|
|
if (
|
|
|
|
e instanceof OperationFailedError ||
|
|
|
|
e instanceof OperationFailedAndReportedError
|
|
|
|
) {
|
2020-08-14 09:36:42 +02:00
|
|
|
return {
|
2021-06-15 18:52:43 +02:00
|
|
|
type: "error",
|
2020-08-14 09:36:42 +02:00
|
|
|
operation,
|
|
|
|
id,
|
2021-06-15 18:52:43 +02:00
|
|
|
error: e.operationError,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
type: "error",
|
|
|
|
operation,
|
|
|
|
id,
|
|
|
|
error: makeErrorDetails(
|
|
|
|
TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
|
|
|
|
`unexpected exception: ${e}`,
|
|
|
|
{},
|
|
|
|
),
|
2020-08-14 09:36:42 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2016-10-18 01:16:31 +02:00
|
|
|
}
|