summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/reserves.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/reserves.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/reserves.ts843
1 files changed, 0 insertions, 843 deletions
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts
deleted file mode 100644
index b33f574f4..000000000
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ /dev/null
@@ -1,843 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AbsoluteTime,
- AcceptWithdrawalResponse,
- addPaytoQueryParams,
- Amounts,
- canonicalizeBaseUrl,
- codecForBankWithdrawalOperationPostResponse,
- codecForReserveStatus,
- codecForWithdrawOperationStatusResponse,
- CreateReserveRequest,
- CreateReserveResponse,
- Duration,
- durationMax,
- durationMin,
- encodeCrock,
- ForcedDenomSel,
- getRandomBytes,
- j2s,
- Logger,
- NotificationType,
- randomBytes,
- TalerErrorCode,
- TalerErrorDetail,
- URL,
-} from "@gnu-taler/taler-util";
-import {
- DenomSelectionState,
- OperationStatus,
- ReserveBankInfo,
- ReserveRecord,
- ReserveRecordStatus,
- WalletStoresV1,
- WithdrawalGroupRecord,
-} from "../db.js";
-import { TalerError } from "../errors.js";
-import { InternalWalletState } from "../internal-wallet-state.js";
-import { assertUnreachable } from "../util/assertUnreachable.js";
-import {
- readSuccessResponseJsonOrErrorCode,
- readSuccessResponseJsonOrThrow,
- throwUnexpectedRequestError,
-} from "../util/http.js";
-import { GetReadOnlyAccess } from "../util/query.js";
-import { RetryInfo } from "../util/retries.js";
-import { guardOperationException } from "./common.js";
-import {
- getExchangeDetails,
- getExchangePaytoUri,
- getExchangeTrust,
- updateExchangeFromUrl,
-} from "./exchanges.js";
-import {
- getBankWithdrawalInfo,
- getCandidateWithdrawalDenoms,
- processWithdrawGroup,
- selectForcedWithdrawalDenominations,
- selectWithdrawalDenominations,
- updateWithdrawalDenoms,
-} from "./withdraw.js";
-
-const logger = new Logger("taler-wallet-core:reserves.ts");
-
-/**
- * Set up the reserve's retry timeout in preparation for
- * processing the reserve.
- */
-async function setupReserveRetry(
- ws: InternalWalletState,
- reservePub: string,
- options: {
- reset: boolean;
- },
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- return;
- }
- if (options.reset) {
- r.retryInfo = RetryInfo.reset();
- } else {
- r.retryInfo = RetryInfo.increment(r.retryInfo);
- }
- delete r.lastError;
- await tx.reserves.put(r);
- });
-}
-
-/**
- * Report an error that happened while processing the reserve.
- *
- * Logs the error via a notification and by storing it in the database.
- */
-async function reportReserveError(
- ws: InternalWalletState,
- reservePub: string,
- err: TalerErrorDetail,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- return;
- }
- if (!r.retryInfo) {
- logger.error(`got reserve error for inactive reserve (no retryInfo)`);
- return;
- }
- r.lastError = err;
- await tx.reserves.put(r);
- });
- ws.notify({
- type: NotificationType.ReserveOperationError,
- error: err,
- });
-}
-
-/**
- * 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.
- */
-export async function createReserve(
- ws: InternalWalletState,
- req: CreateReserveRequest,
-): Promise<CreateReserveResponse> {
- const keypair = await ws.cryptoApi.createEddsaKeypair({});
- const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
- const canonExchange = canonicalizeBaseUrl(req.exchange);
-
- let reserveStatus;
- if (req.bankWithdrawStatusUrl) {
- reserveStatus = ReserveRecordStatus.RegisteringBank;
- } else {
- reserveStatus = ReserveRecordStatus.QueryingStatus;
- }
-
- let bankInfo: ReserveBankInfo | undefined;
-
- if (req.bankWithdrawStatusUrl) {
- if (!req.exchangePaytoUri) {
- throw Error(
- "Exchange payto URI must be specified for a bank-integrated withdrawal",
- );
- }
- bankInfo = {
- statusUrl: req.bankWithdrawStatusUrl,
- exchangePaytoUri: req.exchangePaytoUri,
- };
- }
-
- const initialWithdrawalGroupId = encodeCrock(getRandomBytes(32));
-
- await updateWithdrawalDenoms(ws, canonExchange);
- const denoms = await getCandidateWithdrawalDenoms(ws, canonExchange);
-
- let initialDenomSel: DenomSelectionState;
- if (req.forcedDenomSel) {
- logger.warn("using forced denom selection");
- initialDenomSel = selectForcedWithdrawalDenominations(
- req.amount,
- denoms,
- req.forcedDenomSel,
- );
- } else {
- initialDenomSel = selectWithdrawalDenominations(req.amount, denoms);
- }
-
- const reserveRecord: ReserveRecord = {
- instructedAmount: req.amount,
- initialWithdrawalGroupId,
- initialDenomSel,
- initialWithdrawalStarted: false,
- timestampCreated: now,
- exchangeBaseUrl: canonExchange,
- reservePriv: keypair.priv,
- reservePub: keypair.pub,
- senderWire: req.senderWire,
- timestampBankConfirmed: undefined,
- timestampReserveInfoPosted: undefined,
- bankInfo,
- reserveStatus,
- retryInfo: RetryInfo.reset(),
- lastError: undefined,
- currency: req.amount.currency,
- operationStatus: OperationStatus.Pending,
- restrictAge: req.restrictAge,
- };
-
- const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange);
- const exchangeDetails = exchangeInfo.exchangeDetails;
- if (!exchangeDetails) {
- logger.trace(exchangeDetails);
- throw Error("exchange not updated");
- }
- const { isAudited, isTrusted } = await getExchangeTrust(
- ws,
- exchangeInfo.exchange,
- );
-
- const resp = await ws.db
- .mktx((x) => ({
- exchangeTrust: x.exchangeTrust,
- reserves: x.reserves,
- bankWithdrawUris: x.bankWithdrawUris,
- }))
- .runReadWrite(async (tx) => {
- // Check if we have already created a reserve for that bankWithdrawStatusUrl
- if (reserveRecord.bankInfo?.statusUrl) {
- const bwi = await tx.bankWithdrawUris.get(
- reserveRecord.bankInfo.statusUrl,
- );
- if (bwi) {
- const otherReserve = await tx.reserves.get(bwi.reservePub);
- if (otherReserve) {
- logger.trace(
- "returning existing reserve for bankWithdrawStatusUri",
- );
- return {
- exchange: otherReserve.exchangeBaseUrl,
- reservePub: otherReserve.reservePub,
- };
- }
- }
- await tx.bankWithdrawUris.put({
- reservePub: reserveRecord.reservePub,
- talerWithdrawUri: reserveRecord.bankInfo.statusUrl,
- });
- }
- if (!isAudited && !isTrusted) {
- await tx.exchangeTrust.put({
- currency: reserveRecord.currency,
- exchangeBaseUrl: reserveRecord.exchangeBaseUrl,
- exchangeMasterPub: exchangeDetails.masterPublicKey,
- uids: [encodeCrock(getRandomBytes(32))],
- });
- }
- await tx.reserves.put(reserveRecord);
- const r: CreateReserveResponse = {
- exchange: canonExchange,
- reservePub: keypair.pub,
- };
- return r;
- });
-
- if (reserveRecord.reservePub === resp.reservePub) {
- // Only emit notification when a new reserve was created.
- ws.notify({
- type: NotificationType.ReserveCreated,
- reservePub: reserveRecord.reservePub,
- });
- }
-
- // Asynchronously process the reserve, but return
- // to the caller already.
- processReserve(ws, resp.reservePub, { forceNow: true }).catch((e) => {
- logger.error("Processing reserve (after createReserve) failed:", e);
- });
-
- return resp;
-}
-
-/**
- * Re-query the status of a reserve.
- */
-export async function forceQueryReserve(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<void> {
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const reserve = await tx.reserves.get(reservePub);
- if (!reserve) {
- return;
- }
- // Only force status query where it makes sense
- switch (reserve.reserveStatus) {
- case ReserveRecordStatus.Dormant:
- reserve.reserveStatus = ReserveRecordStatus.QueryingStatus;
- reserve.operationStatus = OperationStatus.Pending;
- reserve.retryInfo = RetryInfo.reset();
- break;
- default:
- break;
- }
- await tx.reserves.put(reserve);
- });
- await processReserve(ws, reservePub, { forceNow: true });
-}
-
-/**
- * First fetch information required to withdraw from the reserve,
- * then deplete the reserve, withdrawing coins until it is empty.
- *
- * The returned promise resolves once the reserve is set to the
- * state "Dormant".
- */
-export async function processReserve(
- ws: InternalWalletState,
- reservePub: string,
- options: {
- forceNow?: boolean;
- } = {},
-): Promise<void> {
- return ws.memoProcessReserve.memo(reservePub, async () => {
- const onOpError = (err: TalerErrorDetail): Promise<void> =>
- reportReserveError(ws, reservePub, err);
- await guardOperationException(
- () => processReserveImpl(ws, reservePub, options),
- onOpError,
- );
- });
-}
-
-async function registerReserveWithBank(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<void> {
- const reserve = await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadOnly(async (tx) => {
- return await tx.reserves.get(reservePub);
- });
- switch (reserve?.reserveStatus) {
- case ReserveRecordStatus.WaitConfirmBank:
- case ReserveRecordStatus.RegisteringBank:
- break;
- default:
- return;
- }
- const bankInfo = reserve.bankInfo;
- if (!bankInfo) {
- return;
- }
- const bankStatusUrl = bankInfo.statusUrl;
- const httpResp = await ws.http.postJson(
- bankStatusUrl,
- {
- reserve_pub: reservePub,
- selected_exchange: bankInfo.exchangePaytoUri,
- },
- {
- timeout: getReserveRequestTimeout(reserve),
- },
- );
- await readSuccessResponseJsonOrThrow(
- httpResp,
- codecForBankWithdrawalOperationPostResponse(),
- );
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- return;
- }
- switch (r.reserveStatus) {
- case ReserveRecordStatus.RegisteringBank:
- case ReserveRecordStatus.WaitConfirmBank:
- break;
- default:
- return;
- }
- r.timestampReserveInfoPosted = AbsoluteTime.toTimestamp(
- AbsoluteTime.now(),
- );
- r.reserveStatus = ReserveRecordStatus.WaitConfirmBank;
- r.operationStatus = OperationStatus.Pending;
- if (!r.bankInfo) {
- throw Error("invariant failed");
- }
- r.retryInfo = RetryInfo.reset();
- await tx.reserves.put(r);
- });
- ws.notify({ type: NotificationType.ReserveRegisteredWithBank });
- return processReserveBankStatus(ws, reservePub);
-}
-
-export function getReserveRequestTimeout(r: ReserveRecord): Duration {
- return durationMax(
- { d_ms: 60000 },
- durationMin({ d_ms: 5000 }, RetryInfo.getDuration(r.retryInfo)),
- );
-}
-
-async function processReserveBankStatus(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<void> {
- const reserve = await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadOnly(async (tx) => {
- return tx.reserves.get(reservePub);
- });
- switch (reserve?.reserveStatus) {
- case ReserveRecordStatus.WaitConfirmBank:
- case ReserveRecordStatus.RegisteringBank:
- break;
- default:
- return;
- }
- const bankStatusUrl = reserve.bankInfo?.statusUrl;
- if (!bankStatusUrl) {
- return;
- }
-
- const statusResp = await ws.http.get(bankStatusUrl, {
- timeout: getReserveRequestTimeout(reserve),
- });
- const status = await readSuccessResponseJsonOrThrow(
- statusResp,
- codecForWithdrawOperationStatusResponse(),
- );
-
- if (status.aborted) {
- logger.info("bank aborted the withdrawal");
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- return;
- }
- switch (r.reserveStatus) {
- case ReserveRecordStatus.RegisteringBank:
- case ReserveRecordStatus.WaitConfirmBank:
- break;
- default:
- return;
- }
- const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
- r.timestampBankConfirmed = now;
- r.reserveStatus = ReserveRecordStatus.BankAborted;
- r.operationStatus = OperationStatus.Finished;
- r.retryInfo = RetryInfo.reset();
- await tx.reserves.put(r);
- });
- return;
- }
-
- // Bank still needs to know our reserve info
- if (!status.selection_done) {
- await registerReserveWithBank(ws, reservePub);
- return await processReserveBankStatus(ws, reservePub);
- }
-
- // FIXME: Why do we do this?!
- if (reserve.reserveStatus === ReserveRecordStatus.RegisteringBank) {
- await registerReserveWithBank(ws, reservePub);
- return await processReserveBankStatus(ws, reservePub);
- }
-
- await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadWrite(async (tx) => {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- return;
- }
- // Re-check reserve status within transaction
- switch (r.reserveStatus) {
- case ReserveRecordStatus.RegisteringBank:
- case ReserveRecordStatus.WaitConfirmBank:
- break;
- default:
- return;
- }
- if (status.transfer_done) {
- const now = AbsoluteTime.toTimestamp(AbsoluteTime.now());
- r.timestampBankConfirmed = now;
- r.reserveStatus = ReserveRecordStatus.QueryingStatus;
- r.operationStatus = OperationStatus.Pending;
- r.retryInfo = RetryInfo.reset();
- } else {
- logger.info("Withdrawal operation not yet confirmed by bank");
- if (r.bankInfo) {
- r.bankInfo.confirmUrl = status.confirm_transfer_url;
- }
- r.retryInfo = RetryInfo.increment(r.retryInfo);
- }
- await tx.reserves.put(r);
- });
-}
-
-/**
- * Update the information about a reserve that is stored in the wallet
- * by querying the reserve's exchange.
- *
- * If the reserve have funds that are not allocated in a withdrawal group yet
- * and are big enough to withdraw with available denominations,
- * create a new withdrawal group for the remaining amount.
- */
-async function updateReserve(
- ws: InternalWalletState,
- reservePub: string,
-): Promise<{ ready: boolean }> {
- const reserve = await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadOnly(async (tx) => {
- return tx.reserves.get(reservePub);
- });
- if (!reserve) {
- throw Error("reserve not in db");
- }
-
- if (reserve.reserveStatus !== ReserveRecordStatus.QueryingStatus) {
- return { ready: true };
- }
-
- const reserveUrl = new URL(`reserves/${reservePub}`, reserve.exchangeBaseUrl);
- reserveUrl.searchParams.set("timeout_ms", "30000");
-
- logger.info(`querying reserve status via ${reserveUrl}`);
-
- const resp = await ws.http.get(reserveUrl.href, {
- timeout: getReserveRequestTimeout(reserve),
- });
-
- const result = await readSuccessResponseJsonOrErrorCode(
- resp,
- codecForReserveStatus(),
- );
-
- if (result.isError) {
- if (
- resp.status === 404 &&
- result.talerErrorResponse.code ===
- TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
- ) {
- ws.notify({
- type: NotificationType.ReserveNotYetFound,
- reservePub,
- });
- return { ready: false };
- } else {
- throwUnexpectedRequestError(resp, result.talerErrorResponse);
- }
- }
-
- logger.trace(`got reserve status ${j2s(result.response)}`);
-
- const reserveInfo = result.response;
- const reserveBalance = Amounts.parseOrThrow(reserveInfo.balance);
- const currency = reserveBalance.currency;
-
- await updateWithdrawalDenoms(ws, reserve.exchangeBaseUrl);
- const denoms = await getCandidateWithdrawalDenoms(
- ws,
- reserve.exchangeBaseUrl,
- );
-
- const newWithdrawalGroup = await ws.db
- .mktx((x) => ({
- planchets: x.planchets,
- withdrawalGroups: x.withdrawalGroups,
- reserves: x.reserves,
- denominations: x.denominations,
- }))
- .runReadWrite(async (tx) => {
- const newReserve = await tx.reserves.get(reserve.reservePub);
- if (!newReserve) {
- return;
- }
-
- let amountReservePlus = reserveBalance;
- let amountReserveMinus = Amounts.getZero(currency);
-
- // Subtract amount allocated in unfinished withdrawal groups
- // for this reserve from the available amount.
- await tx.withdrawalGroups.indexes.byReservePub
- .iter(reservePub)
- .forEachAsync(async (wg) => {
- if (wg.timestampFinish) {
- return;
- }
- await tx.planchets.indexes.byGroup
- .iter(wg.withdrawalGroupId)
- .forEachAsync(async (pr) => {
- if (pr.withdrawalDone) {
- return;
- }
- const denomInfo = await ws.getDenomInfo(
- ws,
- tx,
- wg.exchangeBaseUrl,
- pr.denomPubHash,
- );
- if (!denomInfo) {
- logger.error(`no denom info found for ${pr.denomPubHash}`);
- return;
- }
- amountReserveMinus = Amounts.add(
- amountReserveMinus,
- denomInfo.value,
- denomInfo.feeWithdraw,
- ).amount;
- });
- });
-
- const remainingAmount = Amounts.sub(
- amountReservePlus,
- amountReserveMinus,
- ).amount;
-
- let withdrawalGroupId: string;
- let denomSel: DenomSelectionState;
-
- if (!newReserve.initialWithdrawalStarted) {
- withdrawalGroupId = newReserve.initialWithdrawalGroupId;
- newReserve.initialWithdrawalStarted = true;
- denomSel = newReserve.initialDenomSel;
- } else {
- withdrawalGroupId = encodeCrock(randomBytes(32));
-
- denomSel = selectWithdrawalDenominations(remainingAmount, denoms);
-
- logger.trace(
- `Remaining unclaimed amount in reseve is ${Amounts.stringify(
- remainingAmount,
- )} and can be withdrawn with ${denomSel.selectedDenoms.length} coins`,
- );
-
- if (denomSel.selectedDenoms.length === 0) {
- newReserve.reserveStatus = ReserveRecordStatus.Dormant;
- newReserve.operationStatus = OperationStatus.Finished;
- delete newReserve.lastError;
- delete newReserve.retryInfo;
- await tx.reserves.put(newReserve);
- return;
- }
- }
-
- const withdrawalRecord: WithdrawalGroupRecord = {
- withdrawalGroupId: withdrawalGroupId,
- exchangeBaseUrl: reserve.exchangeBaseUrl,
- reservePub: reserve.reservePub,
- rawWithdrawalAmount: remainingAmount,
- timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
- retryInfo: RetryInfo.reset(),
- lastError: undefined,
- denomsSel: denomSel,
- secretSeed: encodeCrock(getRandomBytes(64)),
- denomSelUid: encodeCrock(getRandomBytes(32)),
- operationStatus: OperationStatus.Pending,
- };
-
- delete newReserve.lastError;
- delete newReserve.retryInfo;
- newReserve.reserveStatus = ReserveRecordStatus.Dormant;
- newReserve.operationStatus = OperationStatus.Finished;
-
- await tx.reserves.put(newReserve);
- await tx.withdrawalGroups.put(withdrawalRecord);
- return withdrawalRecord;
- });
-
- if (newWithdrawalGroup) {
- logger.trace("processing new withdraw group");
- ws.notify({
- type: NotificationType.WithdrawGroupCreated,
- withdrawalGroupId: newWithdrawalGroup.withdrawalGroupId,
- });
- await processWithdrawGroup(ws, newWithdrawalGroup.withdrawalGroupId);
- }
-
- return { ready: true };
-}
-
-async function processReserveImpl(
- ws: InternalWalletState,
- reservePub: string,
- options: {
- forceNow?: boolean;
- } = {},
-): Promise<void> {
- const forceNow = options.forceNow ?? false;
- await setupReserveRetry(ws, reservePub, { reset: forceNow });
- const reserve = await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadOnly(async (tx) => {
- return tx.reserves.get(reservePub);
- });
- if (!reserve) {
- logger.error(
- `not processing reserve: reserve ${reservePub} does not exist`,
- );
- return;
- }
- logger.trace(
- `Processing reserve ${reservePub} with status ${reserve.reserveStatus}`,
- );
- switch (reserve.reserveStatus) {
- case ReserveRecordStatus.RegisteringBank:
- await processReserveBankStatus(ws, reservePub);
- return await processReserveImpl(ws, reservePub, { forceNow: true });
- case ReserveRecordStatus.QueryingStatus: {
- const res = await updateReserve(ws, reservePub);
- if (res.ready) {
- return await processReserveImpl(ws, reservePub, { forceNow: true });
- }
- break;
- }
- case ReserveRecordStatus.Dormant:
- // nothing to do
- break;
- case ReserveRecordStatus.WaitConfirmBank:
- await processReserveBankStatus(ws, reservePub);
- break;
- case ReserveRecordStatus.BankAborted:
- break;
- default:
- console.warn("unknown reserve record status:", reserve.reserveStatus);
- assertUnreachable(reserve.reserveStatus);
- break;
- }
-}
-
-/**
- * Create a reserve for a bank-integrated withdrawal from
- * a taler://withdraw URI.
- */
-export async function createTalerWithdrawReserve(
- ws: InternalWalletState,
- talerWithdrawUri: string,
- selectedExchange: string,
- options: {
- forcedDenomSel?: ForcedDenomSel;
- restrictAge?: number;
- } = {},
-): Promise<AcceptWithdrawalResponse> {
- await updateExchangeFromUrl(ws, selectedExchange);
- const withdrawInfo = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
- const exchangePaytoUri = await getExchangePaytoUri(
- ws,
- selectedExchange,
- withdrawInfo.wireTypes,
- );
- const reserve = await createReserve(ws, {
- amount: withdrawInfo.amount,
- bankWithdrawStatusUrl: withdrawInfo.extractedStatusUrl,
- exchange: selectedExchange,
- senderWire: withdrawInfo.senderWire,
- exchangePaytoUri: exchangePaytoUri,
- restrictAge: options.restrictAge,
- forcedDenomSel: options.forcedDenomSel,
- });
- // We do this here, as the reserve should be registered before we return,
- // so that we can redirect the user to the bank's status page.
- await processReserveBankStatus(ws, reserve.reservePub);
- const processedReserve = await ws.db
- .mktx((x) => ({
- reserves: x.reserves,
- }))
- .runReadOnly(async (tx) => {
- return tx.reserves.get(reserve.reservePub);
- });
- if (processedReserve?.reserveStatus === ReserveRecordStatus.BankAborted) {
- throw TalerError.fromDetail(
- TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
- {},
- );
- }
- return {
- reservePub: reserve.reservePub,
- confirmTransferUrl: withdrawInfo.confirmTransferUrl,
- };
-}
-
-/**
- * Get payto URIs that can be used to fund a reserve.
- */
-export async function getFundingPaytoUris(
- tx: GetReadOnlyAccess<{
- reserves: typeof WalletStoresV1.reserves;
- exchanges: typeof WalletStoresV1.exchanges;
- exchangeDetails: typeof WalletStoresV1.exchangeDetails;
- }>,
- reservePub: string,
-): Promise<string[]> {
- const r = await tx.reserves.get(reservePub);
- if (!r) {
- logger.error(`reserve ${reservePub} not found (DB corrupted?)`);
- return [];
- }
- const exchangeDetails = await getExchangeDetails(tx, r.exchangeBaseUrl);
- if (!exchangeDetails) {
- logger.error(`exchange ${r.exchangeBaseUrl} not found (DB corrupted?)`);
- return [];
- }
- const plainPaytoUris =
- exchangeDetails.wireInfo?.accounts.map((x) => x.payto_uri) ?? [];
- if (!plainPaytoUris) {
- logger.error(`exchange ${r.exchangeBaseUrl} has no wire info`);
- return [];
- }
- return plainPaytoUris.map((x) =>
- addPaytoQueryParams(x, {
- amount: Amounts.stringify(r.instructedAmount),
- message: `Taler Withdrawal ${r.reservePub}`,
- }),
- );
-}