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 { PaytoUri } from "./payto.js";
import { TalerErrorCode } from "./taler-error-codes.js"; import { TalerErrorCode } from "./taler-error-codes.js";
import { AgeCommitmentProof } from "./talerCrypto.js"; import { AgeCommitmentProof } from "./talerCrypto.js";
import { VersionMatchResult } from "./libtool-version.js";
/** /**
* Response for the create reserve request to the wallet. * Response for the create reserve request to the wallet.
@ -692,6 +693,7 @@ export interface ExchangeGlobalFees {
signature: string; signature: string;
} }
const codecForExchangeAccount = (): Codec<ExchangeAccount> => const codecForExchangeAccount = (): Codec<ExchangeAccount> =>
buildCodecForObject<ExchangeAccount>() buildCodecForObject<ExchangeAccount>()
.property("payto_uri", codecForString()) .property("payto_uri", codecForString())
@ -929,6 +931,110 @@ export interface ManualWithdrawalDetails {
* Ways to pay the exchange. * Ways to pay the exchange.
*/ */
paytoUris: string[]; 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 { export interface GetExchangeTosResult {
@ -1142,24 +1248,6 @@ export const codecForForgetKnownBankAccounts =
.property("payto", codecForString()) .property("payto", codecForString())
.build("ForgetKnownBankAccountsRequest"); .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 { export interface AbortProposalRequest {
proposalId: string; proposalId: string;
} }

View File

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

View File

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

View File

@ -37,10 +37,12 @@ import {
codecForWithdrawOperationStatusResponse, codecForWithdrawOperationStatusResponse,
codecForWithdrawResponse, codecForWithdrawResponse,
DenomKeyType, DenomKeyType,
DenomSelectionState,
Duration, Duration,
durationFromSpec, durationFromSpec,
encodeCrock, encodeCrock,
ExchangeListItem, ExchangeListItem,
ExchangeWithdrawalDetails,
ExchangeWithdrawRequest, ExchangeWithdrawRequest,
ForcedDenomSel, ForcedDenomSel,
getRandomBytes, getRandomBytes,
@ -67,9 +69,6 @@ import {
CoinStatus, CoinStatus,
DenominationRecord, DenominationRecord,
DenominationVerificationStatus, DenominationVerificationStatus,
DenomSelectionState,
ExchangeDetailsRecord,
ExchangeRecord,
PlanchetRecord, PlanchetRecord,
WalletStoresV1, WalletStoresV1,
WgInfo, WgInfo,
@ -126,96 +125,6 @@ import {
*/ */
const logger = new Logger("operations/withdraw.ts"); 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, * Check if a denom is withdrawable based on the expiration time,
* revocation and offered state. * revocation and offered state.
@ -1280,7 +1189,7 @@ export async function getExchangeWithdrawalInfo(
exchangeBaseUrl: string, exchangeBaseUrl: string,
instructedAmount: AmountJson, instructedAmount: AmountJson,
ageRestricted: number | undefined, ageRestricted: number | undefined,
): Promise<ExchangeWithdrawDetails> { ): Promise<ExchangeWithdrawalDetails> {
const { exchange, exchangeDetails } = const { exchange, exchangeDetails } =
await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl); await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl);
await updateWithdrawalDenoms(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, earliestDepositExpiration,
exchangeInfo: exchange, exchangePaytoUris: paytoUris,
exchangeDetails,
exchangeWireAccounts, exchangeWireAccounts,
exchangeVersion: exchangeDetails.protocolVersion || "unknown", exchangeVersion: exchangeDetails.protocolVersion || "unknown",
isAudited, isAudited,

View File

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

View File

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

View File

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

View File

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