From b063382d25d1ed8572ebe2f52bf54247379300d5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 8 Sep 2020 17:40:47 +0530 Subject: tipping API and integration test --- packages/taler-integrationtests/src/harness.ts | 67 ++++++++++++- .../taler-integrationtests/src/merchantApiTypes.ts | 60 ++++++++++++ .../taler-integrationtests/src/test-tipping.ts | 106 +++++++++++++++++++++ packages/taler-wallet-cli/src/index.ts | 4 +- packages/taler-wallet-core/src/db.ts | 2 +- .../taler-wallet-core/src/operations/pending.ts | 2 +- packages/taler-wallet-core/src/operations/tip.ts | 81 +++++++--------- packages/taler-wallet-core/src/types/dbTypes.ts | 4 +- .../taler-wallet-core/src/types/notifications.ts | 1 + packages/taler-wallet-core/src/types/talerTypes.ts | 17 +--- .../taler-wallet-core/src/types/walletTypes.ts | 56 ++++++++--- packages/taler-wallet-core/src/wallet.ts | 19 +++- packages/taler-wallet-webextension/src/wxApi.ts | 4 - 13 files changed, 331 insertions(+), 92 deletions(-) create mode 100644 packages/taler-integrationtests/src/test-tipping.ts diff --git a/packages/taler-integrationtests/src/harness.ts b/packages/taler-integrationtests/src/harness.ts index 620b89e76..e40798b4b 100644 --- a/packages/taler-integrationtests/src/harness.ts +++ b/packages/taler-integrationtests/src/harness.ts @@ -72,6 +72,10 @@ import { CoinDumpJson, ForceExchangeUpdateRequest, ForceRefreshRequest, + PrepareTipResult, + PrepareTipRequest, + codecForPrepareTipResult, + AcceptTipRequest, } from "taler-wallet-core"; import { URL } from "url"; import axios, { AxiosError } from "axios"; @@ -81,6 +85,9 @@ import { PostOrderRequest, PostOrderResponse, MerchantOrderPrivateStatusResponse, + TippingReserveStatus, + TipCreateConfirmation, + TipCreateRequest, } from "./merchantApiTypes"; import { ApplyRefundResponse } from "taler-wallet-core"; import { PendingOperationsResponse } from "taler-wallet-core"; @@ -1216,10 +1223,46 @@ export namespace MerchantPrivateApi { }; } - export async function createTippingReserve(merchantService: MerchantServiceInterface, - + export async function createTippingReserve( + merchantService: MerchantServiceInterface, + instance: string, req: CreateMerchantTippingReserveRequest, - ): Promise {} + ): Promise { + const reqUrl = new URL( + `private/reserves`, + merchantService.makeInstanceBaseUrl(instance), + ); + const resp = await axios.post(reqUrl.href, req); + // FIXME: validate + return resp.data; + } + + export async function queryTippingReserves( + merchantService: MerchantServiceInterface, + instance: string, + ): Promise { + const reqUrl = new URL( + `private/reserves`, + merchantService.makeInstanceBaseUrl(instance), + ); + const resp = await axios.get(reqUrl.href); + // FIXME: validate + return resp.data; + } + + export async function giveTip( + merchantService: MerchantServiceInterface, + instance: string, + req: TipCreateRequest, + ): Promise { + const reqUrl = new URL( + `private/tips`, + merchantService.makeInstanceBaseUrl(instance), + ); + const resp = await axios.post(reqUrl.href, req); + // FIXME: validate + return resp.data; + } } export interface CreateMerchantTippingReserveRequest { @@ -1238,7 +1281,7 @@ export interface CreateMerchantTippingReserveConfirmation { reserve_pub: string; // Wire account of the exchange where to transfer the funds - payto_url: string; + payto_uri: string; } export class MerchantService implements MerchantServiceInterface { @@ -1594,6 +1637,22 @@ export class WalletCli { throw new OperationFailedError(resp.error); } + async prepareTip(req: PrepareTipRequest): Promise { + const resp = await this.apiRequest("prepareTip", req); + if (resp.type === "response") { + return codecForPrepareTipResult().decode(resp.result); + } + throw new OperationFailedError(resp.error); + } + + async acceptTip(req: AcceptTipRequest): Promise { + const resp = await this.apiRequest("acceptTip", req); + if (resp.type === "response") { + return; + } + throw new OperationFailedError(resp.error); + } + async dumpCoins(): Promise { const resp = await this.apiRequest("dumpCoins", {}); if (resp.type === "response") { diff --git a/packages/taler-integrationtests/src/merchantApiTypes.ts b/packages/taler-integrationtests/src/merchantApiTypes.ts index 550c5e90c..e89e32642 100644 --- a/packages/taler-integrationtests/src/merchantApiTypes.ts +++ b/packages/taler-integrationtests/src/merchantApiTypes.ts @@ -223,3 +223,63 @@ export interface TransactionWireReport { // Public key of the coin for which we got the exchange error. coin_pub: CoinPublicKeyString; } + +export interface TippingReserveStatus { + // Array of all known reserves (possibly empty!) + reserves: ReserveStatusEntry[]; +} + +export interface ReserveStatusEntry { + // Public key of the reserve + reserve_pub: string; + + // Timestamp when it was established + creation_time: Timestamp; + + // Timestamp when it expires + expiration_time: Timestamp; + + // Initial amount as per reserve creation call + merchant_initial_amount: AmountString; + + // Initial amount as per exchange, 0 if exchange did + // not confirm reserve creation yet. + exchange_initial_amount: AmountString; + + // Amount picked up so far. + pickup_amount: AmountString; + + // Amount approved for tips that exceeds the pickup_amount. + committed_amount: AmountString; + + // Is this reserve active (false if it was deleted but not purged) + active: boolean; +} + + +export interface TipCreateConfirmation { + // Unique tip identifier for the tip that was created. + tip_id: string; + + // taler://tip URI for the tip + taler_tip_uri: string; + + // URL that will directly trigger processing + // the tip when the browser is redirected to it + tip_status_url: string; + + // when does the tip expire + tip_expiration: Timestamp; +} + +export interface TipCreateRequest { + // Amount that the customer should be tipped + amount: AmountString; + + // Justification for giving the tip + justification: string; + + // URL that the user should be directed to after tipping, + // will be included in the tip_token. + next_url: string; +} \ No newline at end of file diff --git a/packages/taler-integrationtests/src/test-tipping.ts b/packages/taler-integrationtests/src/test-tipping.ts new file mode 100644 index 000000000..ddf56c0e0 --- /dev/null +++ b/packages/taler-integrationtests/src/test-tipping.ts @@ -0,0 +1,106 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * Imports. + */ +import { + runTest, + GlobalTestState, + MerchantPrivateApi, + BankAccessApi, + BankApi, +} from "./harness"; +import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers"; + +/** + * Run test for basic, bank-integrated withdrawal. + */ +runTest(async (t: GlobalTestState) => { + // Set up test environment + + const { + wallet, + bank, + exchange, + merchant, + exchangeBankAccount, + } = await createSimpleTestkudosEnvironment(t); + + const mbu = await BankApi.createRandomBankUser(bank); + + const tipReserveResp = await MerchantPrivateApi.createTippingReserve( + merchant, + "default", + { + exchange_url: exchange.baseUrl, + initial_balance: "TESTKUDOS:10", + wire_method: "x-taler-bank", + }, + ); + + console.log("tipReserveResp:", tipReserveResp); + + t.assertDeepEqual( + tipReserveResp.payto_uri, + exchangeBankAccount.accountPaytoUri, + ); + + await BankApi.adminAddIncoming(bank, { + amount: "TESTKUDOS:10", + debitAccountPayto: mbu.accountPaytoUri, + exchangeBankAccount, + reservePub: tipReserveResp.reserve_pub, + }); + + await exchange.runWirewatchOnce(); + await merchant.stop(); + await merchant.start(); + await merchant.pingUntilAvailable(); + + const r = await MerchantPrivateApi.queryTippingReserves(merchant, "default"); + console.log("tipping reserves:", JSON.stringify(r, undefined, 2)); + + t.assertTrue(r.reserves.length === 1); + t.assertDeepEqual( + r.reserves[0].exchange_initial_amount, + r.reserves[0].merchant_initial_amount, + ); + + const tip = await MerchantPrivateApi.giveTip(merchant, "default", { + amount: "TESTKUDOS:5", + justification: "why not?", + next_url: "https://example.com/after-tip", + }); + + console.log("created tip", tip); + + const ptr = await wallet.prepareTip({ + talerTipUri: tip.taler_tip_uri, + }); + + console.log(ptr); + + await wallet.acceptTip({ + walletTipId: ptr.walletTipId, + }); + + await wallet.runUntilDone(); + + const bal = await wallet.getBalances(); + + console.log(bal); +}); diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index a19b8a8fc..5a662d807 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -288,9 +288,9 @@ walletCli break; case TalerUriType.TalerTip: { - const res = await wallet.getTipStatus(uri); + const res = await wallet.prepareTip(uri); console.log("tip status", res); - await wallet.acceptTip(res.tipId); + await wallet.acceptTip(res.walletTipId); } break; case TalerUriType.TalerRefund: diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index b3203935e..c21ff4a43 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -8,7 +8,7 @@ import { IDBFactory, IDBDatabase } from "idb-bridge"; * with each major change. When incrementing the major version, * the wallet should import data from the previous version. */ -const TALER_DB_NAME = "taler-walletdb-v10"; +const TALER_DB_NAME = "taler-walletdb-v11"; /** * Current database minor version, should be incremented diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 3c631eb77..91a55c705 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -368,7 +368,7 @@ async function gatherTipPending( type: PendingOperationType.TipPickup, givesLifeness: true, merchantBaseUrl: tip.merchantBaseUrl, - tipId: tip.tipId, + tipId: tip.walletTipId, merchantTipId: tip.merchantTipId, }); } diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index 7949648c5..6fe374bf0 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -16,7 +16,7 @@ import { InternalWalletState } from "./state"; import { parseTipUri } from "../util/taleruri"; -import { TipStatus, TalerErrorDetails } from "../types/walletTypes"; +import { PrepareTipResult, TalerErrorDetails } from "../types/walletTypes"; import { TipPlanchetDetail, codecForTipPickupGetResponse, @@ -46,20 +46,23 @@ import { getTimestampNow } from "../util/time"; import { readSuccessResponseJsonOrThrow } from "../util/http"; import { URL } from "../util/url"; import { Logger } from "../util/logging"; +import { checkDbInvariant } from "../util/invariants"; const logger = new Logger("operations/tip.ts"); -export async function getTipStatus( +export async function prepareTip( ws: InternalWalletState, talerTipUri: string, -): Promise { +): Promise { const res = parseTipUri(talerTipUri); if (!res) { throw Error("invalid taler://tip URI"); } - const tipStatusUrl = new URL("tip-pickup", res.merchantBaseUrl); - tipStatusUrl.searchParams.set("tip_id", res.merchantTipId); + const tipStatusUrl = new URL( + `tips/${res.merchantTipId}`, + res.merchantBaseUrl, + ); logger.trace("checking tip status from", tipStatusUrl.href); const merchantResp = await ws.http.get(tipStatusUrl.href); const tipPickupStatus = await readSuccessResponseJsonOrThrow( @@ -68,7 +71,7 @@ export async function getTipStatus( ); logger.trace(`status ${tipPickupStatus}`); - const amount = Amounts.parseOrThrow(tipPickupStatus.amount); + const amount = Amounts.parseOrThrow(tipPickupStatus.tip_amount); const merchantOrigin = new URL(res.merchantBaseUrl).origin; @@ -85,7 +88,7 @@ export async function getTipStatus( amount, ); - const tipId = encodeCrock(getRandomBytes(32)); + const walletTipId = encodeCrock(getRandomBytes(32)); const selectedDenoms = await selectWithdrawalDenoms( ws, tipPickupStatus.exchange_url, @@ -93,11 +96,11 @@ export async function getTipStatus( ); tipRecord = { - tipId, + walletTipId: walletTipId, acceptedTimestamp: undefined, rejectedTimestamp: undefined, amount, - deadline: tipPickupStatus.stamp_expire, + deadline: tipPickupStatus.expiration, exchangeUrl: tipPickupStatus.exchange_url, merchantBaseUrl: res.merchantBaseUrl, nextUrl: undefined, @@ -117,18 +120,13 @@ export async function getTipStatus( await ws.db.put(Stores.tips, tipRecord); } - const tipStatus: TipStatus = { + const tipStatus: PrepareTipResult = { accepted: !!tipRecord && !!tipRecord.acceptedTimestamp, - amount: Amounts.parseOrThrow(tipPickupStatus.amount), - amountLeft: Amounts.parseOrThrow(tipPickupStatus.amount_left), - exchangeUrl: tipPickupStatus.exchange_url, - nextUrl: tipPickupStatus.extra.next_url, - merchantOrigin: merchantOrigin, - merchantTipId: res.merchantTipId, - expirationTimestamp: tipPickupStatus.stamp_expire, - timestamp: tipPickupStatus.stamp_created, - totalFees: tipRecord.totalFees, - tipId: tipRecord.tipId, + amount: Amounts.stringify(tipPickupStatus.tip_amount), + exchangeBaseUrl: tipPickupStatus.exchange_url, + expirationTimestamp: tipPickupStatus.expiration, + totalFees: Amounts.stringify(tipRecord.totalFees), + walletTipId: tipRecord.walletTipId, }; return tipStatus; @@ -152,7 +150,9 @@ async function incrementTipRetry( t.lastError = err; await tx.put(Stores.tips, t); }); - ws.notify({ type: NotificationType.TipOperationError }); + if (err) { + ws.notify({ type: NotificationType.TipOperationError, error: err }); + } } export async function processTip( @@ -225,15 +225,8 @@ async function processTipImpl( } tipRecord = await ws.db.get(Stores.tips, tipId); - if (!tipRecord) { - throw Error("tip not in database"); - } - - if (!tipRecord.planchets) { - throw Error("invariant violated"); - } - - logger.trace("got planchets for tip!"); + checkDbInvariant(!!tipRecord, "tip record should be in database"); + checkDbInvariant(!!tipRecord.planchets, "tip record should have planchets"); // Planchets in the form that the merchant expects const planchetsDetail: TipPlanchetDetail[] = tipRecord.planchets.map((p) => ({ @@ -241,23 +234,17 @@ async function processTipImpl( denom_pub_hash: p.denomPubHash, })); - let merchantResp; - - const tipStatusUrl = new URL("tip-pickup", tipRecord.merchantBaseUrl); - - try { - const req = { planchets: planchetsDetail, tip_id: tipRecord.merchantTipId }; - merchantResp = await ws.http.postJson(tipStatusUrl.href, req); - if (merchantResp.status !== 200) { - throw Error(`unexpected status ${merchantResp.status} for tip-pickup`); - } - logger.trace("got merchant resp:", merchantResp); - } catch (e) { - logger.warn("tipping failed", e); - throw e; - } + const tipStatusUrl = new URL( + `/tips/${tipRecord.merchantTipId}/pickup`, + tipRecord.merchantBaseUrl, + ); - const response = codecForTipResponse().decode(await merchantResp.json()); + const req = { planchets: planchetsDetail }; + const merchantResp = await ws.http.postJson(tipStatusUrl.href, req); + const response = await readSuccessResponseJsonOrThrow( + merchantResp, + codecForTipResponse(), + ); if (response.reserve_sigs.length !== tipRecord.planchets.length) { throw Error("number of tip responses does not match requested planchets"); @@ -293,7 +280,7 @@ async function processTipImpl( exchangeBaseUrl: tipRecord.exchangeUrl, source: { type: WithdrawalSourceType.Tip, - tipId: tipRecord.tipId, + tipId: tipRecord.walletTipId, }, timestampStart: getTimestampNow(), withdrawalGroupId: withdrawalGroupId, diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts b/packages/taler-wallet-core/src/types/dbTypes.ts index 0ee41a6a5..4e2ba1bb4 100644 --- a/packages/taler-wallet-core/src/types/dbTypes.ts +++ b/packages/taler-wallet-core/src/types/dbTypes.ts @@ -986,7 +986,7 @@ export interface TipRecord { /** * Tip ID chosen by the wallet. */ - tipId: string; + walletTipId: string; /** * The merchant's identifier for this tip. @@ -1760,7 +1760,7 @@ class ReserveHistoryStore extends Store { class TipsStore extends Store { constructor() { - super("tips", { keyPath: "tipId" }); + super("tips", { keyPath: "walletTipId" }); } } diff --git a/packages/taler-wallet-core/src/types/notifications.ts b/packages/taler-wallet-core/src/types/notifications.ts index 7a51f0d83..e1b9a7aff 100644 --- a/packages/taler-wallet-core/src/types/notifications.ts +++ b/packages/taler-wallet-core/src/types/notifications.ts @@ -186,6 +186,7 @@ export interface ProposalOperationErrorNotification { export interface TipOperationErrorNotification { type: NotificationType.TipOperationError; + error: TalerErrorDetails; } export interface WithdrawOperationErrorNotification { diff --git a/packages/taler-wallet-core/src/types/talerTypes.ts b/packages/taler-wallet-core/src/types/talerTypes.ts index c944f1561..52dc4cb62 100644 --- a/packages/taler-wallet-core/src/types/talerTypes.ts +++ b/packages/taler-wallet-core/src/types/talerTypes.ts @@ -773,17 +773,11 @@ export class WithdrawOperationStatusResponse { * Response from the merchant. */ export class TipPickupGetResponse { - extra: any; - - amount: string; - - amount_left: string; + tip_amount: string; exchange_url: string; - stamp_expire: Timestamp; - - stamp_created: Timestamp; + expiration: Timestamp; } export class WithdrawResponse { @@ -1261,12 +1255,9 @@ export const codecForWithdrawOperationStatusResponse = (): Codec< export const codecForTipPickupGetResponse = (): Codec => buildCodecForObject() - .property("extra", codecForAny()) - .property("amount", codecForString()) - .property("amount_left", codecForString()) + .property("tip_amount", codecForString()) .property("exchange_url", codecForString()) - .property("stamp_expire", codecForTimestamp) - .property("stamp_created", codecForTimestamp) + .property("expiration", codecForTimestamp) .build("TipPickupGetResponse"); export const codecForRecoupConfirmation = (): Codec => diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts b/packages/taler-wallet-core/src/types/walletTypes.ts index 82f29c39d..fb049caf9 100644 --- a/packages/taler-wallet-core/src/types/walletTypes.ts +++ b/packages/taler-wallet-core/src/types/walletTypes.ts @@ -38,7 +38,7 @@ import { ExchangeWireInfo, DenominationSelectionInfo, } from "./dbTypes"; -import { Timestamp } from "../util/time"; +import { Timestamp, codecForTimestamp } from "../util/time"; import { buildCodecForObject, codecForString, @@ -348,23 +348,33 @@ export class ReturnCoinsRequest { static checked: (obj: any) => ReturnCoinsRequest; } -/** - * Status of processing a tip. - */ -export interface TipStatus { +export interface PrepareTipResult { + /** + * Unique ID for the tip assigned by the wallet. + * Typically different from the merchant-generated tip ID. + */ + walletTipId: string; + + /** + * Has the tip already been accepted? + */ accepted: boolean; - amount: AmountJson; - amountLeft: AmountJson; - nextUrl: string; - exchangeUrl: string; - tipId: string; - merchantTipId: string; - merchantOrigin: string; + amount: AmountString; + totalFees: AmountString; + exchangeBaseUrl: string; expirationTimestamp: Timestamp; - timestamp: Timestamp; - totalFees: AmountJson; } +export const codecForPrepareTipResult = (): Codec => + buildCodecForObject() + .property("accepted", codecForBoolean()) + .property("amount", codecForAmountString()) + .property("totalFees", codecForAmountString()) + .property("exchangeBaseUrl", codecForString()) + .property("expirationTimestamp", codecForTimestamp) + .property("walletTipId", codecForString()) + .build("PrepareTipResult"); + export interface BenchmarkResult { time: { [s: string]: number }; repetitions: number; @@ -903,3 +913,21 @@ export const codecForForceRefreshRequest = (): Codec => buildCodecForObject() .property("coinPubList", codecForList(codecForString())) .build("ForceRefreshRequest"); + +export interface PrepareTipRequest { + talerTipUri: string; +} + +export const codecForPrepareTipRequest = (): Codec => + buildCodecForObject() + .property("talerTipUri", codecForString()) + .build("PrepareTipRequest"); + +export interface AcceptTipRequest { + walletTipId: string; +} + +export const codecForAcceptTipRequest = (): Codec => + buildCodecForObject() + .property("walletTipId", codecForString()) + .build("AcceptTipRequest"); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 9666665a4..0507ac8b2 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -59,7 +59,6 @@ import { ConfirmPayResult, ReturnCoinsRequest, SenderWireInfos, - TipStatus, PreparePayResult, AcceptWithdrawalResponse, PurchaseDetails, @@ -93,6 +92,9 @@ import { codecForSetCoinSuspendedRequest, codecForForceExchangeUpdateRequest, codecForForceRefreshRequest, + PrepareTipResult, + codecForPrepareTipRequest, + codecForAcceptTipRequest, } from "./types/walletTypes"; import { Logger } from "./util/logging"; @@ -121,7 +123,7 @@ import { import { processWithdrawGroup } from "./operations/withdraw"; import { getPendingOperations } from "./operations/pending"; import { getBalances } from "./operations/balance"; -import { acceptTip, getTipStatus, processTip } from "./operations/tip"; +import { acceptTip, prepareTip, processTip } from "./operations/tip"; import { TimerGroup } from "./util/timer"; import { AsyncCondition } from "./util/promiseUtils"; import { AsyncOpMemoSingle } from "./util/asyncMemo"; @@ -769,8 +771,8 @@ export class Wallet { } } - async getTipStatus(talerTipUri: string): Promise { - return getTipStatus(this.ws, talerTipUri); + async prepareTip(talerTipUri: string): Promise { + return prepareTip(this.ws, talerTipUri); } async abortFailedPayment(contractTermsHash: string): Promise { @@ -1096,6 +1098,15 @@ export class Wallet { refreshGroupId, }; } + case "prepareTip": { + const req = codecForPrepareTipRequest().decode(payload); + return await this.prepareTip(req.talerTipUri); + } + case "acceptTip": { + const req = codecForAcceptTipRequest().decode(payload); + await this.acceptTip(req.walletTipId); + return {}; + } } throw OperationFailedError.fromCode( TalerErrorCode.WALLET_CORE_API_OPERATION_UNKNOWN, diff --git a/packages/taler-wallet-webextension/src/wxApi.ts b/packages/taler-wallet-webextension/src/wxApi.ts index 947b63cea..9bc4a08e6 100644 --- a/packages/taler-wallet-webextension/src/wxApi.ts +++ b/packages/taler-wallet-webextension/src/wxApi.ts @@ -22,12 +22,8 @@ * Imports. */ import { - AmountJson, ConfirmPayResult, BalancesResponse, - PurchaseDetails, - TipStatus, - BenchmarkResult, PreparePayResult, AcceptWithdrawalResponse, WalletDiagnostics, -- cgit v1.2.3