refactor(wallet-bonding): bonding flow with new gasFee estimation
This commit is contained in:
@@ -1,20 +1,34 @@
|
||||
import React, { useContext, useEffect, useReducer } from 'react';
|
||||
import { Box, Button, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Gateway, MajorCurrencyAmount, MixNode } from '@nymproject/types';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { ConfirmationModal, NymCard } from '../../../components';
|
||||
import NodeIdentityModal from './NodeIdentityModal';
|
||||
import { ACTIONTYPE, AmountData, BondState, FormStep, NodeData, NodeType } from '../types';
|
||||
import {
|
||||
ACTIONTYPE,
|
||||
BondState,
|
||||
FormStep,
|
||||
GatewayAmount,
|
||||
GatewayData,
|
||||
MixnodeAmount,
|
||||
MixnodeData,
|
||||
NodeData,
|
||||
} from '../types';
|
||||
import AmountModal from './AmountModal';
|
||||
import { AppContext, urls } from '../../../context';
|
||||
import SummaryModal from './SummaryModal';
|
||||
import { bond, vestingBond } from '../../../requests';
|
||||
import { TBondArgs } from '../../../types';
|
||||
import { Console } from '../../../utils/console';
|
||||
import {
|
||||
bondGateway as bondGatewayRequest,
|
||||
bondMixNode as bondMixNodeRequest,
|
||||
vestingBondGateway,
|
||||
vestingBondMixNode,
|
||||
} from '../../../requests';
|
||||
import { useGetFee } from '../../../hooks/useGetFee';
|
||||
|
||||
const initialState: BondState = {
|
||||
showModal: false,
|
||||
formStep: 1,
|
||||
bondStatus: 'init',
|
||||
};
|
||||
|
||||
function reducer(state: BondState, action: ACTIONTYPE) {
|
||||
@@ -30,6 +44,8 @@ function reducer(state: BondState, action: ACTIONTYPE) {
|
||||
return { ...state, formStep: action.payload };
|
||||
case 'set_tx':
|
||||
return { ...state, tx: action.payload };
|
||||
case 'set_bond_status':
|
||||
return { ...state, bondStatus: action.payload };
|
||||
case 'next_step':
|
||||
step = state.formStep + 1;
|
||||
return { ...state, formStep: step <= 4 ? (step as FormStep) : 4 };
|
||||
@@ -50,62 +66,107 @@ function reducer(state: BondState, action: ACTIONTYPE) {
|
||||
const BondingCard = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const { formStep, showModal } = state;
|
||||
console.log(state);
|
||||
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
const { fee } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'reset' });
|
||||
}, [clientDetails]);
|
||||
|
||||
const formatData = (nodeType: NodeType, nodeData: NodeData, amountData: AmountData): MixNode | Gateway =>
|
||||
nodeType === 'mixnode'
|
||||
? {
|
||||
host: nodeData.host,
|
||||
mix_port: nodeData.mixPort,
|
||||
verloc_port: nodeData.verlocPort,
|
||||
http_api_port: nodeData.httpApiPort,
|
||||
sphinx_key: nodeData.sphinxKey,
|
||||
identity_key: nodeData.identityKey,
|
||||
version: nodeData.version,
|
||||
profit_margin_percent: amountData.profitMargin as number,
|
||||
}
|
||||
: {
|
||||
host: nodeData.host,
|
||||
mix_port: nodeData.mixPort,
|
||||
clients_port: nodeData.clientsPort,
|
||||
location: nodeData.location as string,
|
||||
sphinx_key: nodeData.sphinxKey,
|
||||
identity_key: nodeData.identityKey,
|
||||
version: nodeData.version,
|
||||
};
|
||||
const bondMixnode = async () => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const { signature, identityKey, sphinxKey, host, version, mixPort, verlocPort, httpApiPort } =
|
||||
state.nodeData as NodeData<MixnodeData>;
|
||||
const { profitMargin, amount, tokenPool } = state.amountData as MixnodeAmount;
|
||||
const payload = {
|
||||
ownerSignature: signature,
|
||||
mixnode: {
|
||||
identity_key: identityKey,
|
||||
sphinx_key: sphinxKey,
|
||||
host,
|
||||
version,
|
||||
mix_port: mixPort,
|
||||
profit_margin_percent: profitMargin,
|
||||
verloc_port: verlocPort,
|
||||
http_api_port: httpApiPort,
|
||||
},
|
||||
pledge: amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
if (tokenPool !== 'locked' && tokenPool !== 'balance') {
|
||||
throw new Error(`token pool [${tokenPool}] not supported`);
|
||||
}
|
||||
try {
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondMixNodeRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondMixNode(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
dispatch({ type: 'set_bond_status', payload: 'success' });
|
||||
return tx;
|
||||
} catch (e) {
|
||||
dispatch({ type: 'set_bond_status', payload: 'error' });
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const bondGateway = async () => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
const { signature, identityKey, sphinxKey, host, version, location, mixPort, clientsPort } =
|
||||
state.nodeData as NodeData<GatewayData>;
|
||||
const { amount, tokenPool } = state.amountData as GatewayAmount;
|
||||
const payload = {
|
||||
ownerSignature: signature,
|
||||
gateway: {
|
||||
identity_key: identityKey,
|
||||
sphinx_key: sphinxKey,
|
||||
host,
|
||||
version,
|
||||
mix_port: mixPort,
|
||||
location,
|
||||
clients_port: clientsPort,
|
||||
},
|
||||
pledge: amount,
|
||||
fee: fee?.fee,
|
||||
};
|
||||
try {
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondGatewayRequest(payload);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondGateway(payload);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
dispatch({ type: 'set_bond_status', payload: 'success' });
|
||||
return tx;
|
||||
} catch (e) {
|
||||
dispatch({ type: 'set_bond_status', payload: 'error' });
|
||||
}
|
||||
return tx;
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
const { nodeData, amountData } = state;
|
||||
if (!nodeData || !amountData) {
|
||||
throw new Error('');
|
||||
const { nodeData } = state;
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
// TODO show a special UI for loading state
|
||||
dispatch({ type: 'set_bond_status', payload: 'loading' });
|
||||
if ((nodeData as NodeData).nodeType === 'mixnode') {
|
||||
tx = await bondMixnode();
|
||||
} else {
|
||||
tx = await bondGateway();
|
||||
}
|
||||
dispatch({ type: 'set_tx', payload: tx });
|
||||
if (state.bondStatus === 'success') {
|
||||
dispatch({ type: 'next_step' });
|
||||
}
|
||||
if (state.bondStatus === 'error') {
|
||||
// TODO show a special UI for error
|
||||
}
|
||||
const request = amountData.tokenPool === 'balance' ? bond : vestingBond;
|
||||
dispatch({ type: 'next_step' });
|
||||
return request({
|
||||
type: nodeData.nodeType,
|
||||
ownerSignature: nodeData.signature,
|
||||
[nodeData.nodeType]: formatData(nodeData.nodeType, nodeData, amountData),
|
||||
pledge: amountData.amount,
|
||||
} as TBondArgs)
|
||||
.then(async (tx) => {
|
||||
if (amountData.tokenPool === 'balance') {
|
||||
await userBalance.fetchBalance();
|
||||
} else {
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
dispatch({ type: 'set_tx', payload: tx });
|
||||
dispatch({ type: 'next_step' });
|
||||
})
|
||||
.catch((e: any) => {
|
||||
Console.error('Failed to bond', e);
|
||||
// TODO do something
|
||||
});
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
@@ -163,9 +224,9 @@ const BondingCard = () => {
|
||||
onClose={() => dispatch({ type: 'reset' })}
|
||||
onCancel={() => dispatch({ type: 'prev_step' })}
|
||||
onSubmit={onSubmit}
|
||||
nodeType={state.nodeData?.nodeType as NodeType}
|
||||
identityKey={state.nodeData?.identityKey as string}
|
||||
amount={state.amountData?.amount as MajorCurrencyAmount}
|
||||
node={state.nodeData as NodeData}
|
||||
amount={state.amountData as MixnodeAmount | GatewayAmount}
|
||||
onError={() => {}}
|
||||
/>
|
||||
)}
|
||||
{formStep === 4 && showModal && (
|
||||
|
||||
@@ -2,7 +2,8 @@ import React from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { Stack } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { NodeData, NodeType } from '../types';
|
||||
import { FieldErrors } from 'react-hook-form/dist/types/errors';
|
||||
import { GatewayData, MixnodeData, NodeData, NodeType } from '../types';
|
||||
import { RadioInput, TextFieldInput, CheckboxInput } from '../components';
|
||||
import nodeSchema from './nodeSchema';
|
||||
import { SimpleDialog } from '../../../components';
|
||||
@@ -32,7 +33,7 @@ const NodeIdentityModal = ({ open, onClose, onSubmit }: Props) => {
|
||||
formState: { errors },
|
||||
} = useForm<NodeData>({
|
||||
defaultValues: {
|
||||
nodeType: radioOptions[0].value,
|
||||
nodeType: 'mixnode',
|
||||
advancedOpt: false,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
@@ -111,8 +112,8 @@ const NodeIdentityModal = ({ open, onClose, onSubmit }: Props) => {
|
||||
defaultValue=""
|
||||
label="Location"
|
||||
placeholder="Location"
|
||||
error={Boolean(errors.location)}
|
||||
helperText={errors.location?.message}
|
||||
error={Boolean((errors as FieldErrors<NodeData<GatewayData>>).location)}
|
||||
helperText={(errors as FieldErrors<NodeData<GatewayData>>).location?.message}
|
||||
required
|
||||
muiTextFieldProps={{ fullWidth: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
@@ -171,8 +172,11 @@ const NodeIdentityModal = ({ open, onClose, onSubmit }: Props) => {
|
||||
control={control}
|
||||
label="Verloc Port"
|
||||
placeholder="Verloc Port"
|
||||
error={Boolean(errors.verlocPort)}
|
||||
helperText={errors.verlocPort?.message && 'A valid port value is required'}
|
||||
error={Boolean((errors as FieldErrors<NodeData<MixnodeData>>).verlocPort)}
|
||||
helperText={
|
||||
(errors as FieldErrors<NodeData<MixnodeData>>).verlocPort?.message &&
|
||||
'A valid port value is required'
|
||||
}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
@@ -182,8 +186,11 @@ const NodeIdentityModal = ({ open, onClose, onSubmit }: Props) => {
|
||||
control={control}
|
||||
label="HTTP API Port"
|
||||
placeholder="HTTP API Port"
|
||||
error={Boolean(errors.httpApiPort)}
|
||||
helperText={errors.httpApiPort?.message && 'A valid port value is required'}
|
||||
error={Boolean((errors as FieldErrors<NodeData<MixnodeData>>).httpApiPort)}
|
||||
helperText={
|
||||
(errors as FieldErrors<NodeData<MixnodeData>>).httpApiPort?.message &&
|
||||
'A valid port value is required'
|
||||
}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
@@ -195,8 +202,11 @@ const NodeIdentityModal = ({ open, onClose, onSubmit }: Props) => {
|
||||
control={control}
|
||||
label="client WS API Port"
|
||||
placeholder="client WS API Port"
|
||||
error={Boolean(errors.clientsPort)}
|
||||
helperText={errors.clientsPort?.message && 'A valid port value is required'}
|
||||
error={Boolean((errors as FieldErrors<NodeData<GatewayData>>).clientsPort)}
|
||||
helperText={
|
||||
(errors as FieldErrors<NodeData<GatewayData>>).clientsPort?.message &&
|
||||
'A valid port value is required'
|
||||
}
|
||||
required
|
||||
registerOptions={{ valueAsNumber: true }}
|
||||
sx={{ mb: 2.5 }}
|
||||
|
||||
@@ -1,40 +1,87 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Divider, Stack, Typography } from '@mui/material';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { getGasFee } from '../../../requests';
|
||||
import { NodeType } from '../types';
|
||||
import { AppContext } from '../../../context';
|
||||
import {
|
||||
simulateBondGateway,
|
||||
simulateBondMixnode,
|
||||
simulateVestingBondGateway,
|
||||
simulateVestingBondMixnode,
|
||||
} from '../../../requests';
|
||||
import { GatewayAmount, GatewayData, MixnodeAmount, MixnodeData, NodeData } from '../types';
|
||||
import { SimpleDialog } from '../../../components';
|
||||
import { useGetFee } from '../../../hooks/useGetFee';
|
||||
|
||||
export interface Props {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onCancel?: () => void;
|
||||
onClose: () => void;
|
||||
onCancel: () => void;
|
||||
onSubmit: () => Promise<void>;
|
||||
identityKey: string;
|
||||
nodeType: NodeType;
|
||||
amount: MajorCurrencyAmount;
|
||||
onError: (message?: string) => void;
|
||||
node: NodeData;
|
||||
amount: MixnodeAmount | GatewayAmount;
|
||||
}
|
||||
|
||||
const SummaryModal = ({ open, onClose, onSubmit, identityKey, nodeType, amount, onCancel }: Props) => {
|
||||
const onConfirm = async () => onSubmit();
|
||||
const [fee, setFee] = useState<string>('-');
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
const SummaryModal = ({ open, onClose, onSubmit, node, amount, onCancel, onError }: Props) => {
|
||||
const { fee, getFee, resetFeeState, feeError, isFeeLoading } = useGetFee();
|
||||
|
||||
const getFee = async (op: 'BondMixnode' | 'BondGateway') => {
|
||||
const res = await getGasFee(op);
|
||||
setFee(`${res.amount} ${clientDetails?.denom}`);
|
||||
useEffect(() => {
|
||||
if (feeError) onError(feeError);
|
||||
}, [feeError]);
|
||||
|
||||
const fetchFee = async () => {
|
||||
const { signature, host, version, mixPort, identityKey, sphinxKey } = node;
|
||||
try {
|
||||
if (node.nodeType === 'mixnode') {
|
||||
await getFee(amount.tokenPool === 'locked' ? simulateVestingBondMixnode : simulateBondMixnode, {
|
||||
ownerSignature: signature,
|
||||
mixnode: {
|
||||
identity_key: identityKey,
|
||||
sphinx_key: sphinxKey,
|
||||
host,
|
||||
version,
|
||||
mix_port: mixPort,
|
||||
profit_margin_percent: (amount as MixnodeAmount).profitMargin,
|
||||
verloc_port: (node as NodeData<MixnodeData>).verlocPort,
|
||||
http_api_port: (node as NodeData<MixnodeData>).httpApiPort,
|
||||
},
|
||||
pledge: amount.amount,
|
||||
});
|
||||
} else {
|
||||
await getFee(amount.tokenPool === 'locked' ? simulateVestingBondGateway : simulateBondGateway, {
|
||||
ownerSignature: signature,
|
||||
gateway: {
|
||||
identity_key: identityKey,
|
||||
sphinx_key: sphinxKey,
|
||||
host,
|
||||
version,
|
||||
mix_port: mixPort,
|
||||
location: (node as NodeData<GatewayData>).location,
|
||||
clients_port: (node as NodeData<GatewayData>).clientsPort,
|
||||
},
|
||||
pledge: amount.amount,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getFee(nodeType === 'mixnode' ? 'BondMixnode' : 'BondGateway');
|
||||
}, [clientDetails, nodeType]);
|
||||
fetchFee();
|
||||
}, [node, amount]);
|
||||
|
||||
const onConfirm = async () => onSubmit();
|
||||
|
||||
return (
|
||||
<SimpleDialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onCancel={onCancel}
|
||||
onClose={() => {
|
||||
resetFeeState();
|
||||
onClose();
|
||||
}}
|
||||
onCancel={() => {
|
||||
resetFeeState();
|
||||
onCancel();
|
||||
}}
|
||||
onConfirm={onConfirm}
|
||||
title="Bond details"
|
||||
confirmButton="Confirm"
|
||||
@@ -45,17 +92,21 @@ const SummaryModal = ({ open, onClose, onSubmit, identityKey, nodeType, amount,
|
||||
>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography>Identity Key</Typography>
|
||||
<Typography>{identityKey}</Typography>
|
||||
<Typography>{node.identityKey}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography>Amount</Typography>
|
||||
<Typography>{`${amount.amount} ${amount.denom}`}</Typography>
|
||||
<Typography>{`${amount.amount.amount} ${amount.amount.denom}`}</Typography>
|
||||
</Stack>
|
||||
<Divider sx={{ my: 1 }} />
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Typography>Fee for this operation</Typography>
|
||||
<Typography>{fee}</Typography>
|
||||
{isFeeLoading ? (
|
||||
<Typography>loading</Typography>
|
||||
) : (
|
||||
<Typography>{fee ? `${fee.amount?.amount} ${fee.amount?.denom}` : ''}</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
</SimpleDialog>
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ interface Props {
|
||||
helperText?: string;
|
||||
sx?: SxProps;
|
||||
registerOptions?: RegisterOptions;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const TextFieldInput = ({
|
||||
@@ -29,6 +30,7 @@ const TextFieldInput = ({
|
||||
helperText,
|
||||
registerOptions,
|
||||
sx,
|
||||
disabled,
|
||||
}: Props) => {
|
||||
const {
|
||||
field: { onChange, onBlur, value, ref },
|
||||
@@ -54,6 +56,7 @@ const TextFieldInput = ({
|
||||
helperText={helperText}
|
||||
{...muiTextFieldProps}
|
||||
sx={sx}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,8 +4,8 @@ import { Box } from '@mui/material';
|
||||
import { useBondingContext, BondingContextProvider } from '../../context';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import BondingCard from './bonding';
|
||||
import MixnodeCard from './mixnode';
|
||||
import GatewayCard from './gateway';
|
||||
/* import MixnodeCard from './mixnode';
|
||||
import GatewayCard from './gateway'; */
|
||||
import { EnumRequestStatus } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
|
||||
@@ -26,8 +26,9 @@ const Bonding = () => {
|
||||
return (
|
||||
<PageLayout>
|
||||
<Box display="flex" flexDirection="column" gap={2}>
|
||||
{bondedMixnode && <MixnodeCard mixnode={bondedMixnode} />}
|
||||
{bondedGateway && <GatewayCard gateway={bondedGateway} />}
|
||||
<BondingCard />
|
||||
{/* {bondedMixnode && <MixnodeCard mixnode={bondedMixnode} />}
|
||||
{bondedGateway && <GatewayCard gateway={bondedGateway} />} */}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
@@ -1,34 +1,53 @@
|
||||
import { MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { MajorCurrencyAmount, TNodeType, TransactionExecuteResult } from '@nymproject/types';
|
||||
|
||||
export type FormStep = 1 | 2 | 3 | 4;
|
||||
export type NodeType = 'mixnode' | 'gateway';
|
||||
export type NodeType = TNodeType;
|
||||
export type BondStatus = 'init' | 'success' | 'error' | 'loading';
|
||||
|
||||
export type ACTIONTYPE =
|
||||
| { type: 'change_bond_type'; payload: NodeType }
|
||||
| { type: 'set_node_data'; payload: NodeData }
|
||||
| { type: 'set_amount_data'; payload: AmountData }
|
||||
| { type: 'set_step'; payload: FormStep }
|
||||
| { type: 'set_tx'; payload: TransactionExecuteResult }
|
||||
| { type: 'set_tx'; payload: TransactionExecuteResult | undefined }
|
||||
| { type: 'set_bond_status'; payload: BondStatus }
|
||||
| { type: 'next_step' }
|
||||
| { type: 'prev_step' }
|
||||
| { type: 'show_modal' }
|
||||
| { type: 'close_modal' }
|
||||
| { type: 'reset' };
|
||||
|
||||
export interface NodeData {
|
||||
nodeType: NodeType;
|
||||
export type NodeIdentity = {
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
signature: string;
|
||||
host: string;
|
||||
location?: string;
|
||||
version: string;
|
||||
advancedOpt: boolean;
|
||||
mixPort: number;
|
||||
};
|
||||
|
||||
export type MixnodeData = NodeIdentity & {
|
||||
verlocPort: number;
|
||||
clientsPort: number;
|
||||
httpApiPort: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type MixnodeAmount = {
|
||||
amount: MajorCurrencyAmount;
|
||||
tokenPool: string;
|
||||
profitMargin: number;
|
||||
};
|
||||
|
||||
export type GatewayData = NodeIdentity & {
|
||||
location: string;
|
||||
clientsPort: number;
|
||||
};
|
||||
|
||||
export type GatewayAmount = Omit<MixnodeAmount, 'profitMargin'>;
|
||||
|
||||
export type NodeData<N = MixnodeData | GatewayData> = {
|
||||
nodeType: TNodeType;
|
||||
} & N;
|
||||
|
||||
export interface AmountData {
|
||||
amount: MajorCurrencyAmount;
|
||||
@@ -40,6 +59,7 @@ export interface BondState {
|
||||
showModal: boolean;
|
||||
formStep: FormStep;
|
||||
nodeData?: NodeData;
|
||||
amountData?: AmountData;
|
||||
amountData?: MixnodeAmount | GatewayAmount;
|
||||
tx?: TransactionExecuteResult;
|
||||
bondStatus: BondStatus;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user