calculate using server
This commit is contained in:
parent
8701ae100e
commit
f947c8e549
8
packages/demobank-ui/src/declaration.d.ts
vendored
8
packages/demobank-ui/src/declaration.d.ts
vendored
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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,21 +260,45 @@ 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) {
|
if (form.isDebit) {
|
||||||
setCalc(zeroCalc);
|
calculateFromDebit(amount, sellFee, sellRate)
|
||||||
|
.then((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 {
|
||||||
if (form.isDebit) {
|
calculateFromCredit(amount, sellFee, sellRate)
|
||||||
calculateFromDebit(amount, sellFee, sellRate).then((r) => {
|
.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 {
|
|
||||||
calculateFromCredit(amount, sellFee, sellRate).then((r) => {
|
|
||||||
setCalc(r);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [form.amount, form.isDebit]);
|
}, [form.amount, form.isDebit]);
|
||||||
|
|
||||||
@ -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 ?? ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<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");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user