Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added fee estimator component to gui #944

Merged
merged 7 commits into from
Aug 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions packages/api-react/src/services/fullNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Block, BlockRecord, BlockHeader, BlockchainState, FullNodeConnecti
import onCacheEntryAddedInvalidate from '../utils/onCacheEntryAddedInvalidate';
import api, { baseQuery } from '../api';

const apiWithTag = api.enhanceEndpoints({addTagTypes: ['BlockchainState', 'FullNodeConnections']})
const apiWithTag = api.enhanceEndpoints({addTagTypes: ['BlockchainState', 'FeeEstimate', 'FullNodeConnections']})

export const fullNodeApi = apiWithTag.injectEndpoints({
endpoints: (build) => ({
Expand All @@ -16,7 +16,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
transformResponse: (response: any) => response?.success,
}),

getBlockRecords: build.query<BlockRecord[], {
getBlockRecords: build.query<BlockRecord[], {
start?: number;
end?: number;
}>({
Expand Down Expand Up @@ -64,7 +64,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
? [
...connections.map(({ nodeId }) => ({ type: 'FullNodeConnections', id: nodeId } as const)),
{ type: 'FullNodeConnections', id: 'LIST' },
]
]
: [{ type: 'FullNodeConnections', id: 'LIST' }],
onCacheEntryAdded: onCacheEntryAddedInvalidate(baseQuery, [{
command: 'onConnections',
Expand All @@ -78,7 +78,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
},
}]),
}),
openFullNodeConnection: build.mutation<FullNodeConnection, {
openFullNodeConnection: build.mutation<FullNodeConnection, {
host: string;
port: number;
}>({
Expand All @@ -89,7 +89,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
}),
invalidatesTags: [{ type: 'FullNodeConnections', id: 'LIST' }],
}),
closeFullNodeConnection: build.mutation<FullNodeConnection, {
closeFullNodeConnection: build.mutation<FullNodeConnection, {
nodeId: string;
}>({
query: ({ nodeId }) => ({
Expand All @@ -99,7 +99,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
}),
invalidatesTags: (_result, _error, { nodeId }) => [{ type: 'FullNodeConnections', id: 'LIST' }, { type: 'FullNodeConnections', id: nodeId }],
}),
getBlock: build.query<Block, {
getBlock: build.query<Block, {
headerHash: string;
}>({
query: ({ headerHash }) => ({
Expand All @@ -109,7 +109,7 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
}),
transformResponse: (response: any) => response?.block,
}),
getBlockRecord: build.query<BlockRecord, {
getBlockRecord: build.query<BlockRecord, {
headerHash: string;
}>({
query: ({ headerHash }) => ({
Expand All @@ -119,10 +119,24 @@ export const fullNodeApi = apiWithTag.injectEndpoints({
}),
transformResponse: (response: any) => response?.blockRecord,
}),
getFeeEstimate: build.query<string, {
targetTimes: number[];
cost: number;
}>({
query: ({
targetTimes,
cost,
}) => ({
command: 'getFeeEstimate',
service: FullNode,
args: [targetTimes, cost],
}),
providesTags: [{ type: 'FeeEstimate' }],
}),
}),
});

export const {
export const {
useFullNodePingQuery,
useGetBlockRecordsQuery,
useGetUnfinishedBlockHeadersQuery,
Expand All @@ -132,4 +146,5 @@ export const {
useCloseFullNodeConnectionMutation,
useGetBlockQuery,
useGetBlockRecordQuery,
useGetFeeEstimateQuery,
} = fullNodeApi;
1 change: 1 addition & 0 deletions packages/api-react/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const {
useCloseFullNodeConnectionMutation,
useGetBlockQuery,
useGetBlockRecordQuery,
useGetFeeEstimateQuery,
} = fullNode;

// wallet hooks
Expand Down
7 changes: 7 additions & 0 deletions packages/api/src/services/FullNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ export default class FullNode extends Service {
});
}

async getFeeEstimate(targetTimes: number[], cost: number) {
return this.command('get_fee_estimate', {
targetTimes,
cost,
});
}

onBlockchainState(
callback: (data: any, message: Message) => void,
processData?: (data: any) => any,
Expand Down
201 changes: 201 additions & 0 deletions packages/core/src/components/EstimatedFee/EstimatedFee.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import React from 'react';
import { get } from 'lodash';
import { Controller, useFormContext } from 'react-hook-form';
import { Trans } from '@lingui/macro';
import { useGetFeeEstimateQuery } from '@chia/api-react';
import {
Fee,
Flex,
mojoToChiaLocaleString,
useLocale,
} from '@chia/core';
import {
FormControl,
IconButton,
InputLabel,
MenuItem,
Select as MaterialSelect,
SelectProps,
Typography,
} from '@mui/material';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import useMode from '../../hooks/useMode';
import Mode from '../../constants/Mode';

type Props = SelectProps & {
hideError?: boolean;
name: string;
};

function Select(props: Props) {
const { name: controllerName, value: controllerValue, onTypeChange, children, ...rest } = props;
const { control, errors, setValue } = useFormContext();
const errorMessage = get(errors, controllerName);

return (
<Controller
name={controllerName}
control={control}
render={({ field: { onChange, onBlur, value, name, ref } }) => (
<MaterialSelect
onChange={(event, ...args) => {
onChange(event, ...args);
if (props.onChange) {
props.onChange(event, ...args);
}
if (event.target.value == "custom") {
onTypeChange("custom");
setValue(controllerName, '');
} else {
onTypeChange("dropdown")
}
}}
onBlur={onBlur}
value={value}
name={name}
ref={ref}
error={!!errorMessage}
{...rest}
>
{children}
</MaterialSelect>
)}
/>
);
}

export default function EstimatedFee(props: FeeProps) {
const { name, txType, required, ...rest } = props;
const { data: ests, isLoading: isFeeLoading, error } = useGetFeeEstimateQuery({"targetTimes": [60, 120, 300], "cost": 1});
const [estList, setEstList] = React.useState([]);
const [inputType, setInputType] = React.useState("dropdown");
const mode = useMode();
const [selectOpen, setSelectOpen] = React.useState(false);
const [locale] = useLocale();

const maxBlockCostCLVM = 11000000000;
const offersAcceptsPerBlock = 500;

const txCostEstimates = {
walletSendXCH: Math.floor(maxBlockCostCLVM / 1170),
createOffer: Math.floor(maxBlockCostCLVM / offersAcceptsPerBlock),
sellNFT: Math.floor(maxBlockCostCLVM / 92),
createPoolingWallet: Math.floor(maxBlockCostCLVM / 462) // JOIN_POOL in GUI = create pooling wallet
}

const multiplier = txCostEstimates[txType];

function formatEst(number, multiplier, locale) {
let num = (Math.round(number * multiplier * (10**(-4)))) * (10**(4));
let formatNum = mojoToChiaLocaleString(num, locale);
return (formatNum);
}

if (!isFeeLoading && ests && estList?.length == 0) {
const estimateList = ests.estimates;
const targetTimes = ests.targetTimes;
if (estimateList[0] == 0 && estimateList[1] == 0 && estimateList[2] == 0) {
setInputType("classic");
}
const est0 = formatEst(estimateList[0], multiplier, locale);
const est1 = formatEst(estimateList[1], multiplier, locale);
const est2 = formatEst(estimateList[2], multiplier, locale);
setEstList(current => [...current, { time: "Likely in " + targetTimes[0] + " seconds", estimate: est0 }]);
setEstList(current => [...current, { time: "Likely in " + (targetTimes[1] / 60) + " minutes", estimate: est1 }]);
setEstList(current => [...current, { time: "Likely over " + (targetTimes[2] / 60) + " minutes", estimate: est2 }]);
}

const handleSelectOpen = () => {
setSelectOpen(true);
};

const handleSelectClose = () => {
setSelectOpen(false);
};

function showSelect() {
return (
<div>
<InputLabel required={required} color="secondary">Fee</InputLabel>
<Select name={name} onTypeChange={setInputType} open={selectOpen} onOpen={handleSelectOpen} onClose={handleSelectClose} {...rest}>
{estList.map((option) => (
<MenuItem
value={String(option.estimate)}
key={option.time}
>
<Flex flexDirection="row" flexGrow={1} justifyContent="space-between" alignItems="center">
<Flex>
<Trans>{option.estimate} TXCH</Trans>
</Flex>
<Flex alignSelf="center">
<Trans><Typography color="textSecondary" fontSize="small">{option.time}</Typography></Trans>
</Flex>
</Flex>
</MenuItem>
))}
<MenuItem
value="custom"
key="custom"
>
Enter a custom fee...
</MenuItem>
</Select>
</div>
)
}

function showInput() {
function showDropdown() {
setSelectOpen(true);
setInputType("dropdown");
}

return (
<Flex flexDirection="row">
<Flex flexGrow={1}>
<Fee
name={name}
type="text"
variant="filled"
label={<Trans>Fee</Trans>}
fullWidth
required={required}
autoFocus
color="secondary"
/>
</Flex>
<Flex alignSelf="center">
<IconButton onClick={showDropdown}>
<ArrowDropDownIcon />
</IconButton>
</Flex>
</Flex>
)
}

if (!error && (mode[0] === Mode.FARMING) && (inputType !== "classic")) {
return (
<Flex>
<FormControl variant="filled" fullWidth>
{inputType === "dropdown" ? showSelect() : showInput()}
</FormControl>
</Flex>
);
} else {
return (
<Flex>
<FormControl variant="filled" fullWidth>
<Fee
name={name}
type="text"
variant="filled"
label={<Trans>Fee</Trans>}
fullWidth
required={required}
color="secondary"
/>
</FormControl>
</Flex>
)
}
};
1 change: 1 addition & 0 deletions packages/core/src/components/EstimatedFee/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './EstimatedFee';
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export { default as Checkbox } from './Checkbox';
export { default as DialogActions } from './DialogActions';
export { default as Dropzone } from './Dropzone';
export { default as Fee } from './Fee';
export { default as EstimatedFee } from './EstimatedFee';
export { default as Heading } from './Heading';
export { default as ConfirmDialog } from './ConfirmDialog';
export { default as DarkModeToggle } from './DarkModeToggle';
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/locales/ar-SA/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/be-BY/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/bg-BG/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/ca-ES/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/da-DK/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/de-DE/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/el-GR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/en-AU/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/en-NZ/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/en-PT/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/es-MX/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/fa-IR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/hr-HR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/id-ID/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/ko-KR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/nl-NL/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/no-NO/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/pt-BR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/pt-PT/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/ro-RO/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/sq-AL/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/sr-SP/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/tr-TR/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/uk-UA/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/locales/zh-TW/messages.js

Large diffs are not rendered by default.

Loading