wallet-core: pull out ToS into separate object store

This commit is contained in:
Florian Dold 2022-10-14 22:10:03 +02:00
parent f1cba79c65
commit a57fcb144d
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 171 additions and 94 deletions

View File

@ -1165,8 +1165,6 @@ export interface BackupExchange {
currency: string; currency: string;
protocol_version_range: string;
/** /**
* Time when the pointer to the exchange details * Time when the pointer to the exchange details
* was last updated. * was last updated.

View File

@ -620,7 +620,7 @@ export interface KnownBankAccounts {
accounts: KnownBankAccountsInfo[]; accounts: KnownBankAccountsInfo[];
} }
export interface ExchangeTos { export interface ExchangeTosStatusDetails {
acceptedVersion?: string; acceptedVersion?: string;
currentVersion?: string; currentVersion?: string;
contentType?: string; contentType?: string;
@ -805,7 +805,7 @@ export interface ExchangeFullDetails {
exchangeBaseUrl: string; exchangeBaseUrl: string;
currency: string; currency: string;
paytoUris: string[]; paytoUris: string[];
tos: ExchangeTos; tos: ExchangeTosStatusDetails;
auditors: ExchangeAuditor[]; auditors: ExchangeAuditor[];
wireInfo: WireInfo; wireInfo: WireInfo;
denomFees: DenomOperationMap<FeeDescription[]>; denomFees: DenomOperationMap<FeeDescription[]>;
@ -817,7 +817,7 @@ export interface ExchangeListItem {
exchangeBaseUrl: string; exchangeBaseUrl: string;
currency: string; currency: string;
paytoUris: string[]; paytoUris: string[];
tos: ExchangeTos; tos: ExchangeTosStatusDetails;
} }
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> => const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
@ -833,8 +833,8 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> =>
.property("denomination_keys", codecForList(codecForAuditorDenomSig())) .property("denomination_keys", codecForList(codecForAuditorDenomSig()))
.build("codecForExchangeAuditor"); .build("codecForExchangeAuditor");
const codecForExchangeTos = (): Codec<ExchangeTos> => const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> =>
buildCodecForObject<ExchangeTos>() buildCodecForObject<ExchangeTosStatusDetails>()
.property("acceptedVersion", codecOptional(codecForString())) .property("acceptedVersion", codecOptional(codecForString()))
.property("currentVersion", codecOptional(codecForString())) .property("currentVersion", codecOptional(codecForString()))
.property("contentType", codecOptional(codecForString())) .property("contentType", codecOptional(codecForString()))

View File

@ -80,7 +80,7 @@ import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
* for all previous versions must be written, which should be * for all previous versions must be written, which should be
* avoided. * avoided.
*/ */
export const TALER_DB_NAME = "taler-wallet-main-v6"; export const TALER_DB_NAME = "taler-wallet-main-v7";
/** /**
* Name of the metadata database. This database is used * Name of the metadata database. This database is used
@ -99,7 +99,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
* backwards-compatible way or object stores and indices * backwards-compatible way or object stores and indices
* are added. * are added.
*/ */
export const WALLET_DB_MINOR_VERSION = 2; export const WALLET_DB_MINOR_VERSION = 1;
/** /**
* Ranges for operation status fields. * Ranges for operation status fields.
@ -450,40 +450,41 @@ export interface ExchangeDetailsRecord {
*/ */
signingKeys: ExchangeSignKeyJson[]; signingKeys: ExchangeSignKeyJson[];
/**
* Etag of the current ToS of the exchange.
*/
tosCurrentEtag: string;
/**
* Information about ToS acceptance from the user.
*/
tosAccepted:
| {
etag: string;
timestamp: TalerProtocolTimestamp;
}
| undefined;
wireInfo: WireInfo;
}
export interface ExchangeTosRecord {
exchangeBaseUrl: string;
etag: string;
/** /**
* Terms of service text or undefined if not downloaded yet. * Terms of service text or undefined if not downloaded yet.
* *
* This is just used as a cache of the last downloaded ToS. * This is just used as a cache of the last downloaded ToS.
* *
* FIXME: Put in separate object store!
*/ */
termsOfServiceText: string | undefined; termsOfServiceText: string | undefined;
/** /**
* content-type of the last downloaded termsOfServiceText. * Content-type of the last downloaded termsOfServiceText.
*
* * FIXME: Put in separate object store!
*/ */
termsOfServiceContentType: string | undefined; termsOfServiceContentType: string | undefined;
/**
* ETag for last terms of service download.
*/
termsOfServiceLastEtag: string | undefined;
/**
* ETag for last terms of service accepted.
*/
termsOfServiceAcceptedEtag: string | undefined;
/**
* Timestamp when the ToS was accepted.
*
* Used during backup merging.
*/
termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
wireInfo: WireInfo;
} }
export interface ExchangeDetailsPointer { export interface ExchangeDetailsPointer {
@ -491,11 +492,6 @@ export interface ExchangeDetailsPointer {
currency: string; currency: string;
/**
* Last observed protocol version range offered by the exchange.
*/
protocolVersionRange: string;
/** /**
* Timestamp when the (masterPublicKey, currency) pointer * Timestamp when the (masterPublicKey, currency) pointer
* has been updated. * has been updated.
@ -1899,6 +1895,14 @@ export const WalletStoresV1 = {
byReservePub: describeIndex("byReservePub", "reservePub", {}), byReservePub: describeIndex("byReservePub", "reservePub", {}),
}, },
), ),
exchangeTos: describeStore(
"exchangeTos",
describeContents<ExchangeTosRecord>({
keyPath: ["exchangeBaseUrl", "etag"],
autoIncrement: true,
}),
{},
),
config: describeStore( config: describeStore(
"config", "config",
describeContents<ConfigRecord>({ keyPath: "key" }), describeContents<ConfigRecord>({ keyPath: "key" }),
@ -2116,7 +2120,6 @@ export const WalletStoresV1 = {
"bankAccounts", "bankAccounts",
describeContents<BankAccountsRecord>({ describeContents<BankAccountsRecord>({
keyPath: "uri", keyPath: "uri",
versionAdded: 2,
}), }),
{}, {},
), ),

View File

@ -298,7 +298,6 @@ export async function exportBackup(
currency: dp.currency, currency: dp.currency,
master_public_key: dp.masterPublicKey, master_public_key: dp.masterPublicKey,
update_clock: dp.updateClock, update_clock: dp.updateClock,
protocol_version_range: dp.protocolVersionRange,
}); });
}); });
@ -358,8 +357,8 @@ export async function exportBackup(
purseTimeout: x.purseTimeout, purseTimeout: x.purseTimeout,
startDate: x.startDate, startDate: x.startDate,
})), })),
tos_accepted_etag: ex.termsOfServiceAcceptedEtag, tos_accepted_etag: ex.tosAccepted?.etag,
tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp, tos_accepted_timestamp: ex.tosAccepted?.timestamp,
denominations: denominations:
backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [], backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
}); });

View File

@ -351,7 +351,6 @@ export async function importBackup(
currency: backupExchange.currency, currency: backupExchange.currency,
masterPublicKey: backupExchange.master_public_key, masterPublicKey: backupExchange.master_public_key,
updateClock: backupExchange.update_clock, updateClock: backupExchange.update_clock,
protocolVersionRange: backupExchange.protocol_version_range,
}, },
permanent: true, permanent: true,
lastUpdate: undefined, lastUpdate: undefined,
@ -388,14 +387,18 @@ export async function importBackup(
wadFee: Amounts.parseOrThrow(fee.wad_fee), wadFee: Amounts.parseOrThrow(fee.wad_fee),
}); });
} }
let tosAccepted = undefined;
if (
backupExchangeDetails.tos_accepted_etag &&
backupExchangeDetails.tos_accepted_timestamp
) {
tosAccepted = {
etag: backupExchangeDetails.tos_accepted_etag,
timestamp: backupExchangeDetails.tos_accepted_timestamp,
};
}
await tx.exchangeDetails.put({ await tx.exchangeDetails.put({
exchangeBaseUrl: backupExchangeDetails.base_url, exchangeBaseUrl: backupExchangeDetails.base_url,
termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
termsOfServiceText: undefined,
termsOfServiceLastEtag: undefined,
termsOfServiceContentType: undefined,
termsOfServiceAcceptedTimestamp:
backupExchangeDetails.tos_accepted_timestamp,
wireInfo, wireInfo,
currency: backupExchangeDetails.currency, currency: backupExchangeDetails.currency,
auditors: backupExchangeDetails.auditors.map((x) => ({ auditors: backupExchangeDetails.auditors.map((x) => ({
@ -406,6 +409,8 @@ export async function importBackup(
masterPublicKey: backupExchangeDetails.master_public_key, masterPublicKey: backupExchangeDetails.master_public_key,
protocolVersionRange: backupExchangeDetails.protocol_version, protocolVersionRange: backupExchangeDetails.protocol_version,
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay, reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
tosAccepted,
globalFees: backupExchangeDetails.global_fees.map((x) => ({ globalFees: backupExchangeDetails.global_fees.map((x) => ({
accountFee: Amounts.parseOrThrow(x.accountFee), accountFee: Amounts.parseOrThrow(x.accountFee),
historyFee: Amounts.parseOrThrow(x.historyFee), historyFee: Amounts.parseOrThrow(x.historyFee),
@ -419,7 +424,6 @@ export async function importBackup(
purseTimeout: x.purseTimeout, purseTimeout: x.purseTimeout,
startDate: x.startDate, startDate: x.startDate,
})), })),
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({ signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
key: x.key, key: x.key,
master_sig: x.master_sig, master_sig: x.master_sig,

View File

@ -174,24 +174,40 @@ export async function getExchangeDetails(
getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) => getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
db.mktx((x) => [x.exchanges, x.exchangeDetails]); db.mktx((x) => [x.exchanges, x.exchangeDetails]);
/**
* Update the database based on the download of the terms of service.
*/
export async function updateExchangeTermsOfService( export async function updateExchangeTermsOfService(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
tos: ExchangeTosDownloadResult, tos: ExchangeTosDownloadResult,
): Promise<void> { ): Promise<void> {
await ws.db await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails]) .mktx((x) => [x.exchanges, x.exchangeTos, x.exchangeDetails])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const d = await getExchangeDetails(tx, exchangeBaseUrl); const d = await getExchangeDetails(tx, exchangeBaseUrl);
let tosRecord = await tx.exchangeTos.get([exchangeBaseUrl, tos.tosEtag]);
if (!tosRecord) {
tosRecord = {
etag: tos.tosEtag,
exchangeBaseUrl,
termsOfServiceContentType: tos.tosContentType,
termsOfServiceText: tos.tosText,
};
await tx.exchangeTos.put(tosRecord);
}
if (d) { if (d) {
d.termsOfServiceText = tos.tosText; d.tosCurrentEtag = tos.tosEtag;
d.termsOfServiceContentType = tos.tosContentType;
d.termsOfServiceLastEtag = tos.tosEtag;
await tx.exchangeDetails.put(d); await tx.exchangeDetails.put(d);
} }
}); });
} }
/**
* Mark a ToS version as accepted by the user.
*
* @param etag version of the ToS to accept, or current ToS version of not given
*/
export async function acceptExchangeTermsOfService( export async function acceptExchangeTermsOfService(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
@ -202,7 +218,10 @@ export async function acceptExchangeTermsOfService(
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const d = await getExchangeDetails(tx, exchangeBaseUrl); const d = await getExchangeDetails(tx, exchangeBaseUrl);
if (d) { if (d) {
d.termsOfServiceAcceptedEtag = etag; d.tosAccepted = {
etag: etag || d.tosCurrentEtag,
timestamp: TalerProtocolTimestamp.now(),
};
await tx.exchangeDetails.put(d); await tx.exchangeDetails.put(d);
} }
}); });
@ -611,7 +630,8 @@ export async function updateExchangeFromUrlHandler(
["text/plain"], ["text/plain"],
); );
const tosHasBeenAccepted = const tosHasBeenAccepted =
exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag; exchangeDetails?.tosAccepted &&
exchangeDetails.tosAccepted.etag === tosDownload.tosEtag;
let recoupGroupId: string | undefined; let recoupGroupId: string | undefined;
@ -647,13 +667,13 @@ export async function updateExchangeFromUrlHandler(
globalFees, globalFees,
exchangeBaseUrl: r.baseUrl, exchangeBaseUrl: r.baseUrl,
wireInfo, wireInfo,
termsOfServiceText: tosDownload.tosText, tosCurrentEtag: tosDownload.tosContentType,
termsOfServiceAcceptedEtag: tosHasBeenAccepted tosAccepted: tosHasBeenAccepted
? tosDownload.tosEtag ? {
etag: tosDownload.tosEtag,
timestamp: TalerProtocolTimestamp.now(),
}
: undefined, : undefined,
termsOfServiceContentType: tosDownload.tosContentType,
termsOfServiceLastEtag: tosDownload.tosEtag,
termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
}; };
// FIXME: only update if pointer got updated // FIXME: only update if pointer got updated
r.lastUpdate = TalerProtocolTimestamp.now(); r.lastUpdate = TalerProtocolTimestamp.now();
@ -665,7 +685,6 @@ export async function updateExchangeFromUrlHandler(
masterPublicKey: details.masterPublicKey, masterPublicKey: details.masterPublicKey,
// FIXME: only change if pointer really changed // FIXME: only change if pointer really changed
updateClock: TalerProtocolTimestamp.now(), updateClock: TalerProtocolTimestamp.now(),
protocolVersionRange: keysInfo.protocolVersion,
}; };
await tx.exchanges.put(r); await tx.exchanges.put(r);
await tx.exchangeDetails.put(details); await tx.exchangeDetails.put(details);

View File

@ -69,6 +69,7 @@ import {
CoinStatus, CoinStatus,
DenominationRecord, DenominationRecord,
DenominationVerificationStatus, DenominationVerificationStatus,
ExchangeTosRecord,
PlanchetRecord, PlanchetRecord,
PlanchetStatus, PlanchetStatus,
WalletStoresV1, WalletStoresV1,
@ -1278,12 +1279,8 @@ export async function getExchangeWithdrawalInfo(
} }
let tosAccepted = false; let tosAccepted = false;
if (exchangeDetails.tosAccepted?.timestamp) {
if (exchangeDetails.termsOfServiceLastEtag) { if (exchangeDetails.tosAccepted.etag === exchangeDetails.tosCurrentEtag) {
if (
exchangeDetails.termsOfServiceAcceptedEtag ===
exchangeDetails.termsOfServiceLastEtag
) {
tosAccepted = true; tosAccepted = true;
} }
} }
@ -1357,7 +1354,12 @@ export async function getWithdrawalDetailsForUri(
const exchanges: ExchangeListItem[] = []; const exchanges: ExchangeListItem[] = [];
await ws.db await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations]) .mktx((x) => [
x.exchanges,
x.exchangeDetails,
x.exchangeTos,
x.denominations,
])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray(); const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) { for (const r of exchangeRecords) {
@ -1366,14 +1368,19 @@ export async function getWithdrawalDetailsForUri(
.iter(r.baseUrl) .iter(r.baseUrl)
.toArray(); .toArray();
if (details && denominations) { if (details && denominations) {
const tosRecord = await tx.exchangeTos.get([
details.exchangeBaseUrl,
details.tosCurrentEtag,
]);
exchanges.push({ exchanges.push({
exchangeBaseUrl: details.exchangeBaseUrl, exchangeBaseUrl: details.exchangeBaseUrl,
currency: details.currency, currency: details.currency,
// FIXME: We probably don't want to include the full ToS here!
tos: { tos: {
acceptedVersion: details.termsOfServiceAcceptedEtag, acceptedVersion: details.tosAccepted?.etag,
currentVersion: details.termsOfServiceLastEtag, currentVersion: details.tosCurrentEtag,
contentType: details.termsOfServiceContentType, contentType: tosRecord?.termsOfServiceContentType ?? "",
content: details.termsOfServiceText, content: tosRecord?.termsOfServiceText ?? "",
}, },
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri), paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
}); });

View File

@ -94,6 +94,7 @@ import {
WalletCoreVersion, WalletCoreVersion,
WalletNotification, WalletNotification,
codecForSetDevModeRequest, codecForSetDevModeRequest,
ExchangeTosStatusDetails,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import { import {
@ -107,6 +108,8 @@ import {
CoinStatus, CoinStatus,
ConfigRecordKey, ConfigRecordKey,
DenominationRecord, DenominationRecord,
ExchangeDetailsRecord,
ExchangeTosRecord,
exportDb, exportDb,
importDb, importDb,
WalletStoresV1, WalletStoresV1,
@ -228,7 +231,11 @@ import {
OpenedPromise, OpenedPromise,
openPromise, openPromise,
} from "./util/promiseUtils.js"; } from "./util/promiseUtils.js";
import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import {
DbAccess,
GetReadOnlyAccess,
GetReadWriteAccess,
} from "./util/query.js";
import { OperationAttemptResult } from "./util/retries.js"; import { OperationAttemptResult } from "./util/retries.js";
import { TimerAPI, TimerGroup } from "./util/timer.js"; import { TimerAPI, TimerGroup } from "./util/timer.js";
import { import {
@ -461,6 +468,10 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
}); });
} }
/**
* Get the exchange ToS in the requested format.
* Try to download in the accepted format not cached.
*/
async function getExchangeTos( async function getExchangeTos(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
@ -468,9 +479,14 @@ async function getExchangeTos(
): Promise<GetExchangeTosResult> { ): Promise<GetExchangeTosResult> {
// FIXME: download ToS in acceptable format if passed! // FIXME: download ToS in acceptable format if passed!
const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl); const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
const content = exchangeDetails.termsOfServiceText; const tosDetails = await ws.db
const currentEtag = exchangeDetails.termsOfServiceLastEtag; .mktx((x) => [x.exchangeTos])
const contentType = exchangeDetails.termsOfServiceContentType; .runReadOnly(async (tx) => {
return await getExchangeTosStatusDetails(tx, exchangeDetails);
});
const content = tosDetails.content;
const currentEtag = tosDetails.currentVersion;
const contentType = tosDetails.contentType;
if ( if (
content === undefined || content === undefined ||
currentEtag === undefined || currentEtag === undefined ||
@ -483,7 +499,7 @@ async function getExchangeTos(
acceptedFormat.findIndex((f) => f === contentType) !== -1 acceptedFormat.findIndex((f) => f === contentType) !== -1
) { ) {
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag, currentEtag,
content, content,
contentType, contentType,
@ -499,16 +515,17 @@ async function getExchangeTos(
if (tosDownload.tosContentType === contentType) { if (tosDownload.tosContentType === contentType) {
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag, currentEtag,
content, content,
contentType, contentType,
}; };
} }
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload); await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
return { return {
acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag, acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag: tosDownload.tosEtag, currentEtag: tosDownload.tosEtag,
content: tosDownload.tosText, content: tosDownload.tosText,
contentType: tosDownload.tosContentType, contentType: tosDownload.tosContentType,
@ -585,12 +602,43 @@ async function forgetKnownBankAccounts(
return; return;
} }
async function getExchangeTosStatusDetails(
tx: GetReadOnlyAccess<{ exchangeTos: typeof WalletStoresV1.exchangeTos }>,
exchangeDetails: ExchangeDetailsRecord,
): Promise<ExchangeTosStatusDetails> {
let exchangeTos = await tx.exchangeTos.get([
exchangeDetails.exchangeBaseUrl,
exchangeDetails.tosCurrentEtag,
]);
if (!exchangeTos) {
exchangeTos = {
etag: "not-available",
termsOfServiceContentType: "text/plain",
termsOfServiceText: "terms of service unavailable",
exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
};
}
return {
acceptedVersion: exchangeDetails.tosAccepted?.etag,
content: exchangeTos.termsOfServiceContentType,
contentType: exchangeTos.termsOfServiceContentType,
currentVersion: exchangeTos.etag,
};
}
async function getExchanges( async function getExchanges(
ws: InternalWalletState, ws: InternalWalletState,
): Promise<ExchangesListResponse> { ): Promise<ExchangesListResponse> {
const exchanges: ExchangeListItem[] = []; const exchanges: ExchangeListItem[] = [];
await ws.db await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations]) .mktx((x) => [
x.exchanges,
x.exchangeDetails,
x.exchangeTos,
x.denominations,
])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray(); const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) { for (const r of exchangeRecords) {
@ -612,15 +660,12 @@ async function getExchanges(
continue; continue;
} }
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
exchanges.push({ exchanges.push({
exchangeBaseUrl: r.baseUrl, exchangeBaseUrl: r.baseUrl,
currency, currency,
tos: { tos,
acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
currentVersion: exchangeDetails.termsOfServiceLastEtag,
contentType: exchangeDetails.termsOfServiceContentType,
content: exchangeDetails.termsOfServiceText,
},
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri), paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
}); });
} }
@ -634,7 +679,12 @@ async function getExchangeDetailedInfo(
): Promise<ExchangeFullDetails> { ): Promise<ExchangeFullDetails> {
//TODO: should we use the forceUpdate parameter? //TODO: should we use the forceUpdate parameter?
const exchange = await ws.db const exchange = await ws.db
.mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations]) .mktx((x) => [
x.exchanges,
x.exchangeTos,
x.exchangeDetails,
x.denominations,
])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const ex = await tx.exchanges.get(exchangeBaseurl); const ex = await tx.exchanges.get(exchangeBaseurl);
const dp = ex?.detailsPointer; const dp = ex?.detailsPointer;
@ -656,6 +706,8 @@ async function getExchangeDetailedInfo(
return; return;
} }
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
const denominations: DenominationInfo[] = denominationRecords.map((x) => const denominations: DenominationInfo[] = denominationRecords.map((x) =>
DenominationRecord.toDenomInfo(x), DenominationRecord.toDenomInfo(x),
); );
@ -664,12 +716,7 @@ async function getExchangeDetailedInfo(
info: { info: {
exchangeBaseUrl: ex.baseUrl, exchangeBaseUrl: ex.baseUrl,
currency, currency,
tos: { tos,
acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
currentVersion: exchangeDetails.termsOfServiceLastEtag,
contentType: exchangeDetails.termsOfServiceContentType,
content: exchangeDetails.termsOfServiceText,
},
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri), paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
auditors: exchangeDetails.auditors, auditors: exchangeDetails.auditors,
wireInfo: exchangeDetails.wireInfo, wireInfo: exchangeDetails.wireInfo,