This commit is contained in:
fmtabbara
2022-06-13 17:17:26 +01:00
parent 70011d0592
commit ce7d02220f
13 changed files with 143 additions and 172 deletions
@@ -1,6 +1,5 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { ConfirmTx } from './ConfirmTX';
import { ModalListItem } from './Modals/ModalListItem';
@@ -12,8 +11,8 @@ export default {
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
<ConfirmTx {...args}>
<ModalListItem label="Transaction type" value="Bond" divider />
<ModalListItem label="Current bond" value={`100 ${args.currency}`} divider />
<ModalListItem label="Additional bond" value={`50 ${args.currency}`} divider />
<ModalListItem label="Current bond" value="100 NYM" divider />
<ModalListItem label="Additional bond" value="50 NYM" divider />
</ConfirmTx>
);
@@ -22,8 +21,7 @@ Default.args = {
open: true,
header: 'Confirm transaction',
subheader: 'Confirm and proceed or cancel transaction',
fee: { amount: '1', denom: 'NYM' },
currency: 'NYM',
fee: { amount: { amount: '0.001', denom: 'NYM' } },
onClose: () => {},
onConfirm: async () => {},
onPrev: () => {},
+8 -7
View File
@@ -1,15 +1,14 @@
import React from 'react';
import { FeeDetails } from '@nymproject/types';
import { Box, Button } from '@mui/material';
import { SimpleModal } from './Modals/SimpleModal';
import { MajorCurrencyAmount, MajorAmountString } from '@nymproject/types';
import { ModalListItem } from './Modals/ModalListItem';
import { Button } from '@mui/material';
import { ModalFee } from './Modals/ModalFee';
export const ConfirmTx: React.FC<{
open: boolean;
header: string;
subheader?: string;
fee: MajorCurrencyAmount;
currency: MajorAmountString;
fee: FeeDetails;
onConfirm: () => Promise<void>;
onClose?: () => void;
onPrev: () => void;
@@ -27,7 +26,9 @@ export const ConfirmTx: React.FC<{
</Button>
}
>
{children}
<ModalListItem label="Estimated fee for this operation" value={`${fee.amount} ${fee.denom}`} />
<Box sx={{ mt: 3 }}>
{children}
<ModalFee fee={fee} isLoading={false} />
</Box>
</SimpleModal>
);
@@ -1,73 +1,29 @@
import React, { useState } from 'react';
import { Box, Stack, Typography } from '@mui/material';
import { Box, Typography } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { CurrencyDenom, FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
import { getGasFee, simulateDelegateToMixnode } from 'src/requests';
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
import { useGetFee } from 'src/hooks/useGetFee';
import { simulateDelegateToMixnode } from 'src/requests';
import { SimpleModal } from '../Modals/SimpleModal';
import { ModalListItem } from '../Modals/ModalListItem';
import { checkHasEnoughFunds, checkHasEnoughLockedTokens, validateAmount, validateKey } from '../../utils';
import { TokenPoolSelector, TPoolOption } from '../TokenPoolSelector';
import { ConfirmTx } from '../ConfirmTX';
import { Console } from 'src/utils/console';
const MIN_AMOUNT_TO_DELEGATE = 10;
type confirmationResponseType = {
error?: string;
fees?: FeeDetails;
};
const handleConfirmWithBalance = async ({
identityKey,
amount,
}: {
identityKey: string;
amount: MajorCurrencyAmount;
}) => {
const response: confirmationResponseType = {};
const hasEnoughTokens = await checkHasEnoughFunds(amount.amount);
try {
if (!hasEnoughTokens) {
response.error = 'Not enough funds';
} else {
const fees = await simulateDelegateToMixnode({ identity: identityKey, amount });
response.fees = fees;
}
return response;
} catch (e) {
Console.error(e);
response.fees = undefined;
response.error = 'An error occurred. Please check the address and amount are correct';
return response;
const checkTokenBalance = async (tokenPool: TPoolOption, amount: string) => {
let hasEnoughFunds = false;
if (tokenPool === 'locked') {
hasEnoughFunds = await checkHasEnoughLockedTokens(amount);
}
};
const handleConfirmWithLocked = async ({
identityKey,
amount,
}: {
identityKey: string;
amount: MajorCurrencyAmount;
}) => {
const response: confirmationResponseType = {};
const hasEnoughTokens = await checkHasEnoughLockedTokens(amount.amount);
try {
if (!hasEnoughTokens) {
response.error = 'Not enough funds';
} else {
const fees = await simulateDelegateToMixnode({ identity: identityKey, amount });
response.fees = fees;
}
return response;
} catch (e) {
Console.error(e);
response.fees = undefined;
response.error = 'An error occurred. Please check the address and amount are correct';
return response;
if (tokenPool === 'balance') {
hasEnoughFunds = await checkHasEnoughFunds(amount);
}
return hasEnoughFunds;
};
export const DelegateModal: React.FC<{
@@ -84,7 +40,6 @@ export const DelegateModal: React.FC<{
estimatedReward?: number;
profitMarginPercentage?: number | null;
nodeUptimePercentage?: number | null;
feeOverride?: string;
currency: CurrencyDenom;
initialAmount?: string;
hasVestingContract: boolean;
@@ -111,7 +66,8 @@ export const DelegateModal: React.FC<{
const [isValidated, setValidated] = useState<boolean>(false);
const [errorAmount, setErrorAmount] = useState<string | undefined>();
const [tokenPool, setTokenPool] = useState<TPoolOption>('balance');
const [fee, setFee] = useState<FeeDetails>();
const { fee, getFee, resetFeeState } = useGetFee();
const validate = async () => {
let newValidatedValue = true;
@@ -145,22 +101,20 @@ export const DelegateModal: React.FC<{
}
};
const handleConfirm = async ({ identityKey, amount }: { identityKey: string; amount: MajorCurrencyAmount }) => {
let response: confirmationResponseType = {};
const handleConfirm = async ({ identity, value }: { identity: string; value: MajorCurrencyAmount }) => {
const hasEnoughTokens = await checkTokenBalance(tokenPool, value.amount);
if (!hasEnoughTokens) {
setErrorAmount('Not enough funds');
return;
}
if (tokenPool === 'locked') {
response = await handleConfirmWithLocked({ identityKey, amount });
} else {
response = await handleConfirmWithBalance({ identityKey, amount });
getFee(simulateDelegateToMixnode, { identity, amount: value });
}
if (response.error) {
setErrorAmount(response.error);
}
if (!response.error && response.fees) {
setFee(response.fees);
setErrorAmount(undefined);
if (tokenPool === 'balance') {
getFee(simulateDelegateToMixnode, { identity, amount: value });
}
};
@@ -182,16 +136,15 @@ export const DelegateModal: React.FC<{
validate();
}, [amount, identityKey]);
if (fee?.amount) {
if (fee) {
return (
<ConfirmTx
open
header="Delegation details"
fee={fee.amount}
fee={fee}
onClose={onClose}
onPrev={() => setFee(undefined)}
onPrev={resetFeeState}
onConfirm={handleOk}
currency={currency}
>
<ModalListItem label="Node identity key" value={identityKey} divider />
<ModalListItem label="Amount" value={`${amount} ${currency}`} divider />
@@ -205,7 +158,7 @@ export const DelegateModal: React.FC<{
onClose={onClose}
onOk={async () => {
if (identityKey && amount) {
handleConfirm({ identityKey, amount: { amount, denom: currency } });
handleConfirm({ identity: identityKey, value: { amount, denom: currency } });
}
}}
header={header || 'Delegate'}
@@ -51,7 +51,6 @@ export const Delegate = () => {
onClose={() => setOpen(false)}
onOk={async () => setOpen(false)}
currency="NYM"
feeOverride="0.004375"
estimatedReward={50.423}
accountBalance="425.2345053"
nodeUptimePercentage={99.28394}
@@ -73,7 +72,6 @@ export const DelegateBelowMinimum = () => {
onClose={() => setOpen(false)}
onOk={async () => setOpen(false)}
currency="NYM"
feeOverride="0.004375"
estimatedReward={425.2345053}
nodeUptimePercentage={99.28394}
profitMarginPercentage={11.12334234}
@@ -97,7 +95,6 @@ export const DelegateMore = () => {
header="Delegate more"
buttonText="Delegate more"
currency="NYM"
feeOverride="0.004375"
estimatedReward={50.423}
accountBalance="425.2345053"
nodeUptimePercentage={99.28394}
@@ -1,10 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Box, Stack, Typography } from '@mui/material';
import React, { useEffect } from 'react';
import { Box, Typography } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import { SimpleModal } from '../Modals/SimpleModal';
import { useGetFee } from 'src/hooks/useGetFee';
import { simulateUndelegateFromMixnode } from 'src/requests';
import { SimpleModal } from '../Modals/SimpleModal';
import { ModalListItem } from '../Modals/ModalListItem';
import { FeeDetails } from '@nymproject/types';
import { ModalFee } from '../Modals/ModalFee';
export const UndelegateModal: React.FC<{
open: boolean;
@@ -15,20 +16,11 @@ export const UndelegateModal: React.FC<{
currency: string;
proxy: string | null;
}> = ({ identityKey, open, onClose, onOk, amount, currency, proxy }) => {
const [fee, setFee] = useState<FeeDetails>();
const [error, setError] = useState<string>();
const getFee = async () => {
try {
const simulatedFee = await simulateUndelegateFromMixnode(identityKey);
setFee(simulatedFee);
} catch (e) {
setError('Unable to determine fee estimate');
}
};
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
useEffect(() => {
getFee();
// Need simulateVestingUndelegateFromMixnode
getFee(simulateUndelegateFromMixnode, identityKey);
}, []);
const handleOk = async () => {
@@ -45,7 +37,7 @@ export const UndelegateModal: React.FC<{
header="Undelegate"
subHeader="Undelegate from mixnode"
okLabel="Undelegate stake"
okDisabled={!fee && !error}
okDisabled={!fee}
>
<IdentityKeyFormField
readOnly
@@ -63,10 +55,7 @@ export const UndelegateModal: React.FC<{
Tokens will be transferred to account you are logged in with now
</Typography>
<ModalListItem
label="Estimated fee for this operation"
value={fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : 'n/a'}
/>
<ModalFee fee={fee} isLoading={isFeeLoading} error={feeError} />
</SimpleModal>
);
};
@@ -0,0 +1,17 @@
import React from 'react';
import { FeeDetails } from '@nymproject/types';
import { CircularProgress } from '@mui/material';
import { ModalListItem } from './ModalListItem';
type TFeeProps = { fee?: FeeDetails; isLoading: boolean; error?: string };
const getValue = ({ fee, isLoading, error }: TFeeProps) => {
if (isLoading) return <CircularProgress size={15} />;
if (error && !isLoading) return 'n/a';
if (fee) return `${fee.amount?.amount} ${fee.amount?.denom}`;
return '-';
};
export const ModalFee = ({ fee, isLoading, error }: TFeeProps) => (
<ModalListItem label="Estimated fee for this operation" value={getValue({ fee, isLoading, error })} />
);
@@ -6,7 +6,7 @@ export const ModalListItem: React.FC<{
label: string;
divider?: boolean;
hidden?: boolean;
value: React.ReactNode;
value: string | React.ReactNode;
}> = ({ label, value, hidden, divider }) => (
<Box sx={{ display: hidden ? 'none' : 'block' }}>
<Stack direction="row" justifyContent="space-between">
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import WarningIcon from '@mui/icons-material/Warning';
import { simulateCompoundDelgatorReward } from 'src/requests';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import { simulateCompoundDelgatorReward, simulateVestingCompoundDelgatorReward } from 'src/requests';
import { isGreaterThan } from 'src/utils';
import { useGetFee } from 'src/hooks/useGetFee';
import { SimpleModal } from '../Modals/SimpleModal';
import { FeeDetails } from '@nymproject/types';
import { Console } from 'src/utils/console';
import { ModalListItem } from '../Modals/ModalListItem';
import { ModalFee } from '../Modals/ModalFee';
export const CompoundModal: React.FC<{
open: boolean;
@@ -14,29 +14,24 @@ export const CompoundModal: React.FC<{
onOk?: (identityKey: string) => void;
identityKey: string;
amount: number;
fee: number;
minimum?: number;
currency: string;
message: string;
}> = ({ open, onClose, onOk, identityKey, amount, currency, message }) => {
const [fee, setFee] = useState<FeeDetails>();
proxy: string | null;
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, proxy }) => {
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
const handleOk = async () => {
if (onOk) {
onOk(identityKey);
}
};
const getFee = async () => {
try {
const simulatedfee = await simulateCompoundDelgatorReward(identityKey);
setFee(simulatedfee);
} catch (e) {
Console.log(`Unable to get fee estimate for compounding reward: ${e}`);
}
};
useEffect(() => {
getFee();
if (proxy) getFee(simulateVestingCompoundDelgatorReward, identityKey);
else {
getFee(simulateCompoundDelgatorReward, identityKey);
}
}, []);
return (
@@ -61,12 +56,8 @@ export const CompoundModal: React.FC<{
Rewards will be transferred to account you are logged in with now
</Typography>
<ModalListItem
label="Estimated fee for this operation"
value={fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : 'n/a'}
/>
{fee?.amount && amount < +fee.amount?.amount && (
<ModalFee fee={fee} isLoading={isFeeLoading} error={feeError} />
{fee?.amount && isGreaterThan(+fee.amount.amount, amount) && (
<Alert color="warning" sx={{ mt: 3 }} icon={<WarningIcon />}>
<AlertTitle>Warning: fees are greater than the reward</AlertTitle>
The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue?
@@ -56,8 +56,8 @@ export const RedeemAllRewards = () => {
message="Redeem all rewards"
currency="NYM"
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
fee={0.004375}
amount={425.65843}
proxy={null}
/>
</>
);
@@ -75,8 +75,8 @@ export const RedeemRewardForMixnode = () => {
message="Redeem rewards"
currency="NYM"
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
fee={0.004375}
amount={425.65843}
proxy={null}
/>
</>
);
@@ -94,8 +94,8 @@ export const FeeIsMoreThanAllRewards = () => {
message="Redeem all rewards"
currency="NYM"
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
fee={0.004375}
amount={0.001}
proxy={null}
/>
</>
);
@@ -113,8 +113,8 @@ export const FeeIsMoreThanMixnodeReward = () => {
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
message="Redeem rewards"
currency="NYM"
fee={0.004375}
amount={0.001}
proxy={null}
/>
</>
);
@@ -1,12 +1,12 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import WarningIcon from '@mui/icons-material/Warning';
import { FeeDetails } from '@nymproject/types';
import { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests';
import { isGreaterThan } from 'src/utils';
import { useGetFee } from 'src/hooks/useGetFee';
import { SimpleModal } from '../Modals/SimpleModal';
import { Console } from 'src/utils/console';
import { simulateRedeemDelgatorReward } from 'src/requests';
import { ModalListItem } from '../Modals/ModalListItem';
import { ModalFee } from '../Modals/ModalFee';
export const RedeemModal: React.FC<{
open: boolean;
@@ -14,12 +14,12 @@ export const RedeemModal: React.FC<{
onOk?: (identityKey: string) => void;
identityKey: string;
amount: number;
fee: number;
minimum?: number;
currency: string;
message: string;
}> = ({ open, onClose, onOk, identityKey, amount, currency, message }) => {
const [fee, setFee] = useState<FeeDetails>();
proxy: string | null;
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, proxy }) => {
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
const handleOk = async () => {
if (onOk) {
@@ -27,17 +27,12 @@ export const RedeemModal: React.FC<{
}
};
const getFee = async () => {
try {
const simulatedfee = await simulateRedeemDelgatorReward(identityKey);
setFee(simulatedfee);
} catch (e) {
Console.error(`Unable to get fee estimate for compounding reward: ${e}`);
}
};
useEffect(() => {
getFee();
if (proxy) {
getFee(simulateVestingClaimDelgatorReward, identityKey);
} else {
getFee(simulateClaimDelgatorReward, identityKey);
}
}, []);
return (
@@ -62,12 +57,8 @@ export const RedeemModal: React.FC<{
Rewards will be transferred to account you are logged in with now
</Typography>
<ModalListItem
label="Estimated fee for this operation"
value={fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : 'n/a'}
/>
{fee?.amount && amount < +fee.amount?.amount && (
<ModalFee fee={fee} isLoading={isFeeLoading} error={feeError} />
{fee?.amount && isGreaterThan(+fee.amount.amount, amount) && (
<Alert color="warning" sx={{ mt: 3 }} icon={<WarningIcon />}>
<AlertTitle>Warning: fees are greater than the reward</AlertTitle>
The fees for redeeming rewards will cost more than the rewards. Are you sure you want to continue?
+33
View File
@@ -0,0 +1,33 @@
import { FeeDetails } from '@nymproject/types';
import { useState } from 'react';
export function useGetFee() {
const [fee, setFee] = useState<FeeDetails>();
const [isFeeLoading, setIsFeeLoading] = useState(false);
const [feeError, setFeeError] = useState<string>();
async function getFee<T>(operation: (args: T) => Promise<FeeDetails>, args: T) {
try {
setIsFeeLoading(true);
const simulatedFee = await operation(args);
setFee(simulatedFee);
} catch (e) {
setFeeError(e as string);
}
setIsFeeLoading(false);
}
const resetFeeState = () => {
setFee(undefined);
setIsFeeLoading(false);
setFeeError(undefined);
};
return {
fee,
isFeeLoading,
feeError,
getFee,
resetFeeState,
};
}
+4 -3
View File
@@ -5,6 +5,7 @@ import { AppContext, urls } from 'src/context/main';
import { DelegationList } from 'src/components/Delegation/DelegationList';
import { PendingEvents } from 'src/components/Delegation/PendingEvents';
import { TPoolOption } from 'src/components';
import { Console } from 'src/utils/console';
import { CompoundModal } from 'src/components/Rewards/CompoundModal';
import { getSpendableCoins, userBalance } from 'src/requests';
import { RewardsSummary } from '../../components/Rewards/RewardsSummary';
@@ -106,7 +107,7 @@ export const Delegation: FC = () => {
tokenPool,
});
} catch (e) {
console.log(e);
Console.log(e);
setConfirmationModalProps({
status: 'error',
action: 'delegate',
@@ -336,8 +337,8 @@ export const Delegation: FC = () => {
message="Redeem rewards"
currency={clientDetails!.denom}
identityKey={currentDelegationListActionItem?.node_identity}
fee={0.004375}
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
proxy={currentDelegationListActionItem.proxy}
/>
)}
@@ -349,8 +350,8 @@ export const Delegation: FC = () => {
message="Compound rewards"
currency={clientDetails!.denom}
identityKey={currentDelegationListActionItem?.node_identity}
fee={0.004375}
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
proxy={currentDelegationListActionItem.proxy}
/>
)}
+4 -4
View File
@@ -20,10 +20,10 @@ export const simulateUndelegateFromMixnode = async (identity: string) =>
export const simulateCompoundDelgatorReward = async (identity: string) =>
invokeWrapper<FeeDetails>('simulate_compound_delegator_reward', { mixIdentity: identity });
export const simulateRedeemDelgatorReward = async (identity: string) =>
export const simulateClaimDelgatorReward = async (identity: string) =>
invokeWrapper<FeeDetails>('simulate_claim_delegator_reward', { mixIdentity: identity });
export const simulateVestingRedeemDelgatorReward = async (identity: string) =>
export const simulateVestingClaimDelgatorReward = async (identity: string) =>
invokeWrapper<FeeDetails>('simulate_vesting_claim_delegator_reward', { mixIdentity: identity });
export const simulateVestingCompoundDelgatorReward = async (identity: string) =>
@@ -35,8 +35,8 @@ export const simulateVestingBondGateway = async (args: any) =>
export const simulateVestingUnbondGateway = async (args: any) =>
invokeWrapper<FeeDetails>('simulate_vesting_unbond_gateway', args);
export const simulateVestingDelegateToMixnode = async (args: { identity: string; amount: MajorCurrencyAmount }) =>
invokeWrapper<FeeDetails>('simulate_delegate_to_mixnode', args);
export const simulateVestingDelegateToMixnode = async (args: { identity: string }) =>
invokeWrapper<FeeDetails>('simulate_vesting_delegate_to_mixnode', args);
export const simulateVestingBondMixnode = async (args: any) =>
invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);