diff options
-rw-r--r-- | packages/taler-harness/src/integrationtests/test-wallet-dd48.ts | 178 | ||||
-rw-r--r-- | packages/taler-harness/src/integrationtests/testrunner.ts | 2 | ||||
-rw-r--r-- | packages/taler-util/src/taler-error-codes.ts | 8 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 63 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 8 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/exchanges.ts | 164 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/util/query.ts | 10 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 37 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 22 |
9 files changed, 410 insertions, 82 deletions
diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts new file mode 100644 index 000000000..c4fe40586 --- /dev/null +++ b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts @@ -0,0 +1,178 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; +import { + ExchangeService, + FakebankService, + GlobalTestState, + WalletClient, + WalletService, + setupDb, +} from "../harness/harness.js"; +import { + ExchangeEntryStatus, + NotificationType, + TalerError, + TalerErrorCode, + WalletNotification, + j2s, +} from "@gnu-taler/taler-util"; +import { withdrawViaBankV2 } from "../harness/helpers.js"; + +/** + * Test for DD48 notifications. + */ +export async function runWalletDd48Test(t: GlobalTestState) { + // Set up test environment + + const db = await setupDb(t); + + const bank = await FakebankService.create(t, { + allowRegistrations: true, + currency: "TESTKUDOS", + database: db.connStr, + httpPort: 8082, + }); + + const exchange = ExchangeService.create(t, { + name: "testexchange-1", + currency: "TESTKUDOS", + httpPort: 8081, + database: db.connStr, + }); + + const exchangeBankAccount = await bank.createExchangeAccount( + "myexchange", + "x", + ); + exchange.addBankAccount("1", exchangeBankAccount); + + bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri); + + await bank.start(); + + await bank.pingUntilAvailable(); + + const coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")); + exchange.addCoinConfigList(coinConfig); + + await exchange.start(); + await exchange.pingUntilAvailable(); + + const walletService = new WalletService(t, { + name: "wallet", + useInMemoryDb: true, + }); + await walletService.start(); + await walletService.pingUntilAvailable(); + + const allNotifications: WalletNotification[] = []; + + const walletClient = new WalletClient({ + unixPath: walletService.socketPath, + onNotification(n) { + console.log("got notification", n); + allNotifications.push(n); + }, + }); + await walletClient.connect(); + await walletClient.client.call(WalletApiOperation.InitWallet, { + skipDefaults: true, + }); + + await walletClient.call(WalletApiOperation.AddExchange, { + exchangeBaseUrl: exchange.baseUrl, + }); + + { + const exchangeEntry = await walletClient.call( + WalletApiOperation.GetExchangeEntryByUrl, + { + exchangeBaseUrl: exchange.baseUrl, + }, + ); + + t.assertDeepEqual( + exchangeEntry.exchangeEntryStatus, + ExchangeEntryStatus.Ephemeral, + ); + + const resources = await walletClient.call( + WalletApiOperation.GetExchangeResources, + { + exchangeBaseUrl: exchange.baseUrl, + }, + ); + t.assertDeepEqual(resources.hasResources, false); + } + + const wres = await withdrawViaBankV2(t, { + walletClient, + amount: "TESTKUDOS:20", + bank, + exchange, + }); + + await wres.withdrawalFinishedCond; + + const exchangeEntry = await walletClient.call( + WalletApiOperation.GetExchangeEntryByUrl, + { + exchangeBaseUrl: exchange.baseUrl, + }, + ); + + t.assertDeepEqual( + exchangeEntry.exchangeEntryStatus, + ExchangeEntryStatus.Used, + ); + + t.assertTrue( + !!allNotifications.find( + (x) => + x.type === NotificationType.ExchangeStateTransition && + x.oldExchangeState == null && + x.newExchangeState.exchangeEntryStatus === + ExchangeEntryStatus.Ephemeral, + ), + ); + + console.log(j2s(allNotifications)); + + const delErr = await t.assertThrowsAsync(async () => { + await walletClient.call(WalletApiOperation.DeleteExchange, { + exchangeBaseUrl: exchange.baseUrl, + }); + }); + + t.assertTrue(delErr instanceof TalerError); + t.assertDeepEqual( + delErr.errorDetail.code, + TalerErrorCode.WALLET_EXCHANGE_ENTRY_USED, + ); + + await walletClient.call(WalletApiOperation.DeleteExchange, { + exchangeBaseUrl: exchange.baseUrl, + purge: true, + }); +} + +runWalletDd48Test.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts index 6a8eb9504..6ab87c756 100644 --- a/packages/taler-harness/src/integrationtests/testrunner.ts +++ b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -98,6 +98,7 @@ import { runAgeRestrictionsDepositTest } from "./test-age-restrictions-deposit.j import { runWithdrawalConversionTest } from "./test-withdrawal-conversion.js"; import { runPaymentDeletedTest } from "./test-payment-deleted.js"; import { runWithdrawalNotifyBeforeTxTest } from "./test-withdrawal-notify-before-tx.js"; +import { runWalletDd48Test } from "./test-wallet-dd48.js"; /** * Test runner. @@ -185,6 +186,7 @@ const allTests: TestMainFunction[] = [ runWalletGenDbTest, runLibeufinBankTest, runPaymentDeletedTest, + runWalletDd48Test, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/taler-error-codes.ts b/packages/taler-util/src/taler-error-codes.ts index 9dd965d1b..6b9362329 100644 --- a/packages/taler-util/src/taler-error-codes.ts +++ b/packages/taler-util/src/taler-error-codes.ts @@ -3905,6 +3905,14 @@ export enum TalerErrorCode { /** + * An exchange entry is still used by the exchange, thus it can't be deleted without purging. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_EXCHANGE_ENTRY_USED = 7033, + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 583d5dff5..52156cf9e 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -577,11 +577,11 @@ export interface CoinDumpJson { withdrawal_reserve_pub: string | undefined; coin_status: CoinStatus; spend_allocation: - | { - id: string; - amount: AmountString; - } - | undefined; + | { + id: string; + amount: AmountString; + } + | undefined; /** * Information about the age restriction */ @@ -944,14 +944,14 @@ export interface PreparePayResultAlreadyConfirmed { } export interface BankWithdrawDetails { - status: WithdrawalOperationStatus, + status: WithdrawalOperationStatus; amount: AmountJson; senderWire?: string; suggestedExchange?: string; confirmTransferUrl?: string; wireTypes: string[]; - operationId: string, - apiBaseUrl: string, + operationId: string; + apiBaseUrl: string; } export interface AcceptWithdrawalResponse { @@ -1701,6 +1701,31 @@ export const codecForUpdateExchangeEntryRequest = .property("exchangeBaseUrl", codecForString()) .build("UpdateExchangeEntryRequest"); +export interface GetExchangeResourcesRequest { + exchangeBaseUrl: string; +} + +export const codecForGetExchangeResourcesRequest = + (): Codec<GetExchangeResourcesRequest> => + buildCodecForObject<GetExchangeResourcesRequest>() + .property("exchangeBaseUrl", codecForString()) + .build("GetExchangeResourcesRequest"); + +export interface GetExchangeResourcesResponse { + hasResources: boolean; +} + +export interface DeleteExchangeRequest { + exchangeBaseUrl: string; + purge?: boolean; +} + +export const codecForDeleteExchangeRequest = (): Codec<DeleteExchangeRequest> => + buildCodecForObject<DeleteExchangeRequest>() + .property("exchangeBaseUrl", codecForString()) + .property("purge", codecOptional(codecForBoolean())) + .build("DeleteExchangeRequest"); + export interface ForceExchangeUpdateRequest { exchangeBaseUrl: string; } @@ -1810,7 +1835,10 @@ export const codecForGetWithdrawalDetailsForUri = buildCodecForObject<GetWithdrawalDetailsForUriRequest>() .property("talerWithdrawUri", codecForString()) .property("restrictAge", codecOptional(codecForNumber())) - .property("notifyChangeFromPendingTimeoutMs", codecOptional(codecForNumber())) + .property( + "notifyChangeFromPendingTimeoutMs", + codecOptional(codecForNumber()), + ) .build("GetWithdrawalDetailsForUriRequest"); export interface ListKnownBankAccountsRequest { @@ -2191,7 +2219,7 @@ export interface TxIdResponse { export interface WithdrawUriInfoResponse { operationId: string; - status: WithdrawalOperationStatus, + status: WithdrawalOperationStatus; confirmTransferUrl?: string; amount: AmountString; defaultExchangeBaseUrl?: string; @@ -2203,12 +2231,15 @@ export const codecForWithdrawUriInfoResponse = buildCodecForObject<WithdrawUriInfoResponse>() .property("operationId", codecForString()) .property("confirmTransferUrl", codecOptional(codecForString())) - .property("status", codecForEither( - codecForConstString("pending"), - codecForConstString("selected"), - codecForConstString("aborted"), - codecForConstString("confirmed"), - )) + .property( + "status", + codecForEither( + codecForConstString("pending"), + codecForConstString("selected"), + codecForConstString("aborted"), + codecForConstString("confirmed"), + ), + ) .property("amount", codecForAmountString()) .property("defaultExchangeBaseUrl", codecOptional(codecForString())) .property("possibleExchanges", codecForList(codecForExchangeListItem())) diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 5a412fb27..73739c19c 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -149,7 +149,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName"; * backwards-compatible way or object stores and indices * are added. */ -export const WALLET_DB_MINOR_VERSION = 1; +export const WALLET_DB_MINOR_VERSION = 2; declare const symDbProtocolTimestamp: unique symbol; @@ -2383,6 +2383,9 @@ export const WalletStoresV1 = { autoIncrement: true, }), { + byExchangeBaseUrl: describeIndex("byExchangeBaseUrl", "exchangeBaseUrl", { + versionAdded: 2, + }), byPointer: describeIndex( "byDetailsPointer", ["exchangeBaseUrl", "currency", "masterPublicKey"], @@ -2461,6 +2464,9 @@ export const WalletStoresV1 = { }), { byStatus: describeIndex("byStatus", "status"), + byExchangeBaseUrl: describeIndex("byExchangeBaseUrl", "exchangeBaseUrl", { + versionAdded: 2, + }), byTalerWithdrawUri: describeIndex( "byTalerWithdrawUri", "wgInfo.bankInfo.talerWithdrawUri", diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index ca3ea8af6..f983a7c4d 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -27,6 +27,7 @@ import { AbsoluteTime, Amounts, CancellationToken, + DeleteExchangeRequest, DenomKeyType, DenomOperationMap, DenominationInfo, @@ -43,6 +44,7 @@ import { ExchangesListResponse, FeeDescription, GetExchangeEntryByUrlRequest, + GetExchangeResourcesResponse, GetExchangeTosResult, GlobalFees, LibtoolVersion, @@ -63,7 +65,6 @@ import { WireInfo, canonicalizeBaseUrl, codecForExchangeKeysJson, - durationFromSpec, encodeCrock, hashDenomPub, j2s, @@ -86,12 +87,10 @@ import { import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, - OpenedPromise, PendingTaskType, WalletDbReadWriteTransaction, createTimeline, isWithdrawableDenom, - openPromise, selectBestForOverlappingDenominations, selectMinimumFee, timestampOptionalAbsoluteFromDb, @@ -100,9 +99,14 @@ import { timestampPreciseToDb, timestampProtocolToDb, } from "../index.js"; -import { CancelFn, InternalWalletState } from "../internal-wallet-state.js"; +import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; -import { GetReadOnlyAccess, GetReadWriteAccess } from "../util/query.js"; +import { + DbReadOnlyTransaction, + DbReadOnlyTransactionArr, + GetReadOnlyAccess, + GetReadWriteAccess, +} from "../util/query.js"; import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "../versions.js"; import { TaskIdentifiers, @@ -263,14 +267,11 @@ export async function lookupExchangeByUri( } /** - * Mark a ToS version as accepted by the user. - * - * @param etag version of the ToS to accept, or current ToS version of not given + * Mark the current ToS version as accepted by the user. */ export async function acceptExchangeTermsOfService( ws: InternalWalletState, exchangeBaseUrl: string, - etag: string | undefined, ): Promise<void> { const notif = await ws.db .mktx((x) => [x.exchanges, x.exchangeDetails]) @@ -298,6 +299,11 @@ export async function acceptExchangeTermsOfService( } } +/** + * Validate wire fees and wire accounts. + * + * Throw an exception if they are invalid. + */ async function validateWireInfo( ws: InternalWalletState, versionCurrent: number, @@ -364,6 +370,11 @@ async function validateWireInfo( }; } +/** + * Validate global fees. + * + * Throw an exception if they are invalid. + */ async function validateGlobalFees( ws: InternalWalletState, fees: GlobalFees[], @@ -455,7 +466,6 @@ async function provideExchangeRecordInTx( exchangeDetails: typeof WalletStoresV1.exchangeDetails; }>, baseUrl: string, - now: AbsoluteTime, ): Promise<{ exchange: ExchangeEntryRecord; exchangeDetails: ExchangeDetailsRecord | undefined; @@ -655,7 +665,7 @@ async function downloadExchangeKeysInfo( reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay, expiry: AbsoluteTime.toProtocolTimestamp( getExpiry(resp, { - minDuration: durationFromSpec({ hours: 1 }), + minDuration: Duration.fromSpec({ hours: 1 }), }), ), recoup: exchangeKeysJsonUnchecked.recoup ?? [], @@ -718,12 +728,10 @@ export async function startUpdateExchangeEntry( ): Promise<void> { const canonBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); - const now = AbsoluteTime.now(); - const { notification } = await ws.db .mktx((x) => [x.exchanges, x.exchangeDetails]) .runReadWrite(async (tx) => { - return provideExchangeRecordInTx(ws, tx, exchangeBaseUrl, now); + return provideExchangeRecordInTx(ws, tx, exchangeBaseUrl); }); if (notification) { @@ -778,46 +786,6 @@ export async function startUpdateExchangeEntry( ws.workAvailable.trigger(); } -export interface NotificationWaiter { - waitNext(): Promise<void>; - cancel(): void; -} - -export function createNotificationWaiter( - ws: InternalWalletState, - pred: (x: WalletNotification) => boolean, -): NotificationWaiter { - ws.ensureTaskLoopRunning(); - let cancelFn: CancelFn | undefined = undefined; - let p: OpenedPromise<void> | undefined = undefined; - - return { - cancel() { - cancelFn?.(); - }, - waitNext(): Promise<void> { - if (!p) { - p = openPromise(); - cancelFn = ws.addNotificationListener((notif) => { - if (pred(notif)) { - // We got a notification that matches our predicate. - // Resolve promise for existing waiters, - // and create a new promise to wait for the next - // notification occurrence. - const myResolve = p?.resolve; - const myCancel = cancelFn; - p = undefined; - cancelFn = undefined; - myResolve?.(); - myCancel?.(); - } - }); - } - return p.promise; - }, - }; -} - /** * Basic information about an exchange in a ready state. */ @@ -1363,6 +1331,9 @@ export async function listExchanges( * Transition an exchange to the "used" entry state if necessary. * * Should be called whenever the exchange is actively used by the client (for withdrawals etc.). + * + * The caller should emit the returned notification iff the current transaction + * succeeded. */ export async function markExchangeUsed( ws: InternalWalletState, @@ -1555,3 +1526,88 @@ export async function getExchangeDetailedInfo( }, }; } + +async function internalGetExchangeResources( + ws: InternalWalletState, + tx: DbReadOnlyTransactionArr< + typeof WalletStoresV1, + ["exchanges", "coins", "withdrawalGroups"] + >, + exchangeBaseUrl: string, +): Promise<GetExchangeResourcesResponse> { + let numWithdrawals = 0; + let numCoins = 0; + numCoins = await tx.coins.indexes.byBaseUrl.count(exchangeBaseUrl); + numWithdrawals = + await tx.withdrawalGroups.indexes.byExchangeBaseUrl.count(exchangeBaseUrl); + const total = numWithdrawals + numCoins; + return { + hasResources: total != 0, + }; +} + +export async function deleteExchange( + ws: InternalWalletState, + req: DeleteExchangeRequest, +): Promise<void> { + let inUse: boolean = false; + const exchangeBaseUrl = canonicalizeBaseUrl(req.exchangeBaseUrl); + await ws.db.runReadWriteTx( + ["exchanges", "coins", "withdrawalGroups", "exchangeDetails"], + async (tx) => { + const exchangeRec = await tx.exchanges.get(exchangeBaseUrl); + if (!exchangeRec) { + // Nothing to delete! + logger.info("no exchange found to delete"); + return; + } + const res = await internalGetExchangeResources(ws, tx, exchangeBaseUrl); + if (res.hasResources) { + if (req.purge) { + const detRecs = + await tx.exchangeDetails.indexes.byExchangeBaseUrl.getAll(); + for (const r of detRecs) { + if (r.rowId == null) { + // Should never happen, as rowId is the primary key. + continue; + } + await tx.exchangeDetails.delete(r.rowId); + } + // FIXME: Also remove records related to transactions? + } else { + inUse = true; + return; + } + } + await tx.exchanges.delete(exchangeBaseUrl); + }, + ); + + if (inUse) { + throw TalerError.fromUncheckedDetail({ + code: TalerErrorCode.WALLET_EXCHANGE_ENTRY_USED, + hint: "Exchange in use.", + }); + } +} + +export async function getExchangeResources( + ws: InternalWalletState, + exchangeBaseUrl: string, +): Promise<GetExchangeResourcesResponse> { + // Withdrawals include internal withdrawals from peer transactions + const res = await ws.db.runReadOnlyTx( + ["exchanges", "withdrawalGroups", "coins"], + async (tx) => { + const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl); + if (!exchangeRecord) { + return undefined; + } + return internalGetExchangeResources(ws, tx, exchangeBaseUrl); + }, + ); + if (!res) { + throw Error("exchange not found"); + } + return res; +} diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 17b9b407c..59c6ea2f5 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -344,6 +344,7 @@ interface IndexReadOnlyAccessor<RecordType> { query?: IDBKeyRange | IDBValidKey, count?: number, ): Promise<RecordType[]>; + count(query?: IDBValidKey): Promise<number>; } type GetIndexReadOnlyAccess<RecordType, IndexMap> = { @@ -357,6 +358,7 @@ interface IndexReadWriteAccessor<RecordType> { query?: IDBKeyRange | IDBValidKey, count?: number, ): Promise<RecordType[]>; + count(query?: IDBValidKey): Promise<number>; } type GetIndexReadWriteAccess<RecordType, IndexMap> = { @@ -696,6 +698,10 @@ function makeReadContext( .getAll(query, count); return requestToPromise(req); }, + count(query) { + const req = tx.objectStore(storeName).index(indexName).count(query); + return requestToPromise(req); + }, }; } ctx[storeAlias] = { @@ -745,6 +751,10 @@ function makeWriteContext( .getAll(query, count); return requestToPromise(req); }, + count(query) { + const req = tx.objectStore(storeName).index(indexName).count(query); + return requestToPromise(req); + }, }; } ctx[storeAlias] = { diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 7d3dc86a3..2cbf693f5 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -130,6 +130,9 @@ import { WithdrawUriInfoResponse, WithdrawalTransactionByURIRequest, TransactionWithdrawal, + GetExchangeResourcesRequest, + DeleteExchangeRequest, + GetExchangeResourcesResponse, } from "@gnu-taler/taler-util"; import { AddBackupProviderRequest, @@ -238,6 +241,8 @@ export enum WalletApiOperation { ListExchangesForScopedCurrency = "listExchangesForScopedCurrency", PrepareWithdrawExchange = "prepareWithdrawExchange", TestingInfiniteTransactionLoop = "testingInfiniteTransactionLoop", + GetExchangeResources = "getExchangeResources", + DeleteExchange = "deleteExchange", } // group: Initialization @@ -668,6 +673,24 @@ export type GetExchangeEntryByUrlOp = { }; /** + * Get resources associated with an exchange. + */ +export type GetExchangeResourcesOp = { + op: WalletApiOperation.GetExchangeResources; + request: GetExchangeResourcesRequest; + response: GetExchangeResourcesResponse; +}; + +/** + * Get resources associated with an exchange. + */ +export type DeleteExchangeOp = { + op: WalletApiOperation.GetExchangeResources; + request: DeleteExchangeRequest; + response: EmptyObject; +}; + +/** * List currencies known to the wallet. */ export type ListCurrenciesOp = { @@ -1206,6 +1229,8 @@ export type WalletOperations = { [WalletApiOperation.UpdateExchangeEntry]: UpdateExchangeEntryOp; [WalletApiOperation.PrepareWithdrawExchange]: PrepareWithdrawExchangeOp; [WalletApiOperation.TestingInfiniteTransactionLoop]: any; + [WalletApiOperation.DeleteExchange]: DeleteExchangeOp; + [WalletApiOperation.GetExchangeResources]: GetExchangeResourcesOp; }; export type WalletCoreRequestType< @@ -1229,10 +1254,10 @@ type Primitives = string | number | boolean; type RecursivePartial<T extends object> = { [P in keyof T]?: T[P] extends Array<infer U extends object> - ? Array<RecursivePartial<U>> - : T[P] extends Array<infer J extends Primitives> - ? Array<J> - : T[P] extends object - ? RecursivePartial<T[P]> - : T[P]; + ? Array<RecursivePartial<U>> + : T[P] extends Array<infer J extends Primitives> + ? Array<J> + : T[P] extends object + ? RecursivePartial<T[P]> + : T[P]; } & object; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 1a876b2c8..3294e2a09 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -73,6 +73,7 @@ import { codecForConfirmPeerPushPaymentRequest, codecForConvertAmountRequest, codecForCreateDepositGroupRequest, + codecForDeleteExchangeRequest, codecForDeleteStoredBackupRequest, codecForDeleteTransactionRequest, codecForFailTransactionRequest, @@ -83,6 +84,7 @@ import { codecForGetContractTermsDetails, codecForGetCurrencyInfoRequest, codecForGetExchangeEntryByUrlRequest, + codecForGetExchangeResourcesRequest, codecForGetExchangeTosRequest, codecForGetWithdrawalDetailsForAmountRequest, codecForGetWithdrawalDetailsForUri, @@ -189,8 +191,10 @@ import { import { acceptExchangeTermsOfService, addPresetExchangeEntry, + deleteExchange, fetchFreshExchange, getExchangeDetailedInfo, + getExchangeResources, getExchangeTos, listExchanges, lookupExchangeByUri, @@ -726,9 +730,9 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> { ageCommitmentProof: c.ageCommitmentProof, spend_allocation: c.spendAllocation ? { - amount: c.spendAllocation.amount, - id: c.spendAllocation.id, - } + amount: c.spendAllocation.amount, + id: c.spendAllocation.id, + } : undefined, }); } @@ -1071,7 +1075,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.SetExchangeTosAccepted: { const req = codecForAcceptExchangeTosRequest().decode(payload); - await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl, req.etag); + await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl); return {}; } case WalletApiOperation.AcceptBankIntegratedWithdrawal: { @@ -1400,6 +1404,15 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( ws.workAvailable.trigger(); return {}; } + case WalletApiOperation.DeleteExchange: { + const req = codecForDeleteExchangeRequest().decode(payload); + await deleteExchange(ws, req); + return {}; + } + case WalletApiOperation.GetExchangeResources: { + const req = codecForGetExchangeResourcesRequest().decode(payload); + return await getExchangeResources(ws, req.exchangeBaseUrl); + } case WalletApiOperation.TestingInfiniteTransactionLoop: { const myDelayMs = (payload as any).delayMs ?? 5; const shouldFetch = !!(payload as any).shouldFetch; @@ -1616,7 +1629,6 @@ class InternalWalletStateImpl implements InternalWalletState { createRecoupGroup, }; - refreshOps: RefreshOperations = { createRefreshGroup, }; |