calculate using server

This commit is contained in:
Sebastian 2023-03-31 19:09:41 -03:00
parent 8701ae100e
commit f947c8e549
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
3 changed files with 156 additions and 54 deletions

View File

@ -329,6 +329,14 @@ namespace SandboxBackend {
// where to send cashouts. // where to send cashouts.
cashout_address: string; cashout_address: string;
} }
interface CashoutEstimate {
// Amount that the user will get deducted from their regional
// bank account, according to the 'amount_credit' value.
amount_debit: Amount;
// Amount that the user will receive in their fiat
// bank account, according to 'amount_debit'.
amount_credit: Amount;
}
interface CashoutRequest { interface CashoutRequest {
// Optional subject to associate to the // Optional subject to associate to the
// cashout operation. This data will appear // cashout operation. This data will appear

View File

@ -32,6 +32,7 @@ import {
// FIX default import https://github.com/microsoft/TypeScript/issues/49189 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
import _useSWR, { SWRHook } from "swr"; import _useSWR, { SWRHook } from "swr";
import { AmountJson, Amounts } from "@gnu-taler/taler-util";
const useSWR = _useSWR as unknown as SWRHook; const useSWR = _useSWR as unknown as SWRHook;
export function useAdminAccountAPI(): AdminAccountAPI { export function useAdminAccountAPI(): AdminAccountAPI {
@ -215,6 +216,23 @@ export interface CircuitAccountAPI {
async function getBusinessStatus( async function getBusinessStatus(
request: ReturnType<typeof useApiContext>["request"], request: ReturnType<typeof useApiContext>["request"],
basicAuth: { username: string; password: string }, basicAuth: { username: string; password: string },
): Promise<boolean> {
try {
const url = getInitialBackendBaseURL();
const result = await request<SandboxBackend.Circuit.CircuitAccountData>(
url,
`circuit-api/accounts/${basicAuth.username}`,
{ basicAuth },
);
return result.ok;
} catch (error) {
return false;
}
}
async function getEstimationByCredit(
request: ReturnType<typeof useApiContext>["request"],
basicAuth: { username: string; password: string },
): Promise<boolean> { ): Promise<boolean> {
try { try {
const url = getInitialBackendBaseURL(); const url = getInitialBackendBaseURL();
@ -227,6 +245,93 @@ async function getBusinessStatus(
} }
} }
export type TransferCalculation = {
debit: AmountJson;
credit: AmountJson;
beforeFee: AmountJson;
};
type EstimatorFunction = (
amount: AmountJson,
sellFee: AmountJson,
sellRate: number,
) => Promise<TransferCalculation>;
type CashoutEstimators = {
estimateByCredit: EstimatorFunction;
estimateByDebit: EstimatorFunction;
};
export function useEstimator(): CashoutEstimators {
const { state } = useBackendContext();
const { request } = useApiContext();
const basicAuth =
state.status === "loggedOut"
? undefined
: { username: state.username, password: state.password };
return {
estimateByCredit: async (amount, fee, rate) => {
const zeroBalance = Amounts.zeroOfCurrency(fee.currency);
const zeroFiat = Amounts.zeroOfCurrency(fee.currency);
const zeroCalc = {
debit: zeroBalance,
credit: zeroFiat,
beforeFee: zeroBalance,
};
const url = getInitialBackendBaseURL();
const result = await request<SandboxBackend.Circuit.CashoutEstimate>(
url,
`circuit-api/cashouts/estimates`,
{
basicAuth,
params: {
amount_credit: Amounts.stringify(amount),
},
},
);
// const credit = Amounts.parseOrThrow(result.data.data.amount_credit);
const credit = amount;
const _credit = { ...credit, currency: fee.currency };
const beforeFee = Amounts.sub(_credit, fee).amount;
const debit = Amounts.parseOrThrow(result.data.amount_debit);
return {
debit,
beforeFee,
credit,
};
},
estimateByDebit: async (amount, fee, rate) => {
const zeroBalance = Amounts.zeroOfCurrency(fee.currency);
const zeroFiat = Amounts.zeroOfCurrency(fee.currency);
const zeroCalc = {
debit: zeroBalance,
credit: zeroFiat,
beforeFee: zeroBalance,
};
const url = getInitialBackendBaseURL();
const result = await request<SandboxBackend.Circuit.CashoutEstimate>(
url,
`circuit-api/cashouts/estimates`,
{
basicAuth,
params: {
amount_debit: Amounts.stringify(amount),
},
},
);
const credit = Amounts.parseOrThrow(result.data.amount_credit);
const _credit = { ...credit, currency: fee.currency };
const debit = amount;
const beforeFee = Amounts.sub(_credit, fee).amount;
return {
debit,
beforeFee,
credit,
};
},
};
}
export function useBusinessAccountFlag(): boolean | undefined { export function useBusinessAccountFlag(): boolean | undefined {
const [isBusiness, setIsBusiness] = useState<boolean | undefined>(); const [isBusiness, setIsBusiness] = useState<boolean | undefined>();
const { state } = useBackendContext(); const { state } = useBackendContext();

View File

@ -34,6 +34,7 @@ import { useAccountDetails } from "../hooks/access.js";
import { import {
useCashoutDetails, useCashoutDetails,
useCircuitAccountAPI, useCircuitAccountAPI,
useEstimator,
useRatiosAndFeeConfig, useRatiosAndFeeConfig,
} from "../hooks/circuit.js"; } from "../hooks/circuit.js";
import { import {
@ -230,7 +231,10 @@ function CreateCashout({
const ratiosResult = useRatiosAndFeeConfig(); const ratiosResult = useRatiosAndFeeConfig();
const result = useAccountDetails(account); const result = useAccountDetails(account);
const [error, saveError] = useState<ErrorMessage | undefined>(); const [error, saveError] = useState<ErrorMessage | undefined>();
const {
estimateByCredit: calculateFromCredit,
estimateByDebit: calculateFromDebit,
} = useEstimator();
const [form, setForm] = useState<Partial<FormType>>({ isDebit: true }); const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
const { createCashout } = useCircuitAccountAPI(); const { createCashout } = useCircuitAccountAPI();
@ -256,22 +260,46 @@ function CreateCashout({
if (!sellRate || sellRate < 0) return <div>error rate</div>; if (!sellRate || sellRate < 0) return <div>error rate</div>;
const amount = Amounts.parse(`${balance.currency}:${form.amount}`); const amount = Amounts.parseOrThrow(
`${!form.isDebit ? fiatCurrency : balance.currency}:${
!form.amount ? "0" : form.amount
}`,
);
useEffect(() => { useEffect(() => {
if (!amount) {
setCalc(zeroCalc);
} else {
if (form.isDebit) { if (form.isDebit) {
calculateFromDebit(amount, sellFee, sellRate).then((r) => { calculateFromDebit(amount, sellFee, sellRate)
.then((r) => {
setCalc(r); setCalc(r);
saveError(undefined);
})
.catch((error) => {
saveError(
error instanceof RequestError
? buildRequestErrorMessage(i18n, error.cause)
: {
title: i18n.str`Could not estimate the cashout`,
description: error.message,
},
);
}); });
} else { } else {
calculateFromCredit(amount, sellFee, sellRate).then((r) => { calculateFromCredit(amount, sellFee, sellRate)
.then((r) => {
setCalc(r); setCalc(r);
saveError(undefined);
})
.catch((error) => {
saveError(
error instanceof RequestError
? buildRequestErrorMessage(i18n, error.cause)
: {
title: i18n.str`Could not estimate the cashout`,
description: error.message,
},
);
}); });
} }
}
}, [form.amount, form.isDebit]); }, [form.amount, form.isDebit]);
const balanceAfter = Amounts.sub(balance, calc.debit).amount; const balanceAfter = Amounts.sub(balance, calc.debit).amount;
@ -326,14 +354,10 @@ function CreateCashout({
type="text" type="text"
readonly readonly
class="currency-indicator" class="currency-indicator"
size={ size={amount?.currency.length ?? 0}
!form.isDebit ? fiatCurrency.length : balance.currency.length maxLength={amount?.currency.length ?? 0}
}
maxLength={
!form.isDebit ? fiatCurrency.length : balance.currency.length
}
tabIndex={-1} tabIndex={-1}
value={!form.isDebit ? fiatCurrency : balance.currency} value={amount?.currency ?? ""}
/> />
&nbsp; &nbsp;
<input <input
@ -588,9 +612,7 @@ function CreateCashout({
if (errors) return; if (errors) return;
try { try {
const res = await createCashout({ const res = await createCashout({
amount_credit: `${fiatCurrency}:${Amounts.stringifyValue( amount_credit: Amounts.stringify(calc.credit),
calc.credit,
)}`,
amount_debit: Amounts.stringify(calc.debit), amount_debit: Amounts.stringify(calc.debit),
subject: form.subject, subject: form.subject,
tan_channel: form.channel, tan_channel: form.channel,
@ -842,39 +864,6 @@ function truncate(a: AmountJson): AmountJson {
return Amounts.parseOrThrow(truncated); return Amounts.parseOrThrow(truncated);
} }
type TransferCalculation = {
debit: AmountJson;
credit: AmountJson;
beforeFee: AmountJson;
};
async function calculateFromDebit(
amount: AmountJson,
sellFee: AmountJson,
sellRate: number,
): Promise<TransferCalculation> {
const debit = amount;
const beforeFee = truncate(Amounts.divide(debit, 1 / sellRate));
const credit = Amounts.sub(beforeFee, sellFee).amount;
return { debit, credit, beforeFee };
}
async function calculateFromCredit(
amount: AmountJson,
sellFee: AmountJson,
sellRate: number,
): Promise<TransferCalculation> {
const credit = amount;
const beforeFee = Amounts.add(credit, sellFee).amount;
const debit = truncate(Amounts.divide(beforeFee, sellRate));
return { debit, credit, beforeFee };
}
export function assertUnreachable(x: never): never { export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here"); throw new Error("Didn't expect to get here");
} }