diff options
Diffstat (limited to 'src/wallet.ts')
-rw-r--r-- | src/wallet.ts | 881 |
1 files changed, 0 insertions, 881 deletions
diff --git a/src/wallet.ts b/src/wallet.ts deleted file mode 100644 index 4d22bd591..000000000 --- a/src/wallet.ts +++ /dev/null @@ -1,881 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2015-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/> - */ - -/** - * High-level wallet operations that should be indepentent from the underlying - * browser extension interface. - */ - -/** - * Imports. - */ -import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi"; -import { HttpRequestLibrary } from "./util/http"; -import { Database } from "./util/query"; - -import { Amounts, AmountJson } from "./util/amounts"; - -import { - getExchangeWithdrawalInfo, - getWithdrawalDetailsForUri, -} from "./operations/withdraw"; - -import { - preparePayForUri, - refuseProposal, - confirmPay, - processDownloadProposal, - processPurchasePay, -} from "./operations/pay"; - -import { - CoinRecord, - CurrencyRecord, - DenominationRecord, - ExchangeRecord, - PurchaseRecord, - ReserveRecord, - Stores, - ReserveRecordStatus, - CoinSourceType, - RefundState, -} from "./types/dbTypes"; -import { CoinDumpJson, WithdrawUriInfoResponse } from "./types/talerTypes"; -import { - BenchmarkResult, - ConfirmPayResult, - ReturnCoinsRequest, - SenderWireInfos, - TipStatus, - PreparePayResult, - AcceptWithdrawalResponse, - PurchaseDetails, - RefreshReason, - ExchangeListItem, - ExchangesListRespose, - ManualWithdrawalDetails, - GetExchangeTosResult, - AcceptManualWithdrawalResult, - BalancesResponse, -} from "./types/walletTypes"; -import { Logger } from "./util/logging"; - -import { assertUnreachable } from "./util/assertUnreachable"; - -import { - updateExchangeFromUrl, - getExchangeTrust, - getExchangePaytoUri, - acceptExchangeTermsOfService, -} from "./operations/exchanges"; -import { - processReserve, - createTalerWithdrawReserve, - forceQueryReserve, - getFundingPaytoUris, -} from "./operations/reserves"; - -import { InternalWalletState } from "./operations/state"; -import { createReserve } from "./operations/reserves"; -import { processRefreshGroup, createRefreshGroup } from "./operations/refresh"; -import { processWithdrawGroup } from "./operations/withdraw"; -import { getPendingOperations } from "./operations/pending"; -import { getBalances } from "./operations/balance"; -import { acceptTip, getTipStatus, processTip } from "./operations/tip"; -import { TimerGroup } from "./util/timer"; -import { AsyncCondition } from "./util/promiseUtils"; -import { AsyncOpMemoSingle } from "./util/asyncMemo"; -import { - PendingOperationInfo, - PendingOperationsResponse, - PendingOperationType, -} from "./types/pending"; -import { WalletNotification, NotificationType } from "./types/notifications"; -import { processPurchaseQueryRefund, applyRefund } from "./operations/refund"; -import { durationMin, Duration } from "./util/time"; -import { processRecoupGroup } from "./operations/recoup"; -import { OperationFailedAndReportedError } from "./operations/errors"; -import { - TransactionsRequest, - TransactionsResponse, -} from "./types/transactions"; -import { getTransactions } from "./operations/transactions"; -import { withdrawTestBalance } from "./operations/testing"; - -const builtinCurrencies: CurrencyRecord[] = [ - { - auditors: [ - { - auditorPub: "BW9DC48PHQY4NH011SHHX36DZZ3Q22Y6X7FZ1VD1CMZ2PTFZ6PN0", - baseUrl: "https://auditor.demo.taler.net/", - expirationStamp: new Date(2027, 1).getTime(), - }, - ], - exchanges: [], - fractionalDigits: 2, - name: "KUDOS", - }, -]; - -const logger = new Logger("wallet.ts"); - -/** - * The platform-independent wallet implementation. - */ -export class Wallet { - private ws: InternalWalletState; - private timerGroup: TimerGroup = new TimerGroup(); - private latch = new AsyncCondition(); - private stopped = false; - private memoRunRetryLoop = new AsyncOpMemoSingle<void>(); - - get db(): Database { - return this.ws.db; - } - - constructor( - db: Database, - http: HttpRequestLibrary, - cryptoWorkerFactory: CryptoWorkerFactory, - ) { - this.ws = new InternalWalletState(db, http, cryptoWorkerFactory); - } - - getExchangePaytoUri( - exchangeBaseUrl: string, - supportedTargetTypes: string[], - ): Promise<string> { - return getExchangePaytoUri(this.ws, exchangeBaseUrl, supportedTargetTypes); - } - - async getWithdrawalDetailsForAmount( - exchangeBaseUrl: string, - amount: AmountJson, - ): Promise<ManualWithdrawalDetails> { - const wi = await getExchangeWithdrawalInfo( - this.ws, - exchangeBaseUrl, - amount, - ); - const paytoUris = wi.exchangeInfo.wireInfo?.accounts.map( - (x) => x.payto_uri, - ); - if (!paytoUris) { - throw Error("exchange is in invalid state"); - } - return { - amountRaw: Amounts.stringify(amount), - amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue), - paytoUris, - tosAccepted: wi.termsOfServiceAccepted, - }; - } - - addNotificationListener(f: (n: WalletNotification) => void): void { - this.ws.addNotificationListener(f); - } - - /** - * Execute one operation based on the pending operation info record. - */ - async processOnePendingOperation( - pending: PendingOperationInfo, - forceNow = false, - ): Promise<void> { - logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`); - switch (pending.type) { - case PendingOperationType.Bug: - // Nothing to do, will just be displayed to the user - return; - case PendingOperationType.ExchangeUpdate: - await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl, forceNow); - break; - case PendingOperationType.Refresh: - await processRefreshGroup(this.ws, pending.refreshGroupId, forceNow); - break; - case PendingOperationType.Reserve: - await processReserve(this.ws, pending.reservePub, forceNow); - break; - case PendingOperationType.Withdraw: - await processWithdrawGroup( - this.ws, - pending.withdrawalGroupId, - forceNow, - ); - break; - case PendingOperationType.ProposalChoice: - // Nothing to do, user needs to accept/reject - break; - case PendingOperationType.ProposalDownload: - await processDownloadProposal(this.ws, pending.proposalId, forceNow); - break; - case PendingOperationType.TipChoice: - // Nothing to do, user needs to accept/reject - break; - case PendingOperationType.TipPickup: - await processTip(this.ws, pending.tipId, forceNow); - break; - case PendingOperationType.Pay: - await processPurchasePay(this.ws, pending.proposalId, forceNow); - break; - case PendingOperationType.RefundQuery: - await processPurchaseQueryRefund(this.ws, pending.proposalId, forceNow); - break; - case PendingOperationType.Recoup: - await processRecoupGroup(this.ws, pending.recoupGroupId, forceNow); - break; - default: - assertUnreachable(pending); - } - } - - /** - * Process pending operations. - */ - public async runPending(forceNow = false): Promise<void> { - const onlyDue = !forceNow; - const pendingOpsResponse = await this.getPendingOperations({ onlyDue }); - for (const p of pendingOpsResponse.pendingOperations) { - try { - await this.processOnePendingOperation(p, forceNow); - } catch (e) { - if (e instanceof OperationFailedAndReportedError) { - console.error( - "Operation failed:", - JSON.stringify(e.operationError, undefined, 2), - ); - } else { - console.error(e); - } - } - } - } - - /** - * 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. - */ - public async runUntilDone(): Promise<void> { - let done = false; - const p = new Promise((resolve, reject) => { - // Run this asynchronously - this.addNotificationListener((n) => { - if (done) { - return; - } - if ( - n.type === NotificationType.WaitingForRetry && - n.numGivingLiveness == 0 - ) { - done = true; - logger.trace("no liveness-giving operations left"); - resolve(); - } - }); - this.runRetryLoop().catch((e) => { - console.log("exception in wallet retry loop"); - reject(e); - }); - }); - await p; - } - - /** - * 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. - */ - public async runUntilDoneAndStop(): Promise<void> { - await this.runUntilDone(); - logger.trace("stopping after liveness-giving operations done"); - this.stop(); - } - - /** - * Process pending operations and wait for scheduled operations in - * a loop until the wallet is stopped explicitly. - */ - public async runRetryLoop(): Promise<void> { - // Make sure we only run one main loop at a time. - return this.memoRunRetryLoop.memo(async () => { - try { - await this.runRetryLoopImpl(); - } catch (e) { - console.error("error during retry loop execution", e); - throw e; - } - }); - } - - private async runRetryLoopImpl(): Promise<void> { - while (!this.stopped) { - const pending = await this.getPendingOperations({ onlyDue: true }); - if (pending.pendingOperations.length === 0) { - const allPending = await this.getPendingOperations({ onlyDue: false }); - let numPending = 0; - let numGivingLiveness = 0; - for (const p of allPending.pendingOperations) { - numPending++; - if (p.givesLifeness) { - numGivingLiveness++; - } - } - let dt: Duration; - if ( - allPending.pendingOperations.length === 0 || - allPending.nextRetryDelay.d_ms === Number.MAX_SAFE_INTEGER - ) { - // Wait for 5 seconds - dt = { d_ms: 5000 }; - } else { - dt = durationMin({ d_ms: 5000 }, allPending.nextRetryDelay); - } - const timeout = this.timerGroup.resolveAfter(dt); - this.ws.notify({ - type: NotificationType.WaitingForRetry, - numGivingLiveness, - numPending, - }); - await Promise.race([timeout, this.latch.wait()]); - console.log("timeout done"); - } else { - // FIXME: maybe be a bit smarter about executing these - // operations in parallel? - for (const p of pending.pendingOperations) { - try { - await this.processOnePendingOperation(p); - } catch (e) { - if (e instanceof OperationFailedAndReportedError) { - logger.warn("operation processed resulted in reported error"); - } else { - console.error("Uncaught exception", e); - this.ws.notify({ - type: NotificationType.InternalError, - message: "uncaught exception", - exception: e, - }); - } - } - this.ws.notify({ - type: NotificationType.PendingOperationProcessed, - }); - } - } - } - logger.trace("exiting wallet retry loop"); - } - - /** - * Insert the hard-coded defaults for exchanges, coins and - * auditors into the database, unless these defaults have - * already been applied. - */ - async fillDefaults(): Promise<void> { - await this.db.runWithWriteTransaction( - [Stores.config, Stores.currencies], - async (tx) => { - let applied = false; - await tx.iter(Stores.config).forEach((x) => { - if (x.key == "currencyDefaultsApplied" && x.value == true) { - applied = true; - } - }); - if (!applied) { - for (const c of builtinCurrencies) { - await tx.put(Stores.currencies, c); - } - } - }, - ); - } - - /** - * Check if a payment for the given taler://pay/ URI is possible. - * - * If the payment is possible, the signature are already generated but not - * yet send to the merchant. - */ - async preparePayForUri(talerPayUri: string): Promise<PreparePayResult> { - return preparePayForUri(this.ws, talerPayUri); - } - - /** - * Add a contract to the wallet and sign coins, and send them. - */ - async confirmPay( - proposalId: string, - sessionIdOverride: string | undefined, - ): Promise<ConfirmPayResult> { - try { - return await confirmPay(this.ws, proposalId, sessionIdOverride); - } finally { - this.latch.trigger(); - } - } - - /** - * First fetch information requred 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. - */ - async processReserve(reservePub: string): Promise<void> { - try { - return await processReserve(this.ws, reservePub); - } finally { - this.latch.trigger(); - } - } - - /** - * 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 acceptManualWithdrawal( - exchangeBaseUrl: string, - amount: AmountJson, - ): Promise<AcceptManualWithdrawalResult> { - try { - const resp = await createReserve(this.ws, { - amount, - exchange: exchangeBaseUrl, - }); - const exchangePaytoUris = await this.db.runWithReadTransaction( - [Stores.exchanges, Stores.reserves], - (tx) => getFundingPaytoUris(tx, resp.reservePub), - ); - return { - reservePub: resp.reservePub, - exchangePaytoUris, - }; - } finally { - this.latch.trigger(); - } - } - - /** - * Check if and how an exchange is trusted and/or audited. - */ - async getExchangeTrust( - exchangeInfo: ExchangeRecord, - ): Promise<{ isTrusted: boolean; isAudited: boolean }> { - return getExchangeTrust(this.ws, exchangeInfo); - } - - async getWithdrawalDetailsForUri(talerWithdrawUri: string): Promise<WithdrawUriInfoResponse> { - return getWithdrawalDetailsForUri(this.ws, talerWithdrawUri); - } - - /** - * Update or add exchange DB entry by fetching the /keys and /wire information. - * Optionally link the reserve entry to the new or existing - * exchange entry in then DB. - */ - async updateExchangeFromUrl( - baseUrl: string, - force = false, - ): Promise<ExchangeRecord> { - try { - return updateExchangeFromUrl(this.ws, baseUrl, force); - } finally { - this.latch.trigger(); - } - } - - async getExchangeTos(exchangeBaseUrl: string): Promise<GetExchangeTosResult> { - const exchange = await this.updateExchangeFromUrl(exchangeBaseUrl); - const tos = exchange.termsOfServiceText; - const currentEtag = exchange.termsOfServiceLastEtag; - if (!tos || !currentEtag) { - throw Error("exchange is in invalid state"); - } - return { - acceptedEtag: exchange.termsOfServiceAcceptedEtag, - currentEtag, - tos, - }; - } - - /** - * Get detailed balance information, sliced by exchange and by currency. - */ - async getBalances(): Promise<BalancesResponse> { - return this.ws.memoGetBalance.memo(() => getBalances(this.ws)); - } - - async refresh(oldCoinPub: string): Promise<void> { - try { - const refreshGroupId = await this.db.runWithWriteTransaction( - [Stores.refreshGroups], - async (tx) => { - return await createRefreshGroup( - this.ws, - tx, - [{ coinPub: oldCoinPub }], - RefreshReason.Manual, - ); - }, - ); - await processRefreshGroup(this.ws, refreshGroupId.refreshGroupId); - } catch (e) { - this.latch.trigger(); - } - } - - async findExchange( - exchangeBaseUrl: string, - ): Promise<ExchangeRecord | undefined> { - return await this.db.get(Stores.exchanges, exchangeBaseUrl); - } - - async getPendingOperations({ onlyDue = false } = {}): Promise< - PendingOperationsResponse - > { - return this.ws.memoGetPending.memo(() => - getPendingOperations(this.ws, { onlyDue }), - ); - } - - async acceptExchangeTermsOfService( - exchangeBaseUrl: string, - etag: string | undefined, - ): Promise<void> { - return acceptExchangeTermsOfService(this.ws, exchangeBaseUrl, etag); - } - - async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> { - const denoms = await this.db - .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeUrl) - .toArray(); - return denoms; - } - - /** - * Get all exchanges known to the exchange. - * - * @deprecated Use getExchanges instead - */ - async getExchangeRecords(): Promise<ExchangeRecord[]> { - return await this.db.iter(Stores.exchanges).toArray(); - } - - async getExchanges(): Promise<ExchangesListRespose> { - const exchanges: (ExchangeListItem | undefined)[] = await this.db - .iter(Stores.exchanges) - .map((x) => { - const details = x.details; - if (!details) { - return undefined; - } - if (!x.addComplete) { - return undefined; - } - if (!x.wireInfo) { - return undefined; - } - return { - exchangeBaseUrl: x.baseUrl, - currency: details.currency, - paytoUris: x.wireInfo.accounts.map((x) => x.payto_uri), - }; - }); - return { - exchanges: exchanges.filter((x) => !!x) as ExchangeListItem[], - }; - } - - async getCurrencies(): Promise<CurrencyRecord[]> { - return await this.db.iter(Stores.currencies).toArray(); - } - - async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> { - logger.trace("updating currency to", currencyRecord); - await this.db.put(Stores.currencies, currencyRecord); - } - - async getReserves(exchangeBaseUrl?: string): Promise<ReserveRecord[]> { - if (exchangeBaseUrl) { - return await this.db - .iter(Stores.reserves) - .filter((r) => r.exchangeBaseUrl === exchangeBaseUrl); - } else { - return await this.db.iter(Stores.reserves).toArray(); - } - } - - async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> { - return await this.db - .iter(Stores.coins) - .filter((c) => c.exchangeBaseUrl === exchangeBaseUrl); - } - - async getCoins(): Promise<CoinRecord[]> { - return await this.db.iter(Stores.coins).toArray(); - } - - /** - * Stop ongoing processing. - */ - stop(): void { - this.stopped = true; - this.timerGroup.stopCurrentAndFutureTimers(); - this.ws.cryptoApi.stop(); - } - - async getSenderWireInfos(): Promise<SenderWireInfos> { - const m: { [url: string]: Set<string> } = {}; - - await this.db.iter(Stores.exchanges).forEach((x) => { - const wi = x.wireInfo; - if (!wi) { - return; - } - const s = (m[x.baseUrl] = m[x.baseUrl] || new Set()); - Object.keys(wi.feesForType).map((k) => s.add(k)); - }); - - const exchangeWireTypes: { [url: string]: string[] } = {}; - Object.keys(m).map((e) => { - exchangeWireTypes[e] = Array.from(m[e]); - }); - - const senderWiresSet: Set<string> = new Set(); - await this.db.iter(Stores.senderWires).forEach((x) => { - senderWiresSet.add(x.paytoUri); - }); - - const senderWires: string[] = Array.from(senderWiresSet); - - return { - exchangeWireTypes, - senderWires, - }; - } - - /** - * Trigger paying coins back into the user's account. - */ - async returnCoins(req: ReturnCoinsRequest): Promise<void> { - throw Error("not implemented"); - } - - /** - * Accept a refund, return the contract hash for the contract - * that was involved in the refund. - */ - async applyRefund( - talerRefundUri: string, - ): Promise<{ contractTermsHash: string; proposalId: string }> { - return applyRefund(this.ws, talerRefundUri); - } - - async getPurchase( - contractTermsHash: string, - ): Promise<PurchaseRecord | undefined> { - return this.db.get(Stores.purchases, contractTermsHash); - } - - async acceptTip(talerTipUri: string): Promise<void> { - try { - return acceptTip(this.ws, talerTipUri); - } catch (e) { - this.latch.trigger(); - } - } - - async getTipStatus(talerTipUri: string): Promise<TipStatus> { - return getTipStatus(this.ws, talerTipUri); - } - - async abortFailedPayment(contractTermsHash: string): Promise<void> { - throw Error("not implemented"); - } - - /** - * Inform the wallet that the status of a reserve has changed (e.g. due to a - * confirmation from the bank.). - */ - public async handleNotifyReserve(): Promise<void> { - const reserves = await this.db.iter(Stores.reserves).toArray(); - for (const r of reserves) { - if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) { - try { - this.processReserve(r.reservePub); - } catch (e) { - console.error(e); - } - } - } - } - - /** - * Remove unreferenced / expired data from the wallet's database - * based on the current system time. - */ - async collectGarbage(): Promise<void> { - // FIXME(#5845) - // We currently do not garbage-collect the wallet database. This might change - // after the feature has been properly re-designed, and we have come up with a - // strategy to test it. - } - - async acceptWithdrawal( - talerWithdrawUri: string, - selectedExchange: string, - ): Promise<AcceptWithdrawalResponse> { - try { - return createTalerWithdrawReserve( - this.ws, - talerWithdrawUri, - selectedExchange, - ); - } finally { - this.latch.trigger(); - } - } - - async updateReserve(reservePub: string): Promise<ReserveRecord | undefined> { - await forceQueryReserve(this.ws, reservePub); - return await this.ws.db.get(Stores.reserves, reservePub); - } - - async getReserve(reservePub: string): Promise<ReserveRecord | undefined> { - return await this.ws.db.get(Stores.reserves, reservePub); - } - - async refuseProposal(proposalId: string): Promise<void> { - return refuseProposal(this.ws, proposalId); - } - - async getPurchaseDetails(proposalId: string): Promise<PurchaseDetails> { - const purchase = await this.db.get(Stores.purchases, proposalId); - if (!purchase) { - throw Error("unknown purchase"); - } - const refundsDoneAmounts = Object.values(purchase.refunds) - .filter((x) => x.type === RefundState.Applied) - .map((x) => x.refundAmount); - - const refundsPendingAmounts = Object.values(purchase.refunds) - .filter((x) => x.type === RefundState.Pending) - .map((x) => x.refundAmount); - const totalRefundAmount = Amounts.sum([ - ...refundsDoneAmounts, - ...refundsPendingAmounts, - ]).amount; - const refundsDoneFees = Object.values(purchase.refunds) - .filter((x) => x.type === RefundState.Applied) - .map((x) => x.refundFee); - const refundsPendingFees = Object.values(purchase.refunds) - .filter((x) => x.type === RefundState.Pending) - .map((x) => x.refundFee); - const totalRefundFees = Amounts.sum([ - ...refundsDoneFees, - ...refundsPendingFees, - ]).amount; - const totalFees = totalRefundFees; - return { - contractTerms: JSON.parse(purchase.contractTermsRaw), - hasRefund: purchase.timestampLastRefundStatus !== undefined, - totalRefundAmount: totalRefundAmount, - totalRefundAndRefreshFees: totalFees, - }; - } - - benchmarkCrypto(repetitions: number): Promise<BenchmarkResult> { - return this.ws.cryptoApi.benchmark(repetitions); - } - - async setCoinSuspended(coinPub: string, suspended: boolean): Promise<void> { - await this.db.runWithWriteTransaction([Stores.coins], async (tx) => { - const c = await tx.get(Stores.coins, coinPub); - if (!c) { - logger.warn(`coin ${coinPub} not found, won't suspend`); - return; - } - c.suspended = suspended; - await tx.put(Stores.coins, c); - }); - } - - /** - * Dump the public information of coins we have in an easy-to-process format. - */ - async dumpCoins(): Promise<CoinDumpJson> { - const coins = await this.db.iter(Stores.coins).toArray(); - const coinsJson: CoinDumpJson = { coins: [] }; - for (const c of coins) { - const denom = await this.db.get(Stores.denominations, [ - c.exchangeBaseUrl, - c.denomPub, - ]); - 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 this.db.get( - Stores.withdrawalGroups, - cs.withdrawalGroupId, - ); - if (!ws) { - console.error("no withdrawal session found for coin"); - continue; - } - if (ws.source.type == "reserve") { - withdrawalReservePub = ws.source.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; - } - - async getTransactions( - request: TransactionsRequest, - ): Promise<TransactionsResponse> { - return getTransactions(this.ws, request); - } - - - async withdrawTestBalance( - amount = "TESTKUDOS:10", - bankBaseUrl = "https://bank.test.taler.net/", - exchangeBaseUrl = "https://exchange.test.taler.net/", - ): Promise<void> { - await withdrawTestBalance(this.ws, amount, bankBaseUrl, exchangeBaseUrl); - } -} |