Merge pull request #1357 from nymtech/feature/wallet-delegation-fees
Wallet: simulated delegation fees
This commit is contained in:
@@ -9,6 +9,7 @@ module.exports = {
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
typescript: { reactDocgen: false },
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// // You can change the configuration based on that.
|
||||
@@ -32,15 +33,17 @@ module.exports = {
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
|
||||
config.plugins.push(new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
mode: 'write-references',
|
||||
diagnosticOptions: {
|
||||
semantic: true,
|
||||
syntactic: true,
|
||||
config.plugins.push(
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
mode: 'write-references',
|
||||
diagnosticOptions: {
|
||||
semantic: true,
|
||||
syntactic: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
if (!config.resolve.alias) {
|
||||
config.resolve.alias = {};
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
getVersion: () => undefined
|
||||
}
|
||||
getVersion: () => undefined,
|
||||
};
|
||||
|
||||
@@ -130,6 +130,8 @@ fn main() {
|
||||
simulate::mixnet::simulate_update_mixnode,
|
||||
simulate::mixnet::simulate_delegate_to_mixnode,
|
||||
simulate::mixnet::simulate_undelegate_from_mixnode,
|
||||
simulate::vesting::simulate_vesting_delegate_to_mixnode,
|
||||
simulate::vesting::simulate_vesting_undelegate_from_mixnode,
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
|
||||
@@ -150,6 +150,7 @@ pub async fn simulate_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
println!("Called");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
|
||||
@@ -126,6 +126,53 @@ pub async fn simulate_vesting_update_mixnode(
|
||||
Ok(detailed_fee(client, result))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::DelegateToMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
amount,
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
|
||||
let msg = client.nymd.wrap_contract_execute_message(
|
||||
vesting_contract,
|
||||
&ExecuteMsg::UndelegateFromMixnode {
|
||||
mix_identity: identity.to_string(),
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_withdraw_vested_coins(
|
||||
amount: MajorCurrencyAmount,
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { ConfirmTx } from './ConfirmTX';
|
||||
import { ModalListItem } from './Modals/ModalListItem';
|
||||
|
||||
export default {
|
||||
title: 'Wallet / Confirm Transaction',
|
||||
component: ConfirmTx,
|
||||
} as ComponentMeta<typeof ConfirmTx>;
|
||||
|
||||
const Template: ComponentStory<typeof ConfirmTx> = (args) => (
|
||||
<ConfirmTx {...args}>
|
||||
<ModalListItem label="Transaction type" value="Bond" divider />
|
||||
<ModalListItem label="Current bond" value="100 NYM" divider />
|
||||
<ModalListItem label="Additional bond" value="50 NYM" divider />
|
||||
</ConfirmTx>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
open: true,
|
||||
header: 'Confirm transaction',
|
||||
subheader: 'Confirm and proceed or cancel transaction',
|
||||
fee: { amount: { amount: '0.001', denom: 'NYM' }, fee: { Auto: null } },
|
||||
onClose: () => {},
|
||||
onConfirm: async () => {},
|
||||
onPrev: () => {},
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { SimpleModal } from './Modals/SimpleModal';
|
||||
import { ModalFee } from './Modals/ModalFee';
|
||||
|
||||
export const ConfirmTx: React.FC<{
|
||||
open: boolean;
|
||||
header: string;
|
||||
subheader?: string;
|
||||
fee: FeeDetails;
|
||||
onConfirm: () => Promise<void>;
|
||||
onClose?: () => void;
|
||||
onPrev: () => void;
|
||||
}> = ({ open, fee, onConfirm, onClose, header, subheader, onPrev, children }) => (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
header={header}
|
||||
subHeader={subheader}
|
||||
okLabel="Confirm"
|
||||
onOk={onConfirm}
|
||||
onClose={onClose}
|
||||
SecondaryAction={
|
||||
<Button fullWidth sx={{ mt: 1 }} size="large" onClick={onPrev}>
|
||||
Cancel
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
{children}
|
||||
<ModalFee fee={fee} isLoading={false} />
|
||||
</Box>
|
||||
</SimpleModal>
|
||||
);
|
||||
@@ -13,7 +13,7 @@ export const OverSaturatedBlockerModal: React.FC<{
|
||||
hideCloseIcon
|
||||
displayErrorIcon
|
||||
onClose={onClose}
|
||||
onOk={onClose}
|
||||
onOk={async () => onClose?.()}
|
||||
header={header || 'Delegate'}
|
||||
subHeader={subHeader || "This node is over saturated, you can't delegate more stake to it"}
|
||||
okLabel={buttonText || 'Close'}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { getGasFee } from 'src/requests';
|
||||
import { CurrencyDenom, FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode } from 'src/requests';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from './ModalListItem';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
import { checkTokenBalance, validateAmount, validateKey } from '../../utils';
|
||||
import { TokenPoolSelector, TPoolOption } from '../TokenPoolSelector';
|
||||
import { ConfirmTx } from '../ConfirmTX';
|
||||
|
||||
import { getMixnodeStakeSaturation } from '../../requests';
|
||||
|
||||
const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
@@ -15,7 +19,7 @@ const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
export const DelegateModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption) => Promise<void>;
|
||||
onOk?: (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption, fee?: FeeDetails) => Promise<void>;
|
||||
identityKey?: string;
|
||||
onIdentityKeyChanged?: (identityKey: string) => void;
|
||||
onAmountChanged?: (amount: string) => void;
|
||||
@@ -26,7 +30,6 @@ export const DelegateModal: React.FC<{
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: number | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
feeOverride?: string;
|
||||
currency: CurrencyDenom;
|
||||
initialAmount?: string;
|
||||
hasVestingContract: boolean;
|
||||
@@ -41,7 +44,6 @@ export const DelegateModal: React.FC<{
|
||||
identityKey: initialIdentityKey,
|
||||
rewardInterval,
|
||||
accountBalance,
|
||||
feeOverride,
|
||||
estimatedReward,
|
||||
currency,
|
||||
profitMarginPercentage,
|
||||
@@ -51,66 +53,89 @@ export const DelegateModal: React.FC<{
|
||||
}) => {
|
||||
const [identityKey, setIdentityKey] = useState<string | undefined>(initialIdentityKey);
|
||||
const [amount, setAmount] = useState<string | undefined>(initialAmount);
|
||||
const [fee, setFee] = useState<string>();
|
||||
const [tokenPool, setTokenPool] = useState<TPoolOption>('balance');
|
||||
const [isValidated, setValidated] = useState<boolean>(false);
|
||||
const [errorAmount, setErrorAmount] = useState<string>();
|
||||
const [errorNodeSaturation, setErrorNodeSaturation] = useState<string>();
|
||||
const [errorAmount, setErrorAmount] = useState<string | undefined>();
|
||||
const [tokenPool, setTokenPool] = useState<TPoolOption>('balance');
|
||||
const [errorIdentityKey, setErrorIdentityKey] = useState<string>();
|
||||
|
||||
const getFee = async () => {
|
||||
if (feeOverride) setFee(feeOverride);
|
||||
else {
|
||||
const res = await getGasFee('BondMixnode');
|
||||
setFee(res.amount);
|
||||
}
|
||||
};
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const handleCheckStakeSaturation = async (identity: string) => {
|
||||
setErrorNodeSaturation(undefined);
|
||||
|
||||
try {
|
||||
const newSaturation = await getMixnodeStakeSaturation(identity);
|
||||
if (newSaturation && newSaturation.saturation > 1) {
|
||||
const saturationPercentage = Math.round(newSaturation.saturation * 100);
|
||||
setErrorNodeSaturation(`This node is over saturated (${saturationPercentage}%), please select another node`);
|
||||
return { isOverSaturated: true, saturationPercentage };
|
||||
}
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
} catch (e) {
|
||||
Console.error('Error fetching the saturation, error:', e);
|
||||
setErrorNodeSaturation(undefined);
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
const validateIdentityKey = (isValid: boolean) => {
|
||||
if (!isValid) {
|
||||
setErrorIdentityKey('Identity key is invalid');
|
||||
setErrorNodeSaturation(undefined);
|
||||
} else {
|
||||
setErrorIdentityKey(undefined);
|
||||
const validate = async () => {
|
||||
let newValidatedValue = true;
|
||||
let errorAmountMessage;
|
||||
let errorIdentityKeyMessage;
|
||||
|
||||
if (!identityKey || !validateKey(identityKey, 32)) {
|
||||
newValidatedValue = false;
|
||||
errorIdentityKeyMessage = undefined;
|
||||
}
|
||||
|
||||
if (identityKey && validateKey(identityKey, 32)) {
|
||||
const { isOverSaturated, saturationPercentage } = await handleCheckStakeSaturation(identityKey);
|
||||
if (isOverSaturated) {
|
||||
newValidatedValue = false;
|
||||
errorIdentityKeyMessage = `This node is over saturated (${saturationPercentage}%), please select another node`;
|
||||
}
|
||||
}
|
||||
|
||||
if (amount && !(await validateAmount(amount, '0'))) {
|
||||
newValidatedValue = false;
|
||||
errorAmountMessage = 'Please enter a valid amount';
|
||||
}
|
||||
|
||||
if (amount && Number(amount) < MIN_AMOUNT_TO_DELEGATE) {
|
||||
errorAmountMessage = `Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`;
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
if (!amount?.length) {
|
||||
newValidatedValue = false;
|
||||
}
|
||||
|
||||
setErrorIdentityKey(errorIdentityKeyMessage);
|
||||
setErrorAmount(errorAmountMessage);
|
||||
setValidated(newValidatedValue);
|
||||
};
|
||||
|
||||
const validateAmount = (newValue: MajorCurrencyAmount) => {
|
||||
setErrorAmount(undefined);
|
||||
|
||||
if (newValue.amount && Number(newValue.amount) < MIN_AMOUNT_TO_DELEGATE) {
|
||||
setErrorAmount(`Min. delegation amount: ${MIN_AMOUNT_TO_DELEGATE} ${currency}`);
|
||||
}
|
||||
|
||||
if (!newValue.amount) {
|
||||
setErrorAmount('Amount required');
|
||||
}
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
const handleOk = async () => {
|
||||
if (onOk && amount && identityKey) {
|
||||
onOk(identityKey, { amount, denom: currency }, tokenPool);
|
||||
onOk(identityKey, { amount, denom: currency }, tokenPool, fee);
|
||||
}
|
||||
};
|
||||
|
||||
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 === 'balance') {
|
||||
getFee(simulateDelegateToMixnode, { identity, amount: value });
|
||||
}
|
||||
|
||||
if (tokenPool === 'locked') {
|
||||
getFee(simulateVestingDelegateToMixnode, { identity, amount: value });
|
||||
}
|
||||
};
|
||||
|
||||
const handleIdentityKeyChanged = (newIdentityKey: string) => {
|
||||
setIdentityKey(newIdentityKey);
|
||||
handleCheckStakeSaturation(newIdentityKey);
|
||||
|
||||
if (onIdentityKeyChanged) {
|
||||
onIdentityKeyChanged(newIdentityKey);
|
||||
@@ -119,27 +144,41 @@ export const DelegateModal: React.FC<{
|
||||
|
||||
const handleAmountChanged = (newAmount: MajorCurrencyAmount) => {
|
||||
setAmount(newAmount.amount);
|
||||
validateAmount(newAmount);
|
||||
|
||||
if (onAmountChanged) {
|
||||
onAmountChanged(newAmount.amount);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getFee();
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
validate();
|
||||
}, [amount, identityKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!errorIdentityKey || !!errorAmount || errorNodeSaturation) setValidated(false);
|
||||
else setValidated(true);
|
||||
}, [errorIdentityKey, errorAmount, errorNodeSaturation]);
|
||||
if (fee) {
|
||||
return (
|
||||
<ConfirmTx
|
||||
open
|
||||
header="Delegation details"
|
||||
fee={fee}
|
||||
onClose={onClose}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleOk}
|
||||
>
|
||||
<ModalListItem label="Node identity key" value={identityKey} divider />
|
||||
<ModalListItem label="Amount" value={`${amount} ${currency}`} divider />
|
||||
</ConfirmTx>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={handleOk}
|
||||
onOk={async () => {
|
||||
if (identityKey && amount) {
|
||||
handleConfirm({ identity: identityKey, value: { amount, denom: currency } });
|
||||
}
|
||||
}}
|
||||
header={header || 'Delegate'}
|
||||
subHeader="Delegate to mixnode"
|
||||
okLabel={buttonText || 'Delegate stake'}
|
||||
@@ -150,8 +189,7 @@ export const DelegateModal: React.FC<{
|
||||
fullWidth
|
||||
placeholder="Node identity key"
|
||||
onChanged={handleIdentityKeyChanged}
|
||||
onValidate={validateIdentityKey}
|
||||
initialValue={initialIdentityKey}
|
||||
initialValue={identityKey}
|
||||
readOnly={Boolean(initialIdentityKey)}
|
||||
textFieldProps={{
|
||||
autoFocus: !initialIdentityKey,
|
||||
@@ -163,7 +201,7 @@ export const DelegateModal: React.FC<{
|
||||
variant="caption"
|
||||
sx={{ color: 'error.main', mx: '14px', mt: '3px' }}
|
||||
>
|
||||
{errorNodeSaturation}
|
||||
{errorIdentityKey}
|
||||
</Typography>
|
||||
<Box display="flex" gap={2} alignItems="center" sx={{ mt: 2 }}>
|
||||
{hasVestingContract && <TokenPoolSelector disabled={false} onSelect={(pool) => setTokenPool(pool)} />}
|
||||
@@ -171,7 +209,7 @@ export const DelegateModal: React.FC<{
|
||||
required
|
||||
fullWidth
|
||||
placeholder="Amount"
|
||||
initialValue={initialAmount}
|
||||
initialValue={amount}
|
||||
autoFocus={Boolean(initialIdentityKey)}
|
||||
onChanged={handleAmountChanged}
|
||||
/>
|
||||
@@ -184,10 +222,10 @@ export const DelegateModal: React.FC<{
|
||||
>
|
||||
{errorAmount}
|
||||
</Typography>
|
||||
<Stack direction="row" justifyContent="space-between" my={3}>
|
||||
<Typography fontWeight={600}>Account balance</Typography>
|
||||
<Typography fontWeight={600}>{accountBalance}</Typography>
|
||||
</Stack>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Account balance" value={accountBalance} divider />
|
||||
</Box>
|
||||
|
||||
<ModalListItem label="Rewards payout interval" value={rewardInterval} hidden divider />
|
||||
<ModalListItem
|
||||
label="Node profit margin"
|
||||
@@ -203,14 +241,6 @@ export const DelegateModal: React.FC<{
|
||||
/>
|
||||
|
||||
<ModalListItem label="Node est. reward per epoch" value={`${estimatedReward} ${currency}`} hidden divider />
|
||||
<Stack direction="row" justifyContent="space-between" mt={4}>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
@@ -117,9 +114,8 @@ export const Undelegate = () => {
|
||||
<UndelegateModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
fee={0.004375}
|
||||
amount={150}
|
||||
identityKey="AA6RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujyxx"
|
||||
usesVestingContractTokens={false}
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import React, { useEffect } from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateUndelegateFromMixnode, simulateVestingUndelegateFromMixnode } from 'src/requests';
|
||||
import { ModalFee } from '../Modals/ModalFee';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const UndelegateModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string, usesVestingContractTokens: boolean) => void;
|
||||
onOk?: (identityKey: string, usesVestingContractTokens: boolean, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
currency: string;
|
||||
usesVestingContractTokens: boolean;
|
||||
}> = ({ identityKey, open, onClose, onOk, amount, fee, currency, usesVestingContractTokens }) => {
|
||||
const handleOk = () => {
|
||||
}> = ({ identityKey, open, onClose, onOk, amount, currency, usesVestingContractTokens }) => {
|
||||
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
if (usesVestingContractTokens) getFee(simulateVestingUndelegateFromMixnode, { identity: identityKey });
|
||||
else {
|
||||
getFee(simulateUndelegateFromMixnode, identityKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey, usesVestingContractTokens);
|
||||
onOk(identityKey, usesVestingContractTokens, fee);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
@@ -26,6 +40,7 @@ export const UndelegateModal: React.FC<{
|
||||
header="Undelegate"
|
||||
subHeader="Undelegate from mixnode"
|
||||
okLabel="Undelegate stake"
|
||||
okDisabled={!fee}
|
||||
>
|
||||
<IdentityKeyFormField
|
||||
readOnly
|
||||
@@ -35,25 +50,15 @@ export const UndelegateModal: React.FC<{
|
||||
showTickOnValid={false}
|
||||
/>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" my={3}>
|
||||
<Typography fontWeight={600}>Delegation amount:</Typography>
|
||||
<Typography fontWeight={600}>
|
||||
{amount} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Delegation amount" value={`${amount} ${currency}`} divider />
|
||||
</Box>
|
||||
|
||||
<Typography mb={5} fontSize="smaller">
|
||||
Tokens will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between" mt={3}>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<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 })} />
|
||||
);
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { ModalDivider } from '../Modals/ModalDivider';
|
||||
import { ModalDivider } from './ModalDivider';
|
||||
|
||||
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">
|
||||
@@ -58,7 +58,7 @@ export const Default = () => {
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="This is a modal"
|
||||
subHeader="This is a sub header"
|
||||
okLabel="Click to continue"
|
||||
@@ -87,7 +87,7 @@ export const NoSubheader = () => {
|
||||
<SimpleModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="This is a modal"
|
||||
okLabel="Kaplow!"
|
||||
>
|
||||
@@ -114,7 +114,7 @@ export const hideCloseIcon = () => {
|
||||
open={open}
|
||||
hideCloseIcon
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="This is a modal"
|
||||
okLabel="Kaplow!"
|
||||
>
|
||||
@@ -142,7 +142,7 @@ export const hideCloseIconAndDisplayErrorIcon = () => {
|
||||
hideCloseIcon
|
||||
displayErrorIcon
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
header="This modal announces an error !"
|
||||
okLabel="Kaplow!"
|
||||
sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}
|
||||
|
||||
@@ -11,12 +11,13 @@ export const SimpleModal: React.FC<{
|
||||
headerStyles?: SxProps;
|
||||
subHeaderStyles?: SxProps;
|
||||
onClose?: () => void;
|
||||
onOk?: () => void;
|
||||
onOk?: () => Promise<void>;
|
||||
header: string;
|
||||
subHeader?: string;
|
||||
okLabel: string;
|
||||
okDisabled?: boolean;
|
||||
sx?: SxProps;
|
||||
SecondaryAction?: React.ReactNode;
|
||||
}> = ({
|
||||
open,
|
||||
hideCloseIcon,
|
||||
@@ -30,6 +31,7 @@ export const SimpleModal: React.FC<{
|
||||
subHeader,
|
||||
okLabel,
|
||||
sx,
|
||||
SecondaryAction,
|
||||
children,
|
||||
}) => (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
@@ -55,9 +57,11 @@ export const SimpleModal: React.FC<{
|
||||
|
||||
{children}
|
||||
|
||||
<Button variant="contained" fullWidth sx={{ mt: 3 }} size="large" onClick={onOk} disabled={okDisabled}>
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled} sx={{ mt: 2 }}>
|
||||
{okLabel}
|
||||
</Button>
|
||||
|
||||
{SecondaryAction}
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ export const modalStyle = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
width: 600,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: '16px',
|
||||
|
||||
@@ -1,24 +1,40 @@
|
||||
import React 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 { 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 { ModalFee } from '../Modals/ModalFee';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
export const CompoundModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string) => void;
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, fee, currency, message }) => {
|
||||
const handleOk = () => {
|
||||
usesVestingTokens: boolean;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, usesVestingTokens }) => {
|
||||
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey);
|
||||
onOk(identityKey, fee);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (usesVestingTokens) getFee(simulateVestingCompoundDelgatorReward, identityKey);
|
||||
else {
|
||||
getFee(simulateCompoundDelgatorReward, identityKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
@@ -41,16 +57,8 @@ export const CompoundModal: React.FC<{
|
||||
Rewards will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{amount < fee && (
|
||||
<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?
|
||||
|
||||
@@ -52,12 +52,12 @@ export const RedeemAllRewards = () => {
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={425.65843}
|
||||
usesVestingTokens={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -71,12 +71,12 @@ export const RedeemRewardForMixnode = () => {
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={425.65843}
|
||||
usesVestingTokens={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -94,8 +94,8 @@ export const FeeIsMoreThanAllRewards = () => {
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
fee={0.004375}
|
||||
amount={0.001}
|
||||
usesVestingTokens={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -109,12 +109,12 @@ export const FeeIsMoreThanMixnodeReward = () => {
|
||||
<RedeemModal
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
fee={0.004375}
|
||||
amount={0.001}
|
||||
usesVestingTokens={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,24 +1,41 @@
|
||||
import React 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 { simulateClaimDelgatorReward, simulateVestingClaimDelgatorReward } from 'src/requests';
|
||||
import { isGreaterThan } from 'src/utils';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalFee } from '../Modals/ModalFee';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
export const RedeemModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string) => void;
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
fee: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, fee, currency, message }) => {
|
||||
const handleOk = () => {
|
||||
usesVestingTokens: boolean;
|
||||
}> = ({ open, onClose, onOk, identityKey, amount, currency, message, usesVestingTokens }) => {
|
||||
const { fee, isFeeLoading, feeError, getFee } = useGetFee();
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk) {
|
||||
onOk(identityKey);
|
||||
onOk(identityKey, fee);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (usesVestingTokens) {
|
||||
getFee(simulateVestingClaimDelgatorReward, identityKey);
|
||||
} else {
|
||||
getFee(simulateClaimDelgatorReward, identityKey);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open={open}
|
||||
@@ -41,16 +58,8 @@ export const RedeemModal: React.FC<{
|
||||
Rewards will be transferred to account you are logged in with now
|
||||
</Typography>
|
||||
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
Est. fee for this transaction:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" color={(theme) => theme.palette.nym.fee}>
|
||||
{fee} {currency}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
{amount < fee && (
|
||||
<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?
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getDelegationSummary, undelegateAllFromMixnode } from 'src/requests/del
|
||||
import {
|
||||
DelegationEvent,
|
||||
DelegationWithEverything,
|
||||
FeeDetails,
|
||||
MajorCurrencyAmount,
|
||||
TransactionExecuteResult,
|
||||
} from '@nymproject/types';
|
||||
@@ -21,8 +22,13 @@ export type TDelegationContext = {
|
||||
addDelegation: (
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => Promise<TransactionExecuteResult>;
|
||||
undelegate: (identity: string, usesVestingContractTokens: boolean) => Promise<TransactionExecuteResult[]>;
|
||||
undelegate: (
|
||||
identity: string,
|
||||
usesVestingContractTokens: boolean,
|
||||
fee?: FeeDetails,
|
||||
) => Promise<TransactionExecuteResult[]>;
|
||||
};
|
||||
|
||||
export type TDelegationTransaction = {
|
||||
@@ -50,7 +56,11 @@ export const DelegationContextProvider: FC<{
|
||||
const [totalRewards, setTotalRewards] = useState<undefined | string>();
|
||||
const [pendingDelegations, setPendingDelegations] = useState<DelegationEvent[]>();
|
||||
|
||||
const addDelegation = async (data: { identity: string; amount: MajorCurrencyAmount }, tokenPool: TPoolOption) => {
|
||||
const addDelegation = async (
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
try {
|
||||
let tx;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { createContext, FC, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Network } from 'src/types';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { FeeDetails, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { useDelegationContext } from './delegations';
|
||||
import { claimDelegatorRewards, compoundDelegatorRewards } from '../requests';
|
||||
|
||||
@@ -9,8 +9,8 @@ type TRewardsContext = {
|
||||
error?: string;
|
||||
totalRewards?: string;
|
||||
refresh: () => Promise<void>;
|
||||
claimRewards: (identity: string) => Promise<TransactionExecuteResult[]>;
|
||||
compoundRewards: (identity: string) => Promise<TransactionExecuteResult[]>;
|
||||
claimRewards: (identity: string, fee?: FeeDetails) => Promise<TransactionExecuteResult[]>;
|
||||
compoundRewards: (identity: string, fee?: FeeDetails) => Promise<TransactionExecuteResult[]>;
|
||||
};
|
||||
|
||||
export type TRewardsTransaction = {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useState } from 'react';
|
||||
import { Console } from 'src/utils/console';
|
||||
|
||||
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) {
|
||||
Console.error(e);
|
||||
setFeeError(e as string);
|
||||
}
|
||||
setIsFeeLoading(false);
|
||||
}
|
||||
|
||||
const resetFeeState = () => {
|
||||
setFee(undefined);
|
||||
setIsFeeLoading(false);
|
||||
setFeeError(undefined);
|
||||
};
|
||||
|
||||
return {
|
||||
fee,
|
||||
isFeeLoading,
|
||||
feeError,
|
||||
getFee,
|
||||
resetFeeState,
|
||||
};
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Paper, Stack, Typography } from '@mui/material';
|
||||
import { DelegationWithEverything, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { DelegationWithEverything, FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
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 { OverSaturatedBlockerModal } from 'src/components/Delegation/DelegateBlocker';
|
||||
import { getSpendableCoins, userBalance } from 'src/requests';
|
||||
@@ -17,7 +18,6 @@ import { UndelegateModal } from '../../components/Delegation/UndelegateModal';
|
||||
import { DelegationListItemActions } from '../../components/Delegation/DelegationActions';
|
||||
import { RedeemModal } from '../../components/Rewards/RedeemModal';
|
||||
import { DelegationModal, DelegationModalProps } from '../../components/Delegation/DelegationModal';
|
||||
import { Console } from '../../utils/console';
|
||||
|
||||
export const Delegation: FC = () => {
|
||||
const [showNewDelegationModal, setShowNewDelegationModal] = useState<boolean>(false);
|
||||
@@ -99,7 +99,12 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleNewDelegation = async (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption) => {
|
||||
const handleNewDelegation = async (
|
||||
identityKey: string,
|
||||
amount: MajorCurrencyAmount,
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
setConfirmationModalProps({
|
||||
status: 'loading',
|
||||
action: 'delegate',
|
||||
@@ -113,6 +118,7 @@ export const Delegation: FC = () => {
|
||||
amount,
|
||||
},
|
||||
tokenPool,
|
||||
fee,
|
||||
);
|
||||
|
||||
const balances = await getAllBalances();
|
||||
@@ -136,7 +142,12 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelegateMore = async (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption) => {
|
||||
const handleDelegateMore = async (
|
||||
identityKey: string,
|
||||
amount: MajorCurrencyAmount,
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
if (currentDelegationListActionItem?.node_identity !== identityKey || !clientDetails) {
|
||||
setConfirmationModalProps({
|
||||
status: 'error',
|
||||
@@ -159,6 +170,7 @@ export const Delegation: FC = () => {
|
||||
amount,
|
||||
},
|
||||
tokenPool,
|
||||
fee,
|
||||
);
|
||||
const balances = await getAllBalances();
|
||||
|
||||
@@ -180,7 +192,7 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUndelegate = async (identityKey: string, usesVestingContractTokens: boolean) => {
|
||||
const handleUndelegate = async (identityKey: string, usesVestingContractTokens: boolean, fee?: FeeDetails) => {
|
||||
setConfirmationModalProps({
|
||||
status: 'loading',
|
||||
action: 'undelegate',
|
||||
@@ -189,7 +201,7 @@ export const Delegation: FC = () => {
|
||||
setCurrentDelegationListActionItem(undefined);
|
||||
|
||||
try {
|
||||
const txs = await undelegate(identityKey, usesVestingContractTokens);
|
||||
const txs = await undelegate(identityKey, usesVestingContractTokens, fee);
|
||||
const balances = await getAllBalances();
|
||||
|
||||
setConfirmationModalProps({
|
||||
@@ -211,7 +223,7 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleRedeem = async (identityKey: string) => {
|
||||
const handleRedeem = async (identityKey: string, fee?: FeeDetails) => {
|
||||
setConfirmationModalProps({
|
||||
status: 'loading',
|
||||
action: 'redeem',
|
||||
@@ -220,7 +232,7 @@ export const Delegation: FC = () => {
|
||||
setCurrentDelegationListActionItem(undefined);
|
||||
|
||||
try {
|
||||
const txs = await claimRewards(identityKey);
|
||||
const txs = await claimRewards(identityKey, fee);
|
||||
const bal = await userBalance();
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
@@ -241,7 +253,7 @@ export const Delegation: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCompound = async (identityKey: string) => {
|
||||
const handleCompound = async (identityKey: string, fee?: FeeDetails) => {
|
||||
setConfirmationModalProps({
|
||||
status: 'loading',
|
||||
action: 'compound',
|
||||
@@ -250,7 +262,7 @@ export const Delegation: FC = () => {
|
||||
setCurrentDelegationListActionItem(undefined);
|
||||
|
||||
try {
|
||||
const txs = await compoundRewards(identityKey);
|
||||
const txs = await compoundRewards(identityKey, fee);
|
||||
const bal = await userBalance();
|
||||
setConfirmationModalProps({
|
||||
status: 'success',
|
||||
@@ -336,7 +348,6 @@ export const Delegation: FC = () => {
|
||||
buttonText="Delegate more"
|
||||
identityKey={currentDelegationListActionItem.node_identity}
|
||||
currency={clientDetails!.denom}
|
||||
estimatedReward={0}
|
||||
accountBalance={balance?.printable_balance}
|
||||
nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent}
|
||||
profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent}
|
||||
@@ -352,7 +363,6 @@ export const Delegation: FC = () => {
|
||||
onOk={handleUndelegate}
|
||||
usesVestingContractTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
currency={currentDelegationListActionItem.amount.denom}
|
||||
fee={0.1}
|
||||
amount={+currentDelegationListActionItem.amount.amount}
|
||||
identityKey={currentDelegationListActionItem.node_identity}
|
||||
/>
|
||||
@@ -362,12 +372,12 @@ export const Delegation: FC = () => {
|
||||
<RedeemModal
|
||||
open={showRedeemRewardsModal}
|
||||
onClose={() => setShowRedeemRewardsModal(false)}
|
||||
onOk={(identity) => handleRedeem(identity)}
|
||||
onOk={(identity, fee) => handleRedeem(identity, fee)}
|
||||
message="Redeem rewards"
|
||||
currency={clientDetails!.denom}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
fee={0.004375}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -375,12 +385,12 @@ export const Delegation: FC = () => {
|
||||
<CompoundModal
|
||||
open={showCompoundRewardsModal}
|
||||
onClose={() => setShowCompoundRewardsModal(false)}
|
||||
onOk={(identity) => handleCompound(identity)}
|
||||
onOk={(identity, fee) => handleCompound(identity, fee)}
|
||||
message="Compound rewards"
|
||||
currency={clientDetails!.denom}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
fee={0.004375}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DelegationsSummaryResponse,
|
||||
TransactionExecuteResult,
|
||||
MajorCurrencyAmount,
|
||||
FeeDetails,
|
||||
} from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
@@ -14,8 +15,16 @@ export const getDelegationSummary = async () => invokeWrapper<DelegationsSummary
|
||||
export const undelegateFromMixnode = async (identity: string) =>
|
||||
invokeWrapper<TransactionExecuteResult>('undelegate_from_mixnode', { identity });
|
||||
|
||||
export const undelegateAllFromMixnode = async (identity: string, usesVestingContractTokens: boolean) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('undelegate_all_from_mixnode', { identity, usesVestingContractTokens });
|
||||
export const undelegateAllFromMixnode = async (
|
||||
identity: string,
|
||||
usesVestingContractTokens: boolean,
|
||||
fee?: FeeDetails,
|
||||
) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('undelegate_all_from_mixnode', {
|
||||
identity,
|
||||
usesVestingContractTokens,
|
||||
fee: fee?.fee,
|
||||
});
|
||||
|
||||
export const delegateToMixnode = async ({ identity, amount }: { identity: string; amount: MajorCurrencyAmount }) =>
|
||||
invokeWrapper<TransactionExecuteResult>('delegate_to_mixnode', { identity, amount });
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from './queries';
|
||||
export * from './utils';
|
||||
export * from './delegation';
|
||||
export * from './rewards';
|
||||
export * from './simulate';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { FeeDetails, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const claimOperatorRewards = async () => invokeWrapper<TransactionExecuteResult[]>('claim_operator_reward');
|
||||
@@ -6,8 +6,14 @@ export const claimOperatorRewards = async () => invokeWrapper<TransactionExecute
|
||||
export const compoundOperatorRewards = async () =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('compound_operator_reward');
|
||||
|
||||
export const claimDelegatorRewards = async (mixIdentity: string) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('claim_locked_and_unlocked_delegator_reward', { mixIdentity });
|
||||
export const claimDelegatorRewards = async (mixIdentity: string, fee?: FeeDetails) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('claim_locked_and_unlocked_delegator_reward', {
|
||||
mixIdentity,
|
||||
fee: fee?.fee,
|
||||
});
|
||||
|
||||
export const compoundDelegatorRewards = async (mixIdentity: String) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('compound_locked_and_unlocked_delegator_reward', { mixIdentity });
|
||||
export const compoundDelegatorRewards = async (mixIdentity: String, fee?: FeeDetails) =>
|
||||
invokeWrapper<TransactionExecuteResult[]>('compound_locked_and_unlocked_delegator_reward', {
|
||||
mixIdentity,
|
||||
fee: fee?.fee,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const simulateBondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
@@ -11,11 +11,26 @@ export const simulateUnbondMixnode = async (args: any) => invokeWrapper<FeeDetai
|
||||
|
||||
export const simulateUpdateMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_update_mixnode', args);
|
||||
|
||||
export const simulateDelegateToMixnode = async (args: any) =>
|
||||
export const simulateDelegateToMixnode = async (args: { identity: string; amount: MajorCurrencyAmount }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_delegate_to_mixnode', args);
|
||||
|
||||
export const simulateUndelegateFromMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_undelegate_from_mixnode,', args);
|
||||
export const simulateUndelegateFromMixnode = async (identity: string) =>
|
||||
invokeWrapper<FeeDetails>('simulate_undelegate_from_mixnode', { identity });
|
||||
|
||||
export const simulateCompoundDelgatorReward = async (identity: string) =>
|
||||
invokeWrapper<FeeDetails>('simulate_compound_delegator_reward', { mixIdentity: identity });
|
||||
|
||||
export const simulateClaimDelgatorReward = async (identity: string) =>
|
||||
invokeWrapper<FeeDetails>('simulate_claim_delegator_reward', { mixIdentity: identity });
|
||||
|
||||
export const simulateVestingClaimDelgatorReward = async (identity: string) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_claim_delegator_reward', { mixIdentity: identity });
|
||||
|
||||
export const simulateVestingCompoundDelgatorReward = async (identity: string) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_compound_delegator_reward', { mixIdentity: identity });
|
||||
|
||||
export const simulateVestingUndelegateFromMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_undelegate_from_mixnode', args);
|
||||
|
||||
export const simulateVestingBondGateway = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_gateway', args);
|
||||
@@ -23,6 +38,9 @@ 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 }) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_delegate_to_mixnode', args);
|
||||
|
||||
export const simulateVestingBondMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
EnumNodeType,
|
||||
FeeDetails,
|
||||
Gateway,
|
||||
MajorCurrencyAmount,
|
||||
MixNode,
|
||||
@@ -65,10 +66,12 @@ export const vestingUpdateMixnode = async (profitMarginPercent: number) =>
|
||||
export const vestingDelegateToMixnode = async ({
|
||||
identity,
|
||||
amount,
|
||||
fee,
|
||||
}: {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_delegate_to_mixnode', { identity, amount });
|
||||
fee?: FeeDetails;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_delegate_to_mixnode', { identity, amount, fee: fee?.fee });
|
||||
|
||||
export const vestingUndelegateFromMixnode = async (identity: string) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_undelegate_from_mixnode', { identity });
|
||||
|
||||
@@ -2,6 +2,7 @@ import { appWindow } from '@tauri-apps/api/window';
|
||||
import bs58 from 'bs58';
|
||||
import { valid } from 'semver';
|
||||
import { isValidRawCoin, MajorAmountString } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { getLockedCoins, getSpendableCoins, userBalance } from '../requests';
|
||||
import { Console } from './console';
|
||||
|
||||
@@ -107,3 +108,16 @@ export const maximizeWindow = async () => {
|
||||
export function removeObjectDuplicates<T extends object, K extends keyof T>(arr: T[], id: K) {
|
||||
return arr.filter((v, i, a) => a.findIndex((v2) => v2[id] === v[id]) === i);
|
||||
}
|
||||
|
||||
export const checkTokenBalance = async (tokenPool: TPoolOption, amount: string) => {
|
||||
let hasEnoughFunds = false;
|
||||
if (tokenPool === 'locked') {
|
||||
hasEnoughFunds = await checkHasEnoughLockedTokens(amount);
|
||||
}
|
||||
|
||||
if (tokenPool === 'balance') {
|
||||
hasEnoughFunds = await checkHasEnoughFunds(amount);
|
||||
}
|
||||
|
||||
return hasEnoughFunds;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user