summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-01-16 13:15:40 +0100
committerFlorian Dold <florian@dold.me>2024-01-16 13:15:40 +0100
commit1c286ebb2f1c817f5362517d47466c39826c8699 (patch)
treec0cfcee641529032ee06674b943036fe5c0a7cef
parent2e2cf4049a771c82fcc520686de3ace7603baa05 (diff)
downloadwallet-core-1c286ebb2f1c817f5362517d47466c39826c8699.tar.gz
wallet-core-1c286ebb2f1c817f5362517d47466c39826c8699.tar.bz2
wallet-core-1c286ebb2f1c817f5362517d47466c39826c8699.zip
wallet-core: implement remaining DD48 calls, test
-rw-r--r--packages/taler-harness/src/integrationtests/test-wallet-dd48.ts178
-rw-r--r--packages/taler-harness/src/integrationtests/testrunner.ts2
-rw-r--r--packages/taler-util/src/taler-error-codes.ts8
-rw-r--r--packages/taler-util/src/wallet-types.ts63
-rw-r--r--packages/taler-wallet-core/src/db.ts8
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts164
-rw-r--r--packages/taler-wallet-core/src/util/query.ts10
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts37
-rw-r--r--packages/taler-wallet-core/src/wallet.ts22
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,
};