Compare commits

...

13 Commits

Author SHA1 Message Date
fmtabbara d811446b6a use back button component 2022-07-05 16:03:15 +01:00
fmtabbara 65e252416c Merge branch 'develop' into feature/wallet-bonding-with-fees 2022-07-05 16:00:15 +01:00
fmtabbara bfd86d1462 fix type errors 2022-07-04 13:46:00 +01:00
fmtabbara f4215d192f lint fixes 2022-07-04 12:27:42 +01:00
fmtabbara 2524051da9 update tauri functions 2022-07-04 12:01:02 +01:00
fmtabbara 57245aaffe centralize functions 2022-07-04 11:59:46 +01:00
fmtabbara 9398a055a2 refresh state 2022-07-04 11:59:20 +01:00
fmtabbara c8bf0fb853 use confirmation modal for successful/failed unbond 2022-07-04 11:58:58 +01:00
fmtabbara 91b9e2a1a7 update to bonding confirmation modal 2022-07-04 11:58:23 +01:00
fmtabbara 426c9ae026 centralize functions for unbonding gateways and mixnodes 2022-07-04 11:57:28 +01:00
fmtabbara 65e0b69a9d rebuild unbond page to allow logic split between gateways and mixnodes 2022-07-04 11:56:57 +01:00
fmtabbara b9001458de remove dummy data 2022-06-29 15:40:24 +01:00
fmtabbara 3a8214cdb7 split out bond mixnode and gateway forms. add fees for bonding to mixnode or gateway. some small UI additions 2022-06-29 15:15:49 +01:00
26 changed files with 960 additions and 338 deletions
@@ -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;
+1 -5
View File
@@ -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>
);
};
@@ -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>
</>
);
};
@@ -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);
+13 -28
View File
@@ -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) && (
+49 -9
View File
@@ -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 = ({
+1 -1
View File
@@ -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>
);
};
+167 -63
View File
@@ -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: '',
};
+4 -9
View File
@@ -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);
};
+17 -9
View File
@@ -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);
+9 -13
View File
@@ -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');
+3 -2
View File
@@ -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;
+2 -5
View File
@@ -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 = {
+2 -2
View File
@@ -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 };