wallet-core, wallet-cli: add status to exchange list, add detail query to CLI
This commit is contained in:
parent
d98d49aa58
commit
fbb7dd9e7e
@ -905,13 +905,27 @@ export enum ExchangeTosStatus {
|
||||
Accepted = "accepted",
|
||||
Changed = "changed",
|
||||
NotFound = "not-found",
|
||||
Unknown = "unknown",
|
||||
}
|
||||
|
||||
export enum ExchangeEntryStatus {
|
||||
Unknown = "unknown",
|
||||
Outdated = "outdated",
|
||||
Ok = "ok",
|
||||
}
|
||||
|
||||
// FIXME: This should probably include some error status.
|
||||
export interface ExchangeListItem {
|
||||
exchangeBaseUrl: string;
|
||||
currency: string;
|
||||
currency: string | undefined;
|
||||
paytoUris: string[];
|
||||
tosStatus: ExchangeTosStatus;
|
||||
exchangeStatus: ExchangeEntryStatus;
|
||||
/**
|
||||
* Permanently added to the wallet, as opposed to just
|
||||
* temporarily queried.
|
||||
*/
|
||||
permanent: boolean;
|
||||
}
|
||||
|
||||
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
|
||||
@ -984,6 +998,8 @@ export const codecForExchangeListItem = (): Codec<ExchangeListItem> =>
|
||||
.property("exchangeBaseUrl", codecForString())
|
||||
.property("paytoUris", codecForList(codecForString()))
|
||||
.property("tosStatus", codecForAny())
|
||||
.property("exchangeStatus", codecForAny())
|
||||
.property("permanent", codecForBoolean())
|
||||
.build("ExchangeListItem");
|
||||
|
||||
export const codecForExchangesListResponse = (): Codec<ExchangesListResponse> =>
|
||||
|
@ -549,6 +549,25 @@ exchangesCli
|
||||
});
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesShowCmd", "show", {
|
||||
help: "Show exchange details",
|
||||
})
|
||||
.requiredArgument("url", clk.STRING, {
|
||||
help: "Base URL of the exchange.",
|
||||
})
|
||||
.action(async (args) => {
|
||||
await withWallet(args, async (wallet) => {
|
||||
const resp = await wallet.client.call(
|
||||
WalletApiOperation.GetExchangeDetailedInfo,
|
||||
{
|
||||
exchangeBaseUrl: args.exchangesShowCmd.url,
|
||||
},
|
||||
);
|
||||
console.log(JSON.stringify(resp, undefined, 2));
|
||||
});
|
||||
});
|
||||
|
||||
exchangesCli
|
||||
.subcommand("exchangesAddCmd", "add", {
|
||||
help: "Add an exchange by base URL.",
|
||||
|
@ -17,45 +17,42 @@
|
||||
/**
|
||||
* Imports.
|
||||
*/
|
||||
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
||||
import {
|
||||
describeStore,
|
||||
describeContents,
|
||||
describeIndex,
|
||||
} from "./util/query.js";
|
||||
import {
|
||||
AgeCommitmentProof,
|
||||
AmountJson,
|
||||
AmountString,
|
||||
ExchangeAuditor,
|
||||
CoinDepositPermission,
|
||||
CoinEnvelope,
|
||||
CoinRefreshRequest,
|
||||
CoinStatus,
|
||||
ContractTerms,
|
||||
DenominationInfo,
|
||||
DenominationPubKey,
|
||||
ExchangeSignKeyJson,
|
||||
DenomSelectionState,
|
||||
EddsaPublicKeyString,
|
||||
EddsaSignatureString,
|
||||
ExchangeAuditor,
|
||||
ExchangeGlobalFees,
|
||||
InternationalizedString,
|
||||
Location,
|
||||
MerchantInfo,
|
||||
PayCoinSelection,
|
||||
PeerContractTerms,
|
||||
Product,
|
||||
RefreshReason,
|
||||
TalerErrorDetail,
|
||||
UnblindedSignature,
|
||||
CoinEnvelope,
|
||||
TalerProtocolTimestamp,
|
||||
TalerProtocolDuration,
|
||||
AgeCommitmentProof,
|
||||
PayCoinSelection,
|
||||
PeerContractTerms,
|
||||
Location,
|
||||
WireInfo,
|
||||
DenominationInfo,
|
||||
GlobalFees,
|
||||
ExchangeGlobalFees,
|
||||
DenomSelectionState,
|
||||
TalerProtocolTimestamp,
|
||||
TransactionIdStr,
|
||||
CoinRefreshRequest,
|
||||
CoinStatus,
|
||||
EddsaPublicKeyString,
|
||||
EddsaSignatureString,
|
||||
UnblindedSignature,
|
||||
WireInfo,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import {
|
||||
describeContents,
|
||||
describeIndex,
|
||||
describeStore,
|
||||
} from "./util/query.js";
|
||||
import { RetryInfo, RetryTags } from "./util/retries.js";
|
||||
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
|
||||
|
||||
/**
|
||||
* This file contains the database schema of the Taler wallet together
|
||||
@ -354,8 +351,6 @@ export interface DenominationRecord {
|
||||
* Was this denomination still offered by the exchange the last time
|
||||
* we checked?
|
||||
* Only false when the exchange redacts a previously published denomination.
|
||||
*
|
||||
* FIXME: Consider rolling this and isRevoked into some bitfield?
|
||||
*/
|
||||
isOffered: boolean;
|
||||
|
||||
@ -526,6 +521,8 @@ export interface ExchangeRecord {
|
||||
* Should usually not change. Only changes when the
|
||||
* exchange advertises a different master public key and/or
|
||||
* currency.
|
||||
*
|
||||
* FIXME: Use a rowId here?
|
||||
*/
|
||||
detailsPointer: ExchangeDetailsPointer | undefined;
|
||||
|
||||
@ -1168,8 +1165,6 @@ export interface PurchaseRecord {
|
||||
/**
|
||||
* Timestamp of the first time that sending a payment to the merchant
|
||||
* for this purchase was successful.
|
||||
*
|
||||
* FIXME: Does this need to be a timestamp, doesn't boolean suffice?
|
||||
*/
|
||||
timestampFirstSuccessfulPay: TalerProtocolTimestamp | undefined;
|
||||
|
||||
@ -1369,6 +1364,8 @@ export interface WithdrawalGroupRecord {
|
||||
/**
|
||||
* Wire information (as payto URI) for the bank account that
|
||||
* transferred funds for this reserve.
|
||||
*
|
||||
* FIXME: Doesn't this belong to the bankAccounts object store?
|
||||
*/
|
||||
senderWire?: string;
|
||||
|
||||
@ -1604,7 +1601,7 @@ export interface GhostDepositGroupRecord {
|
||||
|
||||
export interface TombstoneRecord {
|
||||
/**
|
||||
* Tombstone ID, with the syntax "<type>:<key>".
|
||||
* Tombstone ID, with the syntax "tmb:<type>:<key>".
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
@ -22,6 +22,8 @@ import {
|
||||
Amounts,
|
||||
CoinRefreshRequest,
|
||||
CoinStatus,
|
||||
ExchangeEntryStatus,
|
||||
ExchangeListItem,
|
||||
ExchangeTosStatus,
|
||||
j2s,
|
||||
Logger,
|
||||
@ -32,7 +34,12 @@ import {
|
||||
TransactionIdStr,
|
||||
TransactionType,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { WalletStoresV1, CoinRecord, ExchangeDetailsRecord } from "../db.js";
|
||||
import {
|
||||
WalletStoresV1,
|
||||
CoinRecord,
|
||||
ExchangeDetailsRecord,
|
||||
ExchangeRecord,
|
||||
} from "../db.js";
|
||||
import { makeErrorDetail, TalerError } from "../errors.js";
|
||||
import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
|
||||
@ -320,3 +327,29 @@ export function getExchangeTosStatus(
|
||||
}
|
||||
return ExchangeTosStatus.Changed;
|
||||
}
|
||||
|
||||
export function makeExchangeListItem(
|
||||
r: ExchangeRecord,
|
||||
exchangeDetails: ExchangeDetailsRecord | undefined,
|
||||
): ExchangeListItem {
|
||||
if (!exchangeDetails) {
|
||||
return {
|
||||
exchangeBaseUrl: r.baseUrl,
|
||||
currency: undefined,
|
||||
tosStatus: ExchangeTosStatus.Unknown,
|
||||
paytoUris: [],
|
||||
exchangeStatus: ExchangeEntryStatus.Unknown,
|
||||
permanent: r.permanent,
|
||||
};
|
||||
}
|
||||
let exchangeStatus;
|
||||
exchangeStatus = ExchangeEntryStatus.Ok;
|
||||
return {
|
||||
exchangeBaseUrl: r.baseUrl,
|
||||
currency: exchangeDetails.currency,
|
||||
tosStatus: getExchangeTosStatus(exchangeDetails),
|
||||
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
||||
exchangeStatus,
|
||||
permanent: r.permanent,
|
||||
};
|
||||
}
|
||||
|
@ -705,8 +705,6 @@ export async function updateExchangeFromUrlHandler(
|
||||
};
|
||||
}
|
||||
await tx.exchanges.put(r);
|
||||
logger.info(`existing details ${j2s(existingDetails)}`);
|
||||
logger.info(`inserting new details ${j2s(newDetails)}`);
|
||||
const drRowId = await tx.exchangeDetails.put(newDetails);
|
||||
checkDbInvariant(typeof drRowId.key === "number");
|
||||
|
||||
|
@ -85,6 +85,7 @@ import { InternalWalletState } from "../internal-wallet-state.js";
|
||||
import {
|
||||
getExchangeTosStatus,
|
||||
makeCoinAvailable,
|
||||
makeExchangeListItem,
|
||||
runOperationWithErrorReporting,
|
||||
} from "../operations/common.js";
|
||||
import { walletCoreDebugFlags } from "../util/debugFlags.js";
|
||||
@ -1367,18 +1368,7 @@ export async function getWithdrawalDetailsForUri(
|
||||
.iter(r.baseUrl)
|
||||
.toArray();
|
||||
if (exchangeDetails && denominations) {
|
||||
const tosRecord = await tx.exchangeTos.get([
|
||||
exchangeDetails.exchangeBaseUrl,
|
||||
exchangeDetails.tosCurrentEtag,
|
||||
]);
|
||||
exchanges.push({
|
||||
exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
|
||||
currency: exchangeDetails.currency,
|
||||
paytoUris: exchangeDetails.wireInfo.accounts.map(
|
||||
(x) => x.payto_uri,
|
||||
),
|
||||
tosStatus: getExchangeTosStatus(exchangeDetails),
|
||||
});
|
||||
exchanges.push(makeExchangeListItem(r, exchangeDetails));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -49,7 +49,6 @@ export enum OperationAttemptResultType {
|
||||
Longpoll = "longpoll",
|
||||
}
|
||||
|
||||
// FIXME: not part of DB!
|
||||
export type OperationAttemptResult<TSuccess = unknown, TPending = unknown> =
|
||||
| OperationAttemptFinishedResult<TSuccess>
|
||||
| OperationAttemptErrorResult
|
||||
|
@ -48,6 +48,7 @@ import {
|
||||
CreateDepositGroupRequest,
|
||||
CreateDepositGroupResponse,
|
||||
DeleteTransactionRequest,
|
||||
ExchangeDetailedResponse,
|
||||
ExchangesListResponse,
|
||||
ForceRefreshRequest,
|
||||
GetExchangeTosRequest,
|
||||
@ -109,6 +110,7 @@ export enum WalletApiOperation {
|
||||
ApplyRefund = "applyRefund",
|
||||
AcceptBankIntegratedWithdrawal = "acceptBankIntegratedWithdrawal",
|
||||
GetExchangeTos = "getExchangeTos",
|
||||
GetExchangeDetailedInfo = "getExchangeDetailedInfo",
|
||||
RetryPendingNow = "retryPendingNow",
|
||||
AbortFailedPayWithRefund = "abortFailedPayWithRefund",
|
||||
ConfirmPay = "confirmPay",
|
||||
@ -333,6 +335,15 @@ export type GetExchangeTosOp = {
|
||||
response: GetExchangeTosResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current terms of a service of an exchange.
|
||||
*/
|
||||
export type GetExchangeDetailedInfoOp = {
|
||||
op: WalletApiOperation.GetExchangeDetailedInfo;
|
||||
request: AddExchangeRequest;
|
||||
response: ExchangeDetailedResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* List currencies known to the wallet.
|
||||
*/
|
||||
@ -661,6 +672,7 @@ export type WalletOperations = {
|
||||
[WalletApiOperation.AddExchange]: AddExchangeOp;
|
||||
[WalletApiOperation.SetExchangeTosAccepted]: SetExchangeTosAcceptedOp;
|
||||
[WalletApiOperation.GetExchangeTos]: GetExchangeTosOp;
|
||||
[WalletApiOperation.GetExchangeDetailedInfo]: GetExchangeDetailedInfoOp;
|
||||
[WalletApiOperation.TrackDepositGroup]: TrackDepositGroupOp;
|
||||
[WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
|
||||
[WalletApiOperation.SetWalletDeviceId]: SetWalletDeviceIdOp;
|
||||
|
@ -97,6 +97,8 @@ import {
|
||||
ExchangeTosStatusDetails,
|
||||
CoinRefreshRequest,
|
||||
CoinStatus,
|
||||
ExchangeEntryStatus,
|
||||
ExchangeTosStatus,
|
||||
} from "@gnu-taler/taler-util";
|
||||
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
|
||||
import {
|
||||
@ -146,7 +148,11 @@ import {
|
||||
} from "./operations/backup/index.js";
|
||||
import { setWalletDeviceId } from "./operations/backup/state.js";
|
||||
import { getBalances } from "./operations/balance.js";
|
||||
import { getExchangeTosStatus, runOperationWithErrorReporting } from "./operations/common.js";
|
||||
import {
|
||||
getExchangeTosStatus,
|
||||
makeExchangeListItem,
|
||||
runOperationWithErrorReporting,
|
||||
} from "./operations/common.js";
|
||||
import {
|
||||
createDepositGroup,
|
||||
getFeeForDeposit,
|
||||
@ -645,32 +651,8 @@ async function getExchanges(
|
||||
.runReadOnly(async (tx) => {
|
||||
const exchangeRecords = await tx.exchanges.iter().toArray();
|
||||
for (const r of exchangeRecords) {
|
||||
const dp = r.detailsPointer;
|
||||
if (!dp) {
|
||||
continue;
|
||||
}
|
||||
const { currency } = dp;
|
||||
const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
|
||||
if (!exchangeDetails) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const denominations = await tx.denominations.indexes.byExchangeBaseUrl
|
||||
.iter(r.baseUrl)
|
||||
.toArray();
|
||||
|
||||
if (!denominations) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
|
||||
|
||||
exchanges.push({
|
||||
exchangeBaseUrl: r.baseUrl,
|
||||
currency,
|
||||
tosStatus: getExchangeTosStatus(exchangeDetails),
|
||||
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
|
||||
});
|
||||
exchanges.push(makeExchangeListItem(r, exchangeDetails));
|
||||
}
|
||||
});
|
||||
return { exchanges };
|
||||
|
@ -185,7 +185,11 @@ export function SelectCurrency({
|
||||
);
|
||||
}
|
||||
const list: Record<string, string> = {};
|
||||
hook.response.exchanges.forEach((e) => (list[e.currency] = e.currency));
|
||||
hook.response.exchanges.forEach((e) => {
|
||||
if (e.currency) {
|
||||
list[e.currency] = e.currency;
|
||||
}
|
||||
});
|
||||
list[""] = "Select a currency";
|
||||
return <SelectCurrencyView onChange={onChange} list={list} />;
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ export function ManualWithdrawPage({ amount, onCancel }: Props): VNode {
|
||||
const exchangeList = state.response.exchanges.reduce(
|
||||
(p, c) => ({
|
||||
...p,
|
||||
[c.exchangeBaseUrl]: c.currency,
|
||||
[c.exchangeBaseUrl]: c.currency || "??",
|
||||
}),
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
@ -204,6 +204,14 @@ export function SettingsView({
|
||||
<i18n.Translate>not accepted</i18n.Translate>
|
||||
</DestructiveText>
|
||||
);
|
||||
case ExchangeTosStatus.Unknown:
|
||||
return (
|
||||
<DestructiveText>
|
||||
<i18n.Translate>
|
||||
unknown (exchange status should be updated)
|
||||
</i18n.Translate>
|
||||
</DestructiveText>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user