wallet-core: get rid of duplicated withdrawal info API

This commit is contained in:
Florian Dold 2022-10-14 18:40:04 +02:00
parent da9ec5eb16
commit 6acddd6d70
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 158 additions and 214 deletions

View File

@ -68,6 +68,7 @@ import { BackupRecovery } from "./backupTypes.js";
import { PaytoUri } from "./payto.js";
import { TalerErrorCode } from "./taler-error-codes.js";
import { AgeCommitmentProof } from "./talerCrypto.js";
import { VersionMatchResult } from "./libtool-version.js";
/**
* Response for the create reserve request to the wallet.
@ -692,6 +693,7 @@ export interface ExchangeGlobalFees {
signature: string;
}
const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
buildCodecForObject<ExchangeAccount>()
.property("payto_uri", codecForString())
@ -929,6 +931,110 @@ export interface ManualWithdrawalDetails {
* Ways to pay the exchange.
*/
paytoUris: string[];
/**
* If the exchange supports age-restricted coins it will return
* the array of ages.
*/
ageRestrictionOptions?: number[];
}
/**
* Selected denominations withn some extra info.
*/
export interface DenomSelectionState {
totalCoinValue: AmountJson;
totalWithdrawCost: AmountJson;
selectedDenoms: {
denomPubHash: string;
count: number;
}[];
}
/**
* Information about what will happen doing a withdrawal.
*
* Sent to the wallet frontend to be rendered and shown to the user.
*/
export interface ExchangeWithdrawalDetails {
exchangePaytoUris: string[];
/**
* Filtered wire info to send to the bank.
*/
exchangeWireAccounts: string[];
/**
* Selected denominations for withdraw.
*/
selectedDenoms: DenomSelectionState;
/**
* Does the wallet know about an auditor for
* the exchange that the reserve.
*/
isAudited: boolean;
/**
* Did the user already accept the current terms of service for the exchange?
*/
termsOfServiceAccepted: boolean;
/**
* The exchange is trusted directly.
*/
isTrusted: boolean;
/**
* The earliest deposit expiration of the selected coins.
*/
earliestDepositExpiration: TalerProtocolTimestamp;
/**
* Number of currently offered denominations.
*/
numOfferedDenoms: number;
/**
* Public keys of trusted auditors for the currency we're withdrawing.
*/
trustedAuditorPubs: string[];
/**
* Result of checking the wallet's version
* against the exchange's version.
*
* Older exchanges don't return version information.
*/
versionMatch: VersionMatchResult | undefined;
/**
* Libtool-style version string for the exchange or "unknown"
* for older exchanges.
*/
exchangeVersion: string;
/**
* Libtool-style version string for the wallet.
*/
walletVersion: string;
/**
* Amount that will be subtracted from the reserve's balance.
*/
withdrawalAmountRaw: AmountString;
/**
* Amount that will actually be added to the wallet's balance.
*/
withdrawalAmountEffective: AmountString;
/**
* If the exchange supports age-restricted coins it will return
* the array of ages.
*
*/
ageRestrictionOptions?: number[];
}
export interface GetExchangeTosResult {
@ -1142,24 +1248,6 @@ export const codecForForgetKnownBankAccounts =
.property("payto", codecForString())
.build("ForgetKnownBankAccountsRequest");
export interface GetExchangeWithdrawalInfo {
exchangeBaseUrl: string;
amount: AmountJson;
tosAcceptedFormat?: string[];
ageRestricted?: number;
}
export const codecForGetExchangeWithdrawalInfo =
(): Codec<GetExchangeWithdrawalInfo> =>
buildCodecForObject<GetExchangeWithdrawalInfo>()
.property("exchangeBaseUrl", codecForString())
.property("amount", codecForAmountJson())
.property(
"tosAcceptedFormat",
codecOptional(codecForList(codecForString())),
)
.build("GetExchangeWithdrawalInfo");
export interface AbortProposalRequest {
proposalId: string;
}

View File

@ -47,6 +47,7 @@ import {
DenominationInfo,
GlobalFees,
ExchangeGlobalFees,
DenomSelectionState,
} from "@gnu-taler/taler-util";
import { RetryInfo, RetryTags } from "./util/retries.js";
import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
@ -430,8 +431,11 @@ export interface ExchangeDetailsRecord {
/**
* Fees for exchange services
*
* FIXME: Put in separate object store!
*/
globalFees: ExchangeGlobalFees[];
/**
* Signing keys we got from the exchange, can also contain
* older signing keys that are not returned by /keys anymore.
@ -1280,18 +1284,6 @@ export interface WalletBackupConfState {
lastBackupNonce?: string;
}
/**
* Selected denominations withn some extra info.
*/
export interface DenomSelectionState {
totalCoinValue: AmountJson;
totalWithdrawCost: AmountJson;
selectedDenoms: {
denomPubHash: string;
count: number;
}[];
}
export const enum WithdrawalRecordType {
BankManual = "bank-manual",
BankIntegrated = "bank-integrated",

View File

@ -28,6 +28,7 @@ import {
BackupWgType,
codecForContractTerms,
DenomKeyType,
DenomSelectionState,
j2s,
Logger,
PayCoinSelection,
@ -43,7 +44,6 @@ import {
CoinStatus,
DenominationRecord,
DenominationVerificationStatus,
DenomSelectionState,
OperationStatus,
ProposalDownload,
PurchaseStatus,

View File

@ -37,10 +37,12 @@ import {
codecForWithdrawOperationStatusResponse,
codecForWithdrawResponse,
DenomKeyType,
DenomSelectionState,
Duration,
durationFromSpec,
encodeCrock,
ExchangeListItem,
ExchangeWithdrawalDetails,
ExchangeWithdrawRequest,
ForcedDenomSel,
getRandomBytes,
@ -67,9 +69,6 @@ import {
CoinStatus,
DenominationRecord,
DenominationVerificationStatus,
DenomSelectionState,
ExchangeDetailsRecord,
ExchangeRecord,
PlanchetRecord,
WalletStoresV1,
WgInfo,
@ -126,96 +125,6 @@ import {
*/
const logger = new Logger("operations/withdraw.ts");
/**
* Information about what will happen when creating a reserve.
*
* Sent to the wallet frontend to be rendered and shown to the user.
*/
export interface ExchangeWithdrawDetails {
/**
* Exchange that the reserve will be created at.
*
* FIXME: Should be its own record.
*/
exchangeInfo: ExchangeRecord;
exchangeDetails: ExchangeDetailsRecord;
/**
* Filtered wire info to send to the bank.
*/
exchangeWireAccounts: string[];
/**
* Selected denominations for withdraw.
*/
selectedDenoms: DenomSelectionState;
/**
* Does the wallet know about an auditor for
* the exchange that the reserve.
*/
isAudited: boolean;
/**
* Did the user already accept the current terms of service for the exchange?
*/
termsOfServiceAccepted: boolean;
/**
* The exchange is trusted directly.
*/
isTrusted: boolean;
/**
* The earliest deposit expiration of the selected coins.
*/
earliestDepositExpiration: TalerProtocolTimestamp;
/**
* Number of currently offered denominations.
*/
numOfferedDenoms: number;
/**
* Public keys of trusted auditors for the currency we're withdrawing.
*/
trustedAuditorPubs: string[];
/**
* Result of checking the wallet's version
* against the exchange's version.
*
* Older exchanges don't return version information.
*/
versionMatch: VersionMatchResult | undefined;
/**
* Libtool-style version string for the exchange or "unknown"
* for older exchanges.
*/
exchangeVersion: string;
/**
* Libtool-style version string for the wallet.
*/
walletVersion: string;
withdrawalAmountRaw: AmountString;
/**
* Amount that will actually be added to the wallet's balance.
*/
withdrawalAmountEffective: AmountString;
/**
* If the exchange supports age-restricted coins it will return
* the array of ages.
*
*/
ageRestrictionOptions?: number[];
}
/**
* Check if a denom is withdrawable based on the expiration time,
* revocation and offered state.
@ -1280,7 +1189,7 @@ export async function getExchangeWithdrawalInfo(
exchangeBaseUrl: string,
instructedAmount: AmountJson,
ageRestricted: number | undefined,
): Promise<ExchangeWithdrawDetails> {
): Promise<ExchangeWithdrawalDetails> {
const { exchange, exchangeDetails } =
await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl);
await updateWithdrawalDenoms(ws, exchangeBaseUrl);
@ -1378,10 +1287,14 @@ export async function getExchangeWithdrawalInfo(
}
}
const ret: ExchangeWithdrawDetails = {
const paytoUris = exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri);
if (!paytoUris) {
throw Error("exchange is in invalid state");
}
const ret: ExchangeWithdrawalDetails = {
earliestDepositExpiration,
exchangeInfo: exchange,
exchangeDetails,
exchangePaytoUris: paytoUris,
exchangeWireAccounts,
exchangeVersion: exchangeDetails.protocolVersion || "unknown",
isAudited,

View File

@ -47,7 +47,6 @@ import {
codecForForgetKnownBankAccounts,
codecForGetContractTermsDetails,
codecForGetExchangeTosRequest,
codecForGetExchangeWithdrawalInfo,
codecForGetFeeForDeposit,
codecForGetWithdrawalDetailsForAmountRequest,
codecForGetWithdrawalDetailsForUri,
@ -112,7 +111,11 @@ import {
importDb,
WalletStoresV1,
} from "./db.js";
import { applyDevExperiment, maybeInitDevMode, setDevMode } from "./dev-experiments.js";
import {
applyDevExperiment,
maybeInitDevMode,
setDevMode,
} from "./dev-experiments.js";
import { getErrorDetailFromException, TalerError } from "./errors.js";
import {
ActiveLongpollInfo,
@ -248,32 +251,6 @@ const builtinExchanges: string[] = ["https://exchange.demo.taler.net/"];
const logger = new Logger("wallet.ts");
async function getWithdrawalDetailsForAmount(
ws: InternalWalletState,
exchangeBaseUrl: string,
amount: AmountJson,
restrictAge: number | undefined,
): Promise<ManualWithdrawalDetails> {
const wi = await getExchangeWithdrawalInfo(
ws,
exchangeBaseUrl,
amount,
restrictAge,
);
const paytoUris = wi.exchangeDetails.wireInfo.accounts.map(
(x) => x.payto_uri,
);
if (!paytoUris) {
throw Error("exchange is in invalid state");
}
return {
amountRaw: Amounts.stringify(amount),
amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue),
paytoUris,
tosAccepted: wi.termsOfServiceAccepted,
};
}
/**
* Call the right handler for a pending operation without doing
* any special error handling.
@ -1038,16 +1015,6 @@ async function dispatchRequestInternal(
const req = codecForGetWithdrawalDetailsForUri().decode(payload);
return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri);
}
case "getExchangeWithdrawalInfo": {
const req = codecForGetExchangeWithdrawalInfo().decode(payload);
return await getExchangeWithdrawalInfo(
ws,
req.exchangeBaseUrl,
req.amount,
req.ageRestricted,
);
}
case "acceptManualWithdrawal": {
const req = codecForAcceptManualWithdrawalRequet().decode(payload);
const res = await createManualWithdrawal(ws, {
@ -1060,12 +1027,18 @@ async function dispatchRequestInternal(
case "getWithdrawalDetailsForAmount": {
const req =
codecForGetWithdrawalDetailsForAmountRequest().decode(payload);
return await getWithdrawalDetailsForAmount(
const wi = await getExchangeWithdrawalInfo(
ws,
req.exchangeBaseUrl,
Amounts.parseOrThrow(req.amount),
req.restrictAge,
);
return {
amountRaw: req.amount,
amountEffective: Amounts.stringify(wi.selectedDenoms.totalCoinValue),
paytoUris: wi.exchangePaytoUris,
tosAccepted: wi.termsOfServiceAccepted,
};
}
case "getBalances": {
return await getBalances(ws);
@ -1255,7 +1228,7 @@ async function dispatchRequestInternal(
case "withdrawFakebank": {
const req = codecForWithdrawFakebankRequest().decode(payload);
const amount = Amounts.parseOrThrow(req.amount);
const details = await getWithdrawalDetailsForAmount(
const details = await getExchangeWithdrawalInfo(
ws,
req.exchange,
amount,
@ -1265,7 +1238,7 @@ async function dispatchRequestInternal(
amount: amount,
exchangeBaseUrl: req.exchange,
});
const paytoUri = details.paytoUris[0];
const paytoUri = details.exchangePaytoUris[0];
const pt = parsePaytoUri(paytoUri);
if (!pt) {
throw Error("failed to parse payto URI");

View File

@ -182,16 +182,15 @@ function exchangeSelectionState(
* about the withdrawal
*/
const amountHook = useAsyncAsHook(async () => {
const info = await api.getExchangeWithdrawalInfo({
const info = await api.getWithdrawalDetailsForAmount({
exchangeBaseUrl: currentExchange.exchangeBaseUrl,
amount: chosenAmount,
tosAcceptedFormat: ["text/xml"],
ageRestricted,
amount: Amounts.stringify(chosenAmount),
restrictAge: ageRestricted,
});
const withdrawAmount = {
raw: Amounts.parseOrThrow(info.withdrawalAmountRaw),
effective: Amounts.parseOrThrow(info.withdrawalAmountEffective),
raw: Amounts.parseOrThrow(info.amountRaw),
effective: Amounts.parseOrThrow(info.amountEffective),
};
return {

View File

@ -22,14 +22,11 @@
import {
Amounts,
ExchangeFullDetails,
ExchangeListItem,
GetExchangeTosResult,
} from "@gnu-taler/taler-util";
import { ExchangeWithdrawDetails } from "@gnu-taler/taler-wallet-core";
import { expect } from "chai";
import { mountHook } from "../../test-utils.js";
import { useComponentStateFromURI } from "./state.js";
import * as wxApi from "../../wxApi.js";
const exchanges: ExchangeFullDetails[] = [
{
@ -162,20 +159,11 @@ describe("Withdraw CTA states", () => {
},
{
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({
talerWithdrawUri,
}: any): Promise<ExchangeWithdrawDetails> =>
({
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
} as Partial<ExchangeWithdrawDetails> as ExchangeWithdrawDetails),
getExchangeWithdrawalInfo:
async (): Promise<ExchangeWithdrawDetails> =>
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
}),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",
@ -255,19 +243,12 @@ describe("Withdraw CTA states", () => {
},
},
{
listExchanges: async () => listExchangesResponse,
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) =>
({
listExchanges: async () => ({ exchanges }),
getWithdrawalDetailsForUri: async ({ talerWithdrawUri }: any) => ({
amount: "ARS:2",
possibleExchanges: exchanges,
defaultExchangeBaseUrl: exchanges[0].exchangeBaseUrl,
} as Partial<ExchangeWithdrawDetails> as ExchangeWithdrawDetails),
getExchangeWithdrawalInfo:
async (): Promise<ExchangeWithdrawDetails> =>
({
withdrawalAmountRaw: "ARS:2",
withdrawalAmountEffective: "ARS:2",
} as any),
}),
getExchangeTos: async (): Promise<GetExchangeTosResult> => ({
contentType: "text",
content: "just accept",

View File

@ -51,8 +51,8 @@ import {
ExchangesListResponse,
ForgetKnownBankAccountsRequest,
GetExchangeTosResult,
GetExchangeWithdrawalInfo,
GetFeeForDepositRequest,
GetWithdrawalDetailsForAmountRequest,
GetWithdrawalDetailsForUriRequest,
InitiatePeerPullPaymentRequest,
InitiatePeerPullPaymentResponse,
@ -60,6 +60,7 @@ import {
InitiatePeerPushPaymentResponse,
KnownBankAccounts,
Logger,
ManualWithdrawalDetails,
NotificationType,
PaytoUri,
PrepareDepositRequest,
@ -81,7 +82,6 @@ import {
import {
AddBackupProviderRequest,
BackupInfo,
ExchangeWithdrawDetails,
PendingOperationsResponse,
RemoveBackupProviderRequest,
TalerError,
@ -459,14 +459,12 @@ export function getWithdrawalDetailsForUri(
return callBackend("getWithdrawalDetailsForUri", req);
}
/**
* Get diagnostics information
*/
export function getExchangeWithdrawalInfo(
req: GetExchangeWithdrawalInfo,
): Promise<ExchangeWithdrawDetails> {
return callBackend("getExchangeWithdrawalInfo", req);
export function getWithdrawalDetailsForAmount(
req: GetWithdrawalDetailsForAmountRequest,
): Promise<ManualWithdrawalDetails> {
return callBackend("getWithdrawalDetailsForAmount", req);
}
export function getExchangeTos(
exchangeBaseUrl: string,
acceptedFormat: string[],