summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/exchanges.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/exchanges.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts188
1 files changed, 162 insertions, 26 deletions
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 67404665c..b4d45db2c 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -25,6 +25,7 @@
*/
import {
AbsoluteTime,
+ AgeRestriction,
Amounts,
CancellationToken,
DeleteExchangeRequest,
@@ -50,7 +51,10 @@ import {
LibtoolVersion,
Logger,
NotificationType,
+ OperationErrorInfo,
Recoup,
+ ScopeInfo,
+ ScopeType,
TalerError,
TalerErrorCode,
TalerErrorDetail,
@@ -88,7 +92,7 @@ import {
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
PendingTaskType,
- WalletDbReadWriteTransaction,
+ WalletDbReadOnlyTransactionArr,
WalletDbReadWriteTransactionArr,
createTimeline,
isWithdrawableDenom,
@@ -103,7 +107,6 @@ import {
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js";
import {
- DbReadOnlyTransaction,
DbReadOnlyTransactionArr,
GetReadOnlyAccess,
GetReadWriteAccess,
@@ -114,9 +117,10 @@ import {
TaskRunResult,
TaskRunResultType,
constructTaskIdentifier,
+ getExchangeEntryStatusFromRecord,
getExchangeState,
getExchangeTosStatusFromRecord,
- makeExchangeListItem,
+ getExchangeUpdateStatusFromRecord,
runTaskWithErrorReporting,
} from "./common.js";
@@ -206,6 +210,105 @@ async function getExchangeRecordsInternal(
]);
}
+export async function getExchangeScopeInfo(
+ tx: WalletDbReadOnlyTransactionArr<
+ [
+ "exchanges",
+ "exchangeDetails",
+ "globalCurrencyExchanges",
+ "globalCurrencyAuditors",
+ ]
+ >,
+ exchangeBaseUrl: string,
+ currency: string,
+): Promise<ScopeInfo> {
+ const det = await getExchangeRecordsInternal(tx, exchangeBaseUrl);
+ if (!det) {
+ return {
+ type: ScopeType.Exchange,
+ currency: currency,
+ url: exchangeBaseUrl,
+ };
+ }
+ return internalGetExchangeScopeInfo(tx, det);
+}
+
+async function internalGetExchangeScopeInfo(
+ tx: WalletDbReadOnlyTransactionArr<
+ ["globalCurrencyExchanges", "globalCurrencyAuditors"]
+ >,
+ exchangeDetails: ExchangeDetailsRecord,
+): Promise<ScopeInfo> {
+ const globalExchangeRec =
+ await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([
+ exchangeDetails.currency,
+ exchangeDetails.exchangeBaseUrl,
+ exchangeDetails.masterPublicKey,
+ ]);
+ if (globalExchangeRec) {
+ return {
+ currency: exchangeDetails.currency,
+ type: ScopeType.Global,
+ };
+ } else {
+ for (const aud of exchangeDetails.auditors) {
+ const globalAuditorRec =
+ await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get([
+ exchangeDetails.currency,
+ aud.auditor_url,
+ aud.auditor_pub,
+ ]);
+ if (globalAuditorRec) {
+ return {
+ currency: exchangeDetails.currency,
+ type: ScopeType.Auditor,
+ url: aud.auditor_url,
+ };
+ }
+ }
+ }
+ return {
+ currency: exchangeDetails.currency,
+ type: ScopeType.Exchange,
+ url: exchangeDetails.exchangeBaseUrl,
+ };
+}
+
+async function makeExchangeListItem(
+ tx: WalletDbReadOnlyTransactionArr<
+ ["globalCurrencyExchanges", "globalCurrencyAuditors"]
+ >,
+ r: ExchangeEntryRecord,
+ exchangeDetails: ExchangeDetailsRecord | undefined,
+ lastError: TalerErrorDetail | undefined,
+): Promise<ExchangeListItem> {
+ const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError
+ ? {
+ error: lastError,
+ }
+ : undefined;
+
+ let scopeInfo: ScopeInfo | undefined = undefined;
+
+ if (exchangeDetails) {
+ scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
+ }
+
+ return {
+ exchangeBaseUrl: r.baseUrl,
+ currency: exchangeDetails?.currency ?? r.presetCurrencyHint,
+ exchangeUpdateStatus: getExchangeUpdateStatusFromRecord(r),
+ exchangeEntryStatus: getExchangeEntryStatusFromRecord(r),
+ tosStatus: getExchangeTosStatusFromRecord(r),
+ ageRestrictionOptions: exchangeDetails?.ageMask
+ ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask)
+ : [],
+ paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? [],
+ lastUpdateErrorInfo,
+ scopeInfo,
+ };
+}
+
export interface ExchangeWireDetails {
currency: string;
masterPublicKey: EddsaPublicKeyString;
@@ -240,14 +343,15 @@ export async function lookupExchangeByUri(
ws: InternalWalletState,
req: GetExchangeEntryByUrlRequest,
): Promise<ExchangeListItem> {
- return await ws.db
- .mktx((x) => [
- x.exchanges,
- x.exchangeDetails,
- x.denominations,
- x.operationRetries,
- ])
- .runReadOnly(async (tx) => {
+ return await ws.db.runReadOnlyTx(
+ [
+ "exchanges",
+ "exchangeDetails",
+ "operationRetries",
+ "globalCurrencyAuditors",
+ "globalCurrencyExchanges",
+ ],
+ async (tx) => {
const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl);
if (!exchangeRec) {
throw Error("exchange not found");
@@ -259,12 +363,14 @@ export async function lookupExchangeByUri(
const opRetryRecord = await tx.operationRetries.get(
TaskIdentifiers.forExchangeUpdate(exchangeRec),
);
- return makeExchangeListItem(
+ return await makeExchangeListItem(
+ tx,
exchangeRec,
exchangeDetails,
opRetryRecord?.lastError,
);
- });
+ },
+ );
}
/**
@@ -800,6 +906,7 @@ export interface ReadyExchangeSummary {
wireInfo: WireInfo;
protocolVersionRange: string;
tosAcceptedTimestamp: TalerPreciseTimestamp | undefined;
+ scopeInfo: ScopeInfo;
}
/**
@@ -863,14 +970,26 @@ export async function fetchFreshExchange(
);
}
- const { exchange, exchangeDetails, retryInfo } = await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries])
- .runReadOnly(async (tx) => {
- const exchange = await tx.exchanges.get(canonUrl);
- const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl);
- const retryInfo = await tx.operationRetries.get(operationId);
- return { exchange, exchangeDetails, retryInfo };
- });
+ const { exchange, exchangeDetails, retryInfo, scopeInfo } =
+ await ws.db.runReadOnlyTx(
+ [
+ "exchanges",
+ "exchangeDetails",
+ "operationRetries",
+ "globalCurrencyAuditors",
+ "globalCurrencyExchanges",
+ ],
+ async (tx) => {
+ const exchange = await tx.exchanges.get(canonUrl);
+ const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl);
+ const retryInfo = await tx.operationRetries.get(operationId);
+ let scopeInfo: ScopeInfo | undefined = undefined;
+ if (exchange && exchangeDetails) {
+ scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails);
+ }
+ return { exchange, exchangeDetails, retryInfo, scopeInfo };
+ },
+ );
if (!exchange) {
throw Error("exchange entry does not exist anymore");
@@ -891,6 +1010,10 @@ export async function fetchFreshExchange(
throw Error("invariant failed");
}
+ if (!scopeInfo) {
+ throw Error("invariant failed");
+ }
+
const res: ReadyExchangeSummary = {
currency: exchangeDetails.currency,
exchangeBaseUrl: canonUrl,
@@ -903,6 +1026,7 @@ export async function fetchFreshExchange(
tosAcceptedTimestamp: timestampOptionalPreciseFromDb(
exchange.tosAcceptedTimestamp,
),
+ scopeInfo,
};
if (options.expectedMasterPub) {
@@ -1309,9 +1433,15 @@ export async function listExchanges(
ws: InternalWalletState,
): Promise<ExchangesListResponse> {
const exchanges: ExchangeListItem[] = [];
- await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries])
- .runReadOnly(async (tx) => {
+ await ws.db.runReadOnlyTx(
+ [
+ "exchanges",
+ "operationRetries",
+ "exchangeDetails",
+ "globalCurrencyAuditors",
+ "globalCurrencyExchanges",
+ ],
+ async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
const taskId = constructTaskIdentifier({
@@ -1321,10 +1451,16 @@ export async function listExchanges(
const exchangeDetails = await getExchangeRecordsInternal(tx, r.baseUrl);
const opRetryRecord = await tx.operationRetries.get(taskId);
exchanges.push(
- makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
+ await makeExchangeListItem(
+ tx,
+ r,
+ exchangeDetails,
+ opRetryRecord?.lastError,
+ ),
);
}
- });
+ },
+ );
return { exchanges };
}