Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d811446b6a | |||
| 65e252416c | |||
| bfd86d1462 | |||
| f4215d192f | |||
| 2524051da9 | |||
| 57245aaffe | |||
| 9398a055a2 | |||
| c8bf0fb853 | |||
| 91b9e2a1a7 | |||
| 426c9ae026 | |||
| 65e0b69a9d | |||
| b9001458de | |||
| 3a8214cdb7 |
@@ -98,9 +98,8 @@ export const DelegationsActionsMenu: React.FC<{
|
||||
onActionClick?: (action: DelegationListItemActions) => void;
|
||||
isPending?: DelegationEventKind;
|
||||
disableRedeemingRewards?: boolean;
|
||||
disableDelegateMore?: boolean;
|
||||
disableCompoundRewards?: boolean;
|
||||
}> = ({ disableRedeemingRewards, disableDelegateMore, disableCompoundRewards, onActionClick, isPending }) => {
|
||||
}> = ({ disableRedeemingRewards, disableCompoundRewards, onActionClick, isPending }) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Box, CircularProgress, Modal, Stack, Typography } from '@mui/material';
|
||||
|
||||
const modalStyle = {
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 500,
|
||||
bgcolor: 'background.paper',
|
||||
boxShadow: 24,
|
||||
borderRadius: '16px',
|
||||
p: 4,
|
||||
};
|
||||
|
||||
export const LoadingModal = () => (
|
||||
<Modal open>
|
||||
<Box sx={modalStyle} textAlign="center">
|
||||
<Stack spacing={4} direction="row" alignItems="center">
|
||||
<CircularProgress />
|
||||
<Typography>Please wait...</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormControl, FormControlLabel, FormLabel, Radio, RadioGroup } from '@mui/material';
|
||||
import { EnumNodeType } from '@nymproject/types';
|
||||
import { TNodeType } from '@nymproject/types';
|
||||
|
||||
export const NodeTypeSelector = ({
|
||||
disabled,
|
||||
@@ -8,10 +8,10 @@ export const NodeTypeSelector = ({
|
||||
setNodeType,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
nodeType: EnumNodeType;
|
||||
setNodeType: (nodeType: EnumNodeType) => void;
|
||||
nodeType: TNodeType;
|
||||
setNodeType: (nodeType: TNodeType) => void;
|
||||
}) => {
|
||||
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as EnumNodeType);
|
||||
const handleNodeTypeChange = (e: React.ChangeEvent<HTMLInputElement>) => setNodeType(e.target.value as TNodeType);
|
||||
|
||||
return (
|
||||
<FormControl component="fieldset">
|
||||
@@ -24,14 +24,14 @@ export const NodeTypeSelector = ({
|
||||
style={{ display: 'block' }}
|
||||
>
|
||||
<FormControlLabel
|
||||
value={EnumNodeType.mixnode}
|
||||
value="mixnode"
|
||||
control={<Radio color="default" />}
|
||||
label="Mixnode"
|
||||
data-testid="mix-node"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<FormControlLabel
|
||||
value={EnumNodeType.gateway}
|
||||
value="gateway"
|
||||
control={<Radio color="default" />}
|
||||
data-testid="gate-way"
|
||||
label="Gateway"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
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';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const CompoundModal: React.FC<{
|
||||
open: boolean;
|
||||
@@ -15,7 +15,6 @@ export const CompoundModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
usesVestingTokens: boolean;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Alert, AlertTitle, Stack, Typography } from '@mui/material';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
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;
|
||||
@@ -15,7 +15,6 @@ export const RedeemModal: React.FC<{
|
||||
onOk?: (identityKey: string, fee?: FeeDetails) => void;
|
||||
identityKey: string;
|
||||
amount: number;
|
||||
minimum?: number;
|
||||
currency: string;
|
||||
message: string;
|
||||
usesVestingTokens: boolean;
|
||||
|
||||
@@ -56,11 +56,7 @@ 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,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
const addDelegation = async (data: { identity: string; amount: MajorCurrencyAmount }, tokenPool: TPoolOption) => {
|
||||
try {
|
||||
let tx;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { ModalFee } from 'src/components/Modals/ModalFee';
|
||||
import { ModalListItem } from 'src/components/Modals/ModalListItem';
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
identity,
|
||||
amount,
|
||||
fee,
|
||||
onPrev,
|
||||
onConfirm,
|
||||
}: {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
fee: FeeDetails;
|
||||
onPrev: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
}) => (
|
||||
<SimpleModal header="Bond confirmation" open onOk={onConfirm} okLabel="Confirm" hideCloseIcon onBack={onPrev}>
|
||||
<Box sx={{ mt: 3 }}>
|
||||
<ModalListItem label="Mixnode identity" value={identity} />
|
||||
<ModalListItem label="Amount" value={`${amount.amount} ${amount.denom}`} />
|
||||
<ModalFee fee={fee} isLoading={false} />
|
||||
</Box>
|
||||
</SimpleModal>
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { TNodeType } from '@nymproject/types';
|
||||
import { NodeTypeSelector } from 'src/components';
|
||||
import { GatewayForm } from './GatewayForm';
|
||||
import { MixnodeForm } from './MixnodeForm';
|
||||
|
||||
export const FormHandler = ({
|
||||
onSuccess,
|
||||
onError,
|
||||
}: {
|
||||
onSuccess: (details: { address: string; amount: string }) => void;
|
||||
onError: (msg?: string) => void;
|
||||
}) => {
|
||||
const [nodeType, setNodeType] = useState<TNodeType>('mixnode');
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<NodeTypeSelector disabled={false} nodeType={nodeType} setNodeType={setNodeType} />
|
||||
{nodeType === 'mixnode' && <MixnodeForm disabled={false} onError={onError} onSuccess={onSuccess} />}
|
||||
{nodeType === 'gateway' && <GatewayForm disabled={false} onError={onError} onSuccess={onSuccess} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
+108
-168
@@ -1,45 +1,34 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import {
|
||||
Gateway,
|
||||
MixNode,
|
||||
EnumNodeType,
|
||||
MajorCurrencyAmount,
|
||||
CurrencyDenom,
|
||||
TransactionExecuteResult,
|
||||
} from '@nymproject/types';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { TBondArgs } from 'src/types';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { bondGateway, simulateBondGateway, simulateVestingBondGateway, vestingBondGateway } from 'src/requests';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils';
|
||||
import { NodeTypeSelector } from '../../components/NodeTypeSelector';
|
||||
import { bond, vestingBond } from '../../requests';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Fee, TokenPoolSelector } from '../../components';
|
||||
import { Fee, TokenPoolSelector } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { gatewayValidationSchema } from '../validationSchema';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
|
||||
type TBondFormFields = {
|
||||
withAdvancedOptions: boolean;
|
||||
nodeType: EnumNodeType;
|
||||
tokenPool: string;
|
||||
ownerSignature: string;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
profitMarginPercent: number;
|
||||
amount: MajorCurrencyAmount;
|
||||
host: string;
|
||||
version: string;
|
||||
location?: string;
|
||||
location: string;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
clientsPort: number;
|
||||
httpApiPort: number;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
withAdvancedOptions: false,
|
||||
nodeType: EnumNodeType.mixnode,
|
||||
tokenPool: 'balance',
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
@@ -47,35 +36,12 @@ const defaultValues = {
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
location: undefined,
|
||||
location: '',
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
httpApiPort: 8000,
|
||||
clientsPort: 9000,
|
||||
};
|
||||
|
||||
const formatData = (data: TBondFormFields): MixNode | Gateway => {
|
||||
const payload: { [key: string]: any } = {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
};
|
||||
|
||||
if (data.nodeType === EnumNodeType.mixnode) {
|
||||
payload.verloc_port = data.verlocPort;
|
||||
payload.http_api_port = data.httpApiPort;
|
||||
return payload as MixNode;
|
||||
}
|
||||
payload.clients_port = data.clientsPort;
|
||||
payload.location = data.location;
|
||||
return payload as Gateway;
|
||||
};
|
||||
|
||||
export const BondForm = ({
|
||||
export const GatewayForm = ({
|
||||
disabled,
|
||||
onError,
|
||||
onSuccess,
|
||||
@@ -91,22 +57,23 @@ export const BondForm = ({
|
||||
watch,
|
||||
reset,
|
||||
setError,
|
||||
getValues,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TBondFormFields>({
|
||||
resolver: yupResolver(validationSchema),
|
||||
resolver: yupResolver(gatewayValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [clientDetails]);
|
||||
|
||||
const watchNodeType = watch('nodeType', defaultValues.nodeType);
|
||||
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
|
||||
|
||||
const onSubmit = async (data: TBondFormFields, cb: (data: TBondArgs) => Promise<TransactionExecuteResult>) => {
|
||||
const handleValidateAndGetFee = async (data: TBondFormFields) => {
|
||||
if (data.tokenPool === 'balance' && !(await checkHasEnoughFunds(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough funds in wallet' });
|
||||
}
|
||||
@@ -115,43 +82,73 @@ export const BondForm = ({
|
||||
return setError('amount.amount', { message: 'Not enough locked tokens' });
|
||||
}
|
||||
|
||||
const formattedData = formatData(data);
|
||||
|
||||
return cb({
|
||||
type: data.nodeType,
|
||||
ownerSignature: data.ownerSignature,
|
||||
[data.nodeType]: formattedData,
|
||||
pledge: data.amount,
|
||||
} as TBondArgs)
|
||||
.then(async () => {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await userBalance.fetchBalance();
|
||||
} else {
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
onSuccess({ address: data.identityKey, amount: data.amount.amount });
|
||||
})
|
||||
.catch((e) => {
|
||||
onError(e);
|
||||
try {
|
||||
await getFee(data.tokenPool === 'locked' ? simulateVestingBondGateway : simulateBondGateway, {
|
||||
ownerSignature: data.ownerSignature,
|
||||
gateway: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
location: data.location,
|
||||
clients_port: data.clientsPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onSubmit = async (data: TBondFormFields) => {
|
||||
const payload = {
|
||||
ownerSignature: data.ownerSignature,
|
||||
gateway: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
location: data.location,
|
||||
clients_port: data.clientsPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await bondGateway(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked') {
|
||||
await vestingBondGateway(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
|
||||
onSuccess({ address: payload.gateway.identity_key, amount: payload.pledge.amount });
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<Box sx={{ p: 3 }}>
|
||||
{isSubmitting && <LoadingModal />}
|
||||
|
||||
{fee && !isSubmitting && (
|
||||
<ConfirmationModal
|
||||
identity={getValues('identityKey')}
|
||||
amount={getValues('amount')}
|
||||
fee={fee}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleSubmit(onSubmit)}
|
||||
/>
|
||||
)}
|
||||
<Box>
|
||||
<Grid container spacing={3}>
|
||||
<Grid container item justifyContent="space-between">
|
||||
<Grid item>
|
||||
<NodeTypeSelector
|
||||
nodeType={watchNodeType}
|
||||
setNodeType={(nodeType) => {
|
||||
setValue('nodeType', nodeType);
|
||||
if (nodeType === EnumNodeType.mixnode) setValue('location', undefined);
|
||||
}}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('identityKey')}
|
||||
@@ -213,40 +210,20 @@ export const BondForm = ({
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{watchNodeType === EnumNodeType.mixnode && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('profitMarginPercent')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="profitMarginPercent"
|
||||
name="profitMarginPercent"
|
||||
label="Profit percentage"
|
||||
fullWidth
|
||||
error={!!errors.profitMarginPercent}
|
||||
helperText={errors.profitMarginPercent ? errors.profitMarginPercent.message : 'Default is 10%'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
{/* if it's a gateway - get location */}
|
||||
{watchNodeType === EnumNodeType.gateway && (
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
{...register('location')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="location"
|
||||
name="location"
|
||||
label="Location"
|
||||
fullWidth
|
||||
error={!!errors.location}
|
||||
helperText={errors.location?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
{...register('location')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="location"
|
||||
name="location"
|
||||
label="Location"
|
||||
fullWidth
|
||||
error={!!errors.location}
|
||||
helperText={errors.location?.message}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
@@ -291,12 +268,6 @@ export const BondForm = ({
|
||||
setValue('clientsPort', defaultValues.clientsPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('verlocPort', defaultValues.verlocPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('httpApiPort', defaultValues.httpApiPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('withAdvancedOptions', false);
|
||||
} else {
|
||||
setValue('withAdvancedOptions', true);
|
||||
@@ -322,55 +293,24 @@ export const BondForm = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
{watchNodeType === EnumNodeType.mixnode ? (
|
||||
<>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('verlocPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="verlocPort"
|
||||
name="verlocPort"
|
||||
label="Verloc Port"
|
||||
fullWidth
|
||||
error={!!errors.verlocPort}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('httpApiPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="httpApiPort"
|
||||
name="httpApiPort"
|
||||
label="HTTP API Port"
|
||||
fullWidth
|
||||
error={!!errors.httpApiPort}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('clientsPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="clientsPort"
|
||||
name="clientsPort"
|
||||
label="client WS API Port"
|
||||
fullWidth
|
||||
error={!!errors.clientsPort}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('clientsPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="clientsPort"
|
||||
name="clientsPort"
|
||||
label="client WS API Port"
|
||||
fullWidth
|
||||
error={!!errors.clientsPort}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
{!disabled ? <Fee feeType={EnumNodeType.mixnode ? 'BondMixnode' : 'BondGateway'} /> : <div />}
|
||||
{!disabled ? <Fee feeType="BondGateway" /> : <div />}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
@@ -390,7 +330,7 @@ export const BondForm = ({
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit((data) => onSubmit(data, data.tokenPool === 'balance' ? bond : vestingBond))}
|
||||
onClick={handleSubmit(handleValidateAndGetFee)}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
@@ -0,0 +1,361 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from 'src/utils';
|
||||
import { TokenPoolSelector } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { bondMixNode, simulateBondMixnode, simulateVestingBondMixnode, vestingBondMixNode } from '../../../requests';
|
||||
import { mixnodeValidationSchema } from '../validationSchema';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
|
||||
type TBondFormFields = {
|
||||
withAdvancedOptions: boolean;
|
||||
tokenPool: string;
|
||||
ownerSignature: string;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
profitMarginPercent: number;
|
||||
amount: MajorCurrencyAmount;
|
||||
host: string;
|
||||
version: string;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
httpApiPort: number;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
withAdvancedOptions: false,
|
||||
tokenPool: 'balance',
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
httpApiPort: 8000,
|
||||
};
|
||||
|
||||
export const MixnodeForm = ({
|
||||
disabled,
|
||||
onError,
|
||||
onSuccess,
|
||||
}: {
|
||||
disabled: boolean;
|
||||
onError: (message?: string) => void;
|
||||
onSuccess: (details: { address: string; amount: string }) => void;
|
||||
}) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
getValues,
|
||||
watch,
|
||||
reset,
|
||||
setError,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TBondFormFields>({
|
||||
resolver: yupResolver(mixnodeValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [clientDetails]);
|
||||
|
||||
const watchAdvancedOptions = watch('withAdvancedOptions', defaultValues.withAdvancedOptions);
|
||||
|
||||
const handleValidateAndGetFee = async (data: TBondFormFields) => {
|
||||
if (data.tokenPool === 'balance' && !(await checkHasEnoughFunds(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough funds in wallet' });
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked' && !(await checkHasEnoughLockedTokens(data.amount.amount || ''))) {
|
||||
return setError('amount.amount', { message: 'Not enough locked tokens' });
|
||||
}
|
||||
|
||||
try {
|
||||
await getFee(data.tokenPool === 'locked' ? simulateVestingBondMixnode : simulateBondMixnode, {
|
||||
ownerSignature: data.ownerSignature,
|
||||
mixnode: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
verloc_port: data.verlocPort,
|
||||
http_api_port: data.httpApiPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const onSubmit = async (data: TBondFormFields) => {
|
||||
const payload = {
|
||||
ownerSignature: data.ownerSignature,
|
||||
mixnode: {
|
||||
identity_key: data.identityKey,
|
||||
sphinx_key: data.sphinxKey,
|
||||
host: data.host,
|
||||
version: data.version,
|
||||
mix_port: data.mixPort,
|
||||
profit_margin_percent: data.profitMarginPercent,
|
||||
verloc_port: data.verlocPort,
|
||||
http_api_port: data.httpApiPort,
|
||||
},
|
||||
pledge: data.amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await bondMixNode(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
|
||||
if (data.tokenPool === 'locked') {
|
||||
await vestingBondMixNode(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
|
||||
onSuccess({ address: payload.mixnode.identity_key, amount: payload.pledge.amount });
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isSubmitting && <LoadingModal />}
|
||||
|
||||
{fee && !isSubmitting && (
|
||||
<ConfirmationModal
|
||||
identity={getValues('identityKey')}
|
||||
amount={getValues('amount')}
|
||||
fee={fee}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleSubmit(onSubmit)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormControl fullWidth>
|
||||
<Box>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('identityKey')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="identityKey"
|
||||
name="identityKey"
|
||||
label="Identity key"
|
||||
fullWidth
|
||||
error={!!errors.identityKey}
|
||||
helperText={errors.identityKey?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('sphinxKey')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="sphinxKey"
|
||||
name="sphinxKey"
|
||||
label="Sphinx key"
|
||||
error={!!errors.sphinxKey}
|
||||
helperText={errors.sphinxKey?.message}
|
||||
fullWidth
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={12}>
|
||||
<TextField
|
||||
{...register('ownerSignature')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="ownerSignature"
|
||||
name="ownerSignature"
|
||||
label="Owner signature"
|
||||
fullWidth
|
||||
error={!!errors.ownerSignature}
|
||||
helperText={errors.ownerSignature?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{userBalance.originalVesting && (
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={disabled} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<CurrencyFormField
|
||||
showCoinMark
|
||||
required
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={clientDetails?.denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('profitMarginPercent')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="profitMarginPercent"
|
||||
name="profitMarginPercent"
|
||||
label="Profit percentage"
|
||||
fullWidth
|
||||
error={!!errors.profitMarginPercent}
|
||||
helperText={errors.profitMarginPercent ? errors.profitMarginPercent.message : 'Default is 10%'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('host')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="host"
|
||||
name="host"
|
||||
label="Host"
|
||||
fullWidth
|
||||
error={!!errors.host}
|
||||
helperText={errors.host?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
{...register('version')}
|
||||
variant="outlined"
|
||||
required
|
||||
id="version"
|
||||
name="version"
|
||||
label="Version"
|
||||
fullWidth
|
||||
error={!!errors.version}
|
||||
helperText={errors.version?.message}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={watchAdvancedOptions}
|
||||
onChange={() => {
|
||||
if (watchAdvancedOptions) {
|
||||
setValue('mixPort', defaultValues.mixPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('httpApiPort', defaultValues.httpApiPort, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
setValue('withAdvancedOptions', false);
|
||||
} else {
|
||||
setValue('withAdvancedOptions', true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Use advanced options"
|
||||
/>
|
||||
</Grid>
|
||||
{watchAdvancedOptions && (
|
||||
<>
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('mixPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="mixPort"
|
||||
name="mixPort"
|
||||
label="Mix Port"
|
||||
fullWidth
|
||||
error={!!errors.mixPort}
|
||||
helperText={errors.mixPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('verlocPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="verlocPort"
|
||||
name="verlocPort"
|
||||
label="Verloc Port"
|
||||
fullWidth
|
||||
error={!!errors.verlocPort}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={4}>
|
||||
<TextField
|
||||
{...register('httpApiPort', { valueAsNumber: true })}
|
||||
variant="outlined"
|
||||
id="httpApiPort"
|
||||
name="httpApiPort"
|
||||
label="HTTP API Port"
|
||||
fullWidth
|
||||
error={!!errors.httpApiPort}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
padding: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
disabled={isSubmitting || disabled}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
onClick={handleSubmit(handleValidateAndGetFee)}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
Bond
|
||||
</Button>
|
||||
</Box>
|
||||
</FormControl>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { SuccessReponse, TransactionDetails } from '../../components';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { SuccessReponse, TransactionDetails } from '../../../components';
|
||||
import { AppContext } from '../../../context/main';
|
||||
import { useCheckOwnership } from '../../../hooks/useCheckOwnership';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
@@ -1,21 +1,20 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { BondForm } from './BondForm';
|
||||
import { SuccessView } from './SuccessView';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { NymCard } from '../../components';
|
||||
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { FormHandler } from './components/FormHandler';
|
||||
import { SuccessView } from './components/SuccessView';
|
||||
|
||||
export const Bond = () => {
|
||||
const [status, setStatus] = useState(EnumRequestStatus.initial);
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
|
||||
const [error, setError] = useState<string>();
|
||||
const [successDetails, setSuccessDetails] = useState<{ amount: string; address: string }>();
|
||||
|
||||
const { checkOwnership, ownership, isLoading } = useCheckOwnership();
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
if (status === EnumRequestStatus.initial) {
|
||||
@@ -28,7 +27,7 @@ export const Bond = () => {
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title="Bond" subheader="Bond a node or gateway" noPadding>
|
||||
<NymCard title="Bond" subheader="Bond a mixnode or gateway" noPadding>
|
||||
{status === EnumRequestStatus.initial && (
|
||||
<Box sx={{ px: 3, mb: 1 }}>
|
||||
<Alert severity="warning">Always ensure you leave yourself enough funds to UNBOND</Alert>
|
||||
@@ -41,20 +40,7 @@ export const Bond = () => {
|
||||
action={
|
||||
<Button
|
||||
disabled={status === EnumRequestStatus.loading}
|
||||
onClick={async () => {
|
||||
setStatus(EnumRequestStatus.loading);
|
||||
try {
|
||||
if (ownership.vestingPledge) {
|
||||
await vestingUnbond(ownership.nodeType!);
|
||||
} else {
|
||||
await unbond(ownership.nodeType!);
|
||||
}
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
} finally {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
}
|
||||
}}
|
||||
onClick={() => navigate('/unbond')}
|
||||
data-testid="unBond"
|
||||
color="inherit"
|
||||
>
|
||||
@@ -78,16 +64,15 @@ export const Bond = () => {
|
||||
</Box>
|
||||
)}
|
||||
{status === EnumRequestStatus.initial && !ownership.hasOwnership && !isLoading && (
|
||||
<BondForm
|
||||
onError={(e?: string) => {
|
||||
setError(e);
|
||||
setStatus(EnumRequestStatus.error);
|
||||
}}
|
||||
<FormHandler
|
||||
onSuccess={(details) => {
|
||||
setSuccessDetails(details);
|
||||
setStatus(EnumRequestStatus.success);
|
||||
setSuccessDetails(details);
|
||||
}}
|
||||
onError={(err) => {
|
||||
setStatus(EnumRequestStatus.error);
|
||||
setError(err);
|
||||
}}
|
||||
disabled={ownership?.hasOwnership}
|
||||
/>
|
||||
)}
|
||||
{(status === EnumRequestStatus.error || status === EnumRequestStatus.success) && (
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
validateVersion,
|
||||
} from '../../utils';
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
export const mixnodeValidationSchema = Yup.object().shape({
|
||||
identityKey: Yup.string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
|
||||
@@ -37,6 +37,54 @@ export const validationSchema = Yup.object().shape({
|
||||
}),
|
||||
}),
|
||||
|
||||
host: Yup.string()
|
||||
.required('A host is required')
|
||||
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
|
||||
|
||||
version: Yup.string()
|
||||
.required('A version is required')
|
||||
.test('valid-version', 'A valid version is required', (value) => (value ? validateVersion(value) : false)),
|
||||
|
||||
mixPort: Yup.number()
|
||||
.required('A mixport is required')
|
||||
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
verlocPort: Yup.number()
|
||||
.required('A verloc port is required')
|
||||
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
httpApiPort: Yup.number()
|
||||
.required('A http-api port is required')
|
||||
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
});
|
||||
|
||||
export const gatewayValidationSchema = Yup.object().shape({
|
||||
identityKey: Yup.string()
|
||||
.required('An indentity key is required')
|
||||
.test('valid-id-key', 'A valid identity key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
sphinxKey: Yup.string()
|
||||
.required('A sphinx key is required')
|
||||
.test('valid-sphinx-key', 'A valid sphinx key is required', (value) => validateKey(value || '', 32)),
|
||||
|
||||
ownerSignature: Yup.string()
|
||||
.required('Signature is required')
|
||||
.test('valid-signature', 'A valid signature is required', (value) => validateKey(value || '', 64)),
|
||||
|
||||
amount: Yup.object().shape({
|
||||
amount: Yup.string()
|
||||
.required('An amount is required')
|
||||
.test('valid-amount', 'Pledge error', async function isValidAmount(this, value) {
|
||||
const isValid = await validateAmount(value || '', '100');
|
||||
|
||||
if (!isValid) {
|
||||
return this.createError({ message: 'A valid amount is required (min 100)' });
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
}),
|
||||
|
||||
host: Yup.string()
|
||||
.required('A host is required')
|
||||
.test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)),
|
||||
@@ -60,14 +108,6 @@ export const validationSchema = Yup.object().shape({
|
||||
.required('A mixport is required')
|
||||
.test('valid-mixport', 'A valid mixport is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
verlocPort: Yup.number()
|
||||
.required('A verloc port is required')
|
||||
.test('valid-verloc', 'A valid verloc port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
httpApiPort: Yup.number()
|
||||
.required('A http-api port is required')
|
||||
.test('valid-http', 'A valid http-api port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
clientsPort: Yup.number()
|
||||
.required('A clients port is required')
|
||||
.test('valid-clients', 'A valid clients port is required', (value) => (value ? validateRawPort(value) : false)),
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { EnumNodeType, MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { MajorCurrencyAmount, TNodeType, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { TDelegateArgs } from '../../types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { AppContext } from '../../context/main';
|
||||
@@ -14,14 +14,14 @@ type TDelegateForm = {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
tokenPool: string;
|
||||
type: EnumNodeType;
|
||||
type: TNodeType;
|
||||
};
|
||||
|
||||
const defaultValues: TDelegateForm = {
|
||||
identity: '',
|
||||
amount: { amount: '', denom: 'NYM' },
|
||||
tokenPool: 'balance',
|
||||
type: EnumNodeType.mixnode,
|
||||
type: 'mixnode',
|
||||
};
|
||||
|
||||
export const DelegateForm = ({
|
||||
|
||||
@@ -410,7 +410,7 @@ export const Delegation: FC = () => {
|
||||
open={Boolean(saturationError)}
|
||||
onClose={() => setSaturationError(undefined)}
|
||||
header={`Node saturation: ${Math.round(saturationError.saturation * 100000) / 1000}%`}
|
||||
subHeader={'This node is over saturated. Choose a new mix node to delegate to and start compounding rewards.'}
|
||||
subHeader="This node is over saturated. Choose a new mix node to delegate to and start compounding rewards."
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
|
||||
export const ConfirmationModal = ({
|
||||
txUrl,
|
||||
message,
|
||||
success,
|
||||
onClose,
|
||||
}: {
|
||||
txUrl?: string;
|
||||
message?: string;
|
||||
success: boolean;
|
||||
onClose: () => void;
|
||||
}) => (
|
||||
<SimpleModal
|
||||
open
|
||||
header={success ? 'Successfully Unbonded' : 'Unbonding failed'}
|
||||
displayErrorIcon={!success}
|
||||
onOk={async () => onClose()}
|
||||
okLabel="Done"
|
||||
hideCloseIcon
|
||||
sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1 }}
|
||||
>
|
||||
{message && <Typography variant="caption">Error: {message}</Typography>}
|
||||
{txUrl && <Link href={txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />}
|
||||
</SimpleModal>
|
||||
);
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateUnbondGateway, simulateVestingUnbondGateway } from 'src/requests';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
|
||||
export const UnbondGateway = ({
|
||||
isWithVestingTokens,
|
||||
onConfirm,
|
||||
onError,
|
||||
}: {
|
||||
isWithVestingTokens: boolean;
|
||||
onConfirm: (isWithVestingTokens: boolean, fee: FeeDetails) => Promise<void>;
|
||||
onError: (err?: string) => void;
|
||||
}) => {
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const handleGetFee = async () => {
|
||||
try {
|
||||
if (isWithVestingTokens) await getFee(simulateVestingUnbondGateway, {});
|
||||
if (!isWithVestingTokens) await getFee(simulateUnbondGateway, {});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
fee={fee}
|
||||
header="Unbond gateway details"
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
onConfirm={async () => {
|
||||
onConfirm(isWithVestingTokens, fee);
|
||||
resetFeeState();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button size="large" variant="contained" disableElevation onClick={handleGetFee}>
|
||||
Unbond gateway
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Box, Button } from '@mui/material';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateUnbondMixnode, simulateVestingUnbondMixnode } from 'src/requests';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
export const UnbondMixnode = ({
|
||||
isWithVestingTokens,
|
||||
onConfirm,
|
||||
onError,
|
||||
}: {
|
||||
isWithVestingTokens: boolean;
|
||||
onConfirm: (isWithVestingTokens: boolean, fee: FeeDetails) => Promise<void>;
|
||||
onError: (err?: string) => void;
|
||||
}) => {
|
||||
const { fee, getFee, resetFeeState } = useGetFee();
|
||||
|
||||
const handleGetFee = async () => {
|
||||
try {
|
||||
if (isWithVestingTokens) await getFee(simulateVestingUnbondMixnode, {});
|
||||
if (!isWithVestingTokens) await getFee(simulateUnbondMixnode, {});
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
{fee && (
|
||||
<ConfirmTx
|
||||
open
|
||||
fee={fee}
|
||||
header="Unbond mixnode details"
|
||||
onPrev={resetFeeState}
|
||||
onClose={resetFeeState}
|
||||
onConfirm={async () => {
|
||||
onConfirm(isWithVestingTokens, fee);
|
||||
resetFeeState();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button size="large" variant="contained" disableElevation onClick={handleGetFee}>
|
||||
Unbond mixnode
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,87 +1,191 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Fee, NymCard } from '../../components';
|
||||
import { Alert, Button } from '@mui/material';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { AppContext, urls } from 'src/context';
|
||||
import { unbondGateway, unbondMixNode, vestingUnbondGateway, vestingUnbondMixnode } from 'src/requests';
|
||||
import { EnumNodeType } from 'src/types';
|
||||
import { NymCard } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { ConfirmationModal } from './ConfirmationModal';
|
||||
import { UnbondGateway } from './UnbondGateway';
|
||||
import { UnbondMixnode } from './UnbondMixnode';
|
||||
|
||||
export const Unbond = () => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { network } = useContext(AppContext);
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
const { userBalance, getBondDetails } = useContext(AppContext);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<
|
||||
{ success: boolean; txUrl?: string; message?: string } | undefined
|
||||
>();
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
const initialiseForm = async () => {
|
||||
await checkOwnership();
|
||||
};
|
||||
initialiseForm();
|
||||
}, [ownership.hasOwnership, checkOwnership]);
|
||||
}, [checkOwnership]);
|
||||
|
||||
const handleUnbondMixnode = async (isWithVestingTokens: boolean, fee: FeeDetails) => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isWithVestingTokens) tx = await vestingUnbondMixnode(fee?.fee);
|
||||
if (!isWithVestingTokens) tx = await unbondMixNode(fee?.fee);
|
||||
setConfirmationDetails({
|
||||
success: true,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
} catch (err) {
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
});
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnbondGateway = async (isWithVestingTokens: boolean, fee: FeeDetails) => {
|
||||
let tx;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (isWithVestingTokens) tx = await vestingUnbondGateway(fee?.fee);
|
||||
if (!isWithVestingTokens) tx = await unbondGateway(fee?.fee);
|
||||
setConfirmationDetails({
|
||||
success: true,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
} catch (err) {
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
});
|
||||
} finally {
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title="Unbond" subheader="Unbond a mixnode or gateway" noPadding>
|
||||
{ownership?.hasOwnership ? (
|
||||
<>
|
||||
<Alert
|
||||
severity="info"
|
||||
data-testid="bond-noded"
|
||||
action={
|
||||
<Button
|
||||
data-testid="un-bond"
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (ownership.vestingPledge) {
|
||||
await vestingUnbond(ownership.nodeType!);
|
||||
} else {
|
||||
await unbond(ownership.nodeType!);
|
||||
}
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
} finally {
|
||||
await getBondDetails();
|
||||
await checkOwnership();
|
||||
await userBalance.fetchBalance();
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
Unbond
|
||||
</Button>
|
||||
}
|
||||
sx={{ m: 2 }}
|
||||
>
|
||||
{`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
</Alert>
|
||||
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Fee feeType="UnbondMixnode" />
|
||||
</Box>
|
||||
</>
|
||||
) : (
|
||||
<Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
|
||||
You do not currently have a bonded node
|
||||
{!ownership.hasOwnership && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ m: 3 }}
|
||||
data-testid="no-bond"
|
||||
action={
|
||||
<Button color="inherit" onClick={() => navigate('/bond')}>
|
||||
Bond
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
You do not currently have a bonded mixnode or gateway
|
||||
</Alert>
|
||||
)}
|
||||
{isLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
</Box>
|
||||
{ownership.hasOwnership && ownership?.nodeType === EnumNodeType.mixnode && (
|
||||
<UnbondMixnode
|
||||
isWithVestingTokens={!!ownership.vestingPledge}
|
||||
onConfirm={handleUnbondMixnode}
|
||||
onError={(err) =>
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{ownership.hasOwnership && ownership?.nodeType === EnumNodeType.gateway && (
|
||||
<UnbondGateway
|
||||
isWithVestingTokens={!!ownership.vestingPledge}
|
||||
onConfirm={handleUnbondGateway}
|
||||
onError={(err) =>
|
||||
setConfirmationDetails({
|
||||
success: false,
|
||||
message: err as string,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isLoading && <LoadingModal />}
|
||||
{confirmationDetails && (
|
||||
<ConfirmationModal {...confirmationDetails} onClose={() => setConfirmationDetails(undefined)} />
|
||||
)}
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
// {fee && <ConfirmationModal fee={fee} nodeType={ownership.nodeType} onPrev={resetFeeState} onConfirm={async () => {
|
||||
// setIsLoading(true);
|
||||
// try {
|
||||
// if (ownership.vestingPledge) {
|
||||
// await vestingUnbond(ownership.nodeType!);
|
||||
// } else {
|
||||
// await unbond(ownership.nodeType!);
|
||||
// }
|
||||
// } catch (e) {
|
||||
// enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
// } finally {
|
||||
// await getBondDetails();
|
||||
// await checkOwnership();
|
||||
// await userBalance.fetchBalance();
|
||||
// setIsLoading(false);
|
||||
// }
|
||||
// }}/>}
|
||||
// <NymCard title="Unbond" subheader="Unbond a mixnode or gateway" noPadding>
|
||||
// {ownership?.hasOwnership ? (
|
||||
// <>
|
||||
// <Alert
|
||||
// severity="info"
|
||||
// data-testid="bond-noded"
|
||||
// action={
|
||||
// <Button
|
||||
// data-testid="un-bond"
|
||||
// disabled={isLoading}
|
||||
// onClick={async () => {
|
||||
// setIsLoading(true)
|
||||
// if (ownership.vestingPledge) {
|
||||
// await getFee(simulateVestingUnbondMixnode);
|
||||
// } else {
|
||||
// await getFee(unbond, {type: ownership.nodeType})
|
||||
// }
|
||||
// }}
|
||||
// color="inherit"
|
||||
// >
|
||||
// Unbond
|
||||
// </Button>
|
||||
// }
|
||||
// sx={{ m: 2 }}
|
||||
// >
|
||||
// {`Looks like you already have a ${ownership.nodeType} bonded.`}
|
||||
// </Alert>
|
||||
|
||||
// <Box sx={{ p: 3 }}>
|
||||
// <Fee feeType="UnbondMixnode" />
|
||||
// </Box>
|
||||
// </>
|
||||
// ) : (
|
||||
// <Alert severity="info" sx={{ m: 3 }} data-testid="no-bond">
|
||||
// You do not currently have a bonded node
|
||||
// </Alert>
|
||||
// )}
|
||||
// {isLoading && (
|
||||
// <Box
|
||||
// sx={{
|
||||
// display: 'flex',
|
||||
// justifyContent: 'center',
|
||||
// p: 3,
|
||||
// pt: 0,
|
||||
// }}
|
||||
// >
|
||||
// <CircularProgress size={48} />
|
||||
// </Box>
|
||||
// )}
|
||||
// </NymCard>
|
||||
|
||||
@@ -13,18 +13,18 @@ import {
|
||||
} from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { format } from 'date-fns';
|
||||
import { EnumNodeType, PendingUndelegate, TDelegation } from '@nymproject/types';
|
||||
import { TNodeType, PendingUndelegate, TDelegation } from '@nymproject/types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { undelegateFromMixnode, vestingUndelegateFromMixnode } from '../../requests';
|
||||
import { Fee } from '../../components';
|
||||
|
||||
type TFormData = {
|
||||
nodeType: EnumNodeType;
|
||||
nodeType: TNodeType;
|
||||
identity: string;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
nodeType: EnumNodeType.mixnode,
|
||||
nodeType: 'Mixnode' as TNodeType,
|
||||
identity: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import { MajorCurrencyAmount, SendTxResult, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { EnumNodeType, TBondArgs, TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { EnumNodeType, TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const bondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_gateway', args);
|
||||
|
||||
export const unbondGateway = async () => invokeWrapper<TransactionExecuteResult>('unbond_gateway');
|
||||
export const unbondGateway = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_gateway', { fee });
|
||||
|
||||
export const bondMixNode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('bond_mixnode', args);
|
||||
|
||||
export const unbondMixNode = async () => invokeWrapper<TransactionExecuteResult>('unbond_mixnode');
|
||||
export const unbondMixNode = async (fee?: Fee) => invokeWrapper<TransactionExecuteResult>('unbond_mixnode', { fee });
|
||||
|
||||
export const updateMixnode = async (profitMarginPercent: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_mixnode', { profitMarginPercent });
|
||||
@@ -22,9 +23,3 @@ export const unbond = async (type: EnumNodeType) => {
|
||||
if (type === EnumNodeType.mixnode) return unbondMixNode();
|
||||
return unbondGateway();
|
||||
};
|
||||
|
||||
export const bond = async (args: TBondArgs) => {
|
||||
const { type, ...other } = args;
|
||||
if (type === EnumNodeType.mixnode) return bondMixNode(other as TBondMixNodeArgs);
|
||||
return bondGateway(other as TBondGatewayArgs);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { FeeDetails, MajorCurrencyAmount, Gateway, MixNode } from '@nymproject/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const simulateBondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
export const simulateBondGateway = async (args: TBondGatewayArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_gateway', args);
|
||||
|
||||
export const simulateUnbondGateway = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_gateway', args);
|
||||
|
||||
export const simulateBondMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_bond_mixnode', args);
|
||||
export const simulateBondMixnode = async (args: TBondMixNodeArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_bond_mixnode', args);
|
||||
|
||||
export const simulateUnbondMixnode = async (args: any) => invokeWrapper<FeeDetails>('simulate_unbond_mixnode', args);
|
||||
|
||||
@@ -32,8 +35,11 @@ export const simulateVestingCompoundDelgatorReward = async (identity: string) =>
|
||||
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);
|
||||
export const simulateVestingBondGateway = async (args: {
|
||||
gateway: Gateway;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<FeeDetails>('simulate_vesting_bond_gateway', args);
|
||||
|
||||
export const simulateVestingUnbondGateway = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_unbond_gateway', args);
|
||||
@@ -41,11 +47,13 @@ export const simulateVestingUnbondGateway = async (args: any) =>
|
||||
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);
|
||||
export const simulateVestingBondMixnode = async (args: {
|
||||
mixnode: MixNode;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<FeeDetails>('simulate_vesting_bond_mixnode', args);
|
||||
|
||||
export const simulateVestingUnbondMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_unbond_mixnode', args);
|
||||
export const simulateVestingUnbondMixnode = async () => invokeWrapper<FeeDetails>('simulate_vesting_unbond_mixnode');
|
||||
|
||||
export const simulateVestingUpdateMixnode = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_mixnode', args);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
EnumNodeType,
|
||||
TNodeType,
|
||||
FeeDetails,
|
||||
Gateway,
|
||||
MajorCurrencyAmount,
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
TransactionExecuteResult,
|
||||
VestingAccountInfo,
|
||||
} from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from '../types';
|
||||
|
||||
export const getLockedCoins = async (): Promise<MajorCurrencyAmount> =>
|
||||
invokeWrapper<MajorCurrencyAmount>('locked_coins');
|
||||
@@ -43,7 +43,8 @@ export const vestingBondGateway = async ({
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_gateway', { gateway, ownerSignature, pledge });
|
||||
|
||||
export const vestingUnbondGateway = async () => invokeWrapper<TransactionExecuteResult>('vesting_unbond_gateway');
|
||||
export const vestingUnbondGateway = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_gateway', { fee });
|
||||
|
||||
export const vestingBondMixNode = async ({
|
||||
mixnode,
|
||||
@@ -55,7 +56,8 @@ export const vestingBondMixNode = async ({
|
||||
ownerSignature: string;
|
||||
}) => invokeWrapper<TransactionExecuteResult>('vesting_bond_mixnode', { mixnode, ownerSignature, pledge });
|
||||
|
||||
export const vestingUnbondMixnode = async () => invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode');
|
||||
export const vestingUnbondMixnode = async (fee?: Fee) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_unbond_mixnode', { fee });
|
||||
|
||||
export const withdrawVestedCoins = async (amount: MajorCurrencyAmount) =>
|
||||
invokeWrapper<TransactionExecuteResult>('withdraw_vested_coins', { amount });
|
||||
@@ -84,7 +86,7 @@ export const getVestingPledgeInfo = async ({
|
||||
type,
|
||||
}: {
|
||||
address?: string;
|
||||
type: EnumNodeType;
|
||||
type: TNodeType;
|
||||
}): Promise<PledgeData | undefined> => {
|
||||
try {
|
||||
return await invokeWrapper<PledgeData>(`vesting_get_${type}_pledge`, { address });
|
||||
@@ -96,17 +98,11 @@ export const getVestingPledgeInfo = async ({
|
||||
export const vestingDelegatedFree = async (vestingAccountAddress: string) =>
|
||||
invokeWrapper<MajorCurrencyAmount>('delegated_free', { vestingAccountAddress });
|
||||
|
||||
export const vestingUnbond = async (type: EnumNodeType) => {
|
||||
if (type === EnumNodeType.mixnode) return vestingUnbondMixnode();
|
||||
export const vestingUnbond = async (type: TNodeType) => {
|
||||
if (type === 'mixnode') return vestingUnbondMixnode();
|
||||
return vestingUnbondGateway();
|
||||
};
|
||||
|
||||
export const vestingBond = async (args: { type: EnumNodeType } & (TBondMixNodeArgs | TBondGatewayArgs)) => {
|
||||
const { type, ...other } = args;
|
||||
if (type === EnumNodeType.mixnode) return vestingBondMixNode(other as TBondMixNodeArgs);
|
||||
return vestingBondGateway(other as TBondGatewayArgs);
|
||||
};
|
||||
|
||||
export const vestingClaimOperatorRewards = async () =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_claim_operator_reward');
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Gateway, MajorCurrencyAmount, MixNode, PledgeData } from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
|
||||
export enum EnumNodeType {
|
||||
mixnode = 'mixnode',
|
||||
@@ -28,16 +29,16 @@ export type TBondGatewayArgs = {
|
||||
gateway: Gateway;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondMixNodeArgs = {
|
||||
mixnode: MixNode;
|
||||
pledge: MajorCurrencyAmount;
|
||||
ownerSignature: string;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TBondArgs = { type: EnumNodeType } & (TBondGatewayArgs | TBondMixNodeArgs);
|
||||
|
||||
export type TDelegateArgs = {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { MixNode, MajorCurrencyAmount } from './rust';
|
||||
|
||||
export enum EnumNodeType {
|
||||
mixnode = 'mixnode',
|
||||
gateway = 'gateway',
|
||||
}
|
||||
export type TNodeType = 'mixnode' | 'gateway';
|
||||
|
||||
export type TNodeOwnership = {
|
||||
hasOwnership: boolean;
|
||||
nodeType?: EnumNodeType;
|
||||
nodeType?: TNodeType;
|
||||
};
|
||||
|
||||
export type TDelegation = {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import type { CosmosFee } from "./CosmosFee";
|
||||
import type { CosmosFee } from './CosmosFee';
|
||||
|
||||
export type Fee = { Manual: CosmosFee } | { Auto: number | null };
|
||||
export type Fee = { Manual: CosmosFee } | { Auto: number | null };
|
||||
|
||||
Reference in New Issue
Block a user