Feature/send with simulated fee (#1402)

* add simulate send function

* extract loading modal into its own component

* move appbar component into folder

* add additional prop for making modal list item text bold

* create new send UI and stories

* remove old send page

* use simulated fee in send request

* use new confirmation modal component
This commit is contained in:
Fouad
2022-07-06 21:08:40 +01:00
committed by GitHub
parent a11e674967
commit 7e9409bbef
27 changed files with 464 additions and 109 deletions
+16 -6
View File
@@ -4,7 +4,7 @@
*/
module.exports = {
invoke: (operation, args) => {
switch(operation) {
switch (operation) {
case 'get_balance': {
return {
amount: {
@@ -12,20 +12,30 @@ module.exports = {
denom: 'NYMT',
},
printable_balance: '100 NYMT',
}
};
}
case 'delegate_to_mixnode': {
return ({
return {
logs_json: '[]',
data_json: '{}',
transaction_hash: '12345',
});
};
}
case 'simulate_send': {
return {
amount: {
amount: '0.01',
denom: 'NYM',
},
};
}
}
console.error(`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_wallet/.storybook/mocks/tauri.js" if you need. The default response is "void".`);
console.error(
`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_wallet/.storybook/mocks/tauri.js" if you need. The default response is "void".`,
);
return new Promise((resolve, reject) => {
reject(new Error(`Tauri operation ${operation} not available in storybook.`));
});
},
}
};
@@ -1,17 +1,16 @@
import React, { useContext } from 'react';
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { Logout } from '@mui/icons-material';
import TerminalIcon from '@mui/icons-material/Terminal';
import { AppContext } from '../context/main';
import { NetworkSelector } from './NetworkSelector';
import { Node as NodeIcon } from '../svg-icons/node';
import { MultiAccounts } from './Accounts';
import { config } from '../config';
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
import { Node } from 'src/svg-icons/node';
import { config } from '../../config';
import { AppContext } from '../../context/main';
import { MultiAccounts } from '../Accounts';
import { NetworkSelector } from '../NetworkSelector';
export const AppBar = () => {
const { logOut, handleShowTerminal, appEnv, handleShowSettings, showSettings } = useContext(AppContext);
const navigate = useNavigate();
const { showSettings, handleShowTerminal, appEnv, handleShowSettings, logOut } = useContext(AppContext);
return (
<MuiAppBar position="sticky" sx={{ boxShadow: 'none', bgcolor: 'transparent' }}>
<Toolbar disableGutters>
@@ -24,7 +23,7 @@ export const AppBar = () => {
<NetworkSelector />
</Grid>
</Grid>
<Grid item container justifyContent="flex-end" md={12} lg={5} spacing={2}>
<Grid item container justifyContent="flex-end" alignItems="center" md={12} lg={5} spacing={2}>
{(appEnv?.SHOW_TERMINAL || config.IS_DEV_MODE) && (
<Grid item>
<IconButton size="small" onClick={handleShowTerminal} sx={{ color: 'nym.background.dark' }}>
@@ -38,18 +37,11 @@ export const AppBar = () => {
sx={{ color: showSettings ? 'primary.main' : 'nym.background.dark' }}
size="small"
>
<NodeIcon fontSize="small" />
<Node fontSize="small" />
</IconButton>
</Grid>
<Grid item>
<IconButton
size="small"
onClick={async () => {
await logOut();
navigate('/');
}}
sx={{ color: 'nym.background.dark' }}
>
<IconButton size="small" onClick={logOut} sx={{ color: 'nym.background.dark' }}>
<Logout fontSize="small" />
</IconButton>
</Grid>
@@ -0,0 +1 @@
export * from './AppBar';
@@ -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>) => {
@@ -1,7 +1,8 @@
import React from 'react';
import { Box, Button, CircularProgress, Modal, Stack, Typography } from '@mui/material';
import { Box, Button, Modal, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { modalStyle } from '../Modals/styles';
import { LoadingModal } from '../Modals/LoadingModal';
export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
@@ -42,18 +43,7 @@ export const DelegationModal: React.FC<
onClose?: () => void;
}
> = ({ status, action, message, recipient, balance, balanceVested, transactions, open, onClose, children }) => {
if (status === 'loading') {
return (
<Modal open>
<Box sx={modalStyle} textAlign="center">
<Stack spacing={4} direction="row" alignItems="center">
<CircularProgress />
<Typography>Please wait...</Typography>
</Stack>
</Box>
</Modal>
);
}
if (status === 'loading') return <LoadingModal />;
if (status === 'error') {
return (
@@ -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>
);
@@ -6,12 +6,17 @@ export const ModalListItem: React.FC<{
label: string;
divider?: boolean;
hidden?: boolean;
value: string | React.ReactNode;
}> = ({ label, value, hidden, divider }) => (
strong?: boolean;
value: React.ReactNode;
}> = ({ label, value, hidden, divider, strong }) => (
<Box sx={{ display: hidden ? 'none' : 'block' }}>
<Stack direction="row" justifyContent="space-between">
<Typography fontSize="smaller">{label}:</Typography>
<Typography fontSize="smaller">{value}</Typography>
<Typography fontSize="smaller" fontWeight={strong ? 600 : undefined}>
{label}:
</Typography>
<Typography fontSize="smaller" fontWeight={strong ? 600 : undefined}>
{value}
</Typography>
</Stack>
{divider && <ModalDivider />}
</Box>
+59 -50
View File
@@ -1,58 +1,67 @@
import React, { useContext } from 'react';
import { Link, useLocation } from 'react-router-dom';
import React, { useState, useContext } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { List, ListItem, ListItemIcon, ListItemText } from '@mui/material';
import { AccountBalanceWalletOutlined, ArrowBack, ArrowForward, Description, Settings } from '@mui/icons-material';
import { AppContext } from '../context/main';
import { Bond, Delegate, Unbond } from '../svg-icons';
const routesSchema = [
{
label: 'Balance',
route: '/balance',
Icon: AccountBalanceWalletOutlined,
},
{
label: 'Send',
route: '/send',
Icon: ArrowForward,
},
{
label: 'Receive',
route: '/receive',
Icon: ArrowBack,
},
{
label: 'Bond',
route: '/bond',
Icon: Bond,
},
{
label: 'Unbond',
route: '/unbond',
Icon: Unbond,
},
{
label: 'Delegation',
route: '/delegation',
Icon: Delegate,
},
{
label: 'Docs',
route: '/docs',
Icon: Description,
mode: 'dev',
},
{
label: 'Admin',
route: '/admin',
Icon: Settings,
mode: 'admin',
},
];
export const Nav = () => {
const location = useLocation();
const { isAdminAddress } = useContext(AppContext);
const navigate = useNavigate();
const { isAdminAddress, handleShowSendModal } = useContext(AppContext);
const [routesSchema] = useState([
{
label: 'Balance',
route: '/balance',
Icon: AccountBalanceWalletOutlined,
onClick: () => navigate('/balance'),
},
{
label: 'Send',
Icon: ArrowForward,
onClick: handleShowSendModal,
},
{
label: 'Receive',
route: '/receive',
Icon: ArrowBack,
onClick: () => navigate('/receive'),
},
{
label: 'Bond',
route: '/bond',
Icon: Bond,
onClick: () => navigate('/bond'),
},
{
label: 'Unbond',
route: '/unbond',
Icon: Unbond,
onClick: () => navigate('/unbond'),
},
{
label: 'Delegation',
route: '/delegation',
Icon: Delegate,
onClick: () => navigate('/delegation'),
},
{
label: 'Docs',
route: '/admin',
Icon: Description,
mode: 'dev',
onClick: () => navigate('/docs'),
},
{
label: 'Admin',
route: '/admin',
Icon: Settings,
mode: 'admin',
onClick: () => navigate('/admin'),
},
]);
return (
<div
@@ -77,8 +86,8 @@ export const Nav = () => {
return false;
}
})
.map(({ Icon, route, label }) => (
<ListItem disableGutters component={Link} to={route} key={label}>
.map(({ Icon, onClick, label, route }) => (
<ListItem disableGutters key={label} onClick={onClick} sx={{ cursor: 'pointer' }}>
<ListItemIcon
sx={{
minWidth: 30,
@@ -1,11 +1,11 @@
import React, { useEffect } from 'react';
import { Stack, Typography } from '@mui/material';
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
import { FeeDetails } from '@nymproject/types';
import { simulateCompoundDelgatorReward, simulateVestingCompoundDelgatorReward } from 'src/requests';
import { useGetFee } from 'src/hooks/useGetFee';
import { SimpleModal } from '../Modals/SimpleModal';
import { ModalFee } from '../Modals/ModalFee';
import { FeeDetails } from '@nymproject/types';
import { FeeWarning } from '../FeeWarning';
export const CompoundModal: React.FC<{
@@ -14,7 +14,6 @@ export const CompoundModal: React.FC<{
onOk?: (identityKey: string, fee?: FeeDetails) => void;
identityKey: string;
amount: number;
minimum?: number;
currency: string;
message: string;
usesVestingTokens: boolean;
@@ -14,7 +14,6 @@ export const RedeemModal: React.FC<{
onOk?: (identityKey: string, fee?: FeeDetails) => void;
identityKey: string;
amount: number;
minimum?: number;
currency: string;
message: string;
usesVestingTokens: boolean;
@@ -0,0 +1,48 @@
import React from 'react';
import { ComponentMeta } from '@storybook/react';
import { MockMainContextProvider } from 'src/context/mocks/main';
import { SendDetailsModal } from './SendDetailsModal';
import { SendSuccessModal } from './SendSuccessModal';
import { SendErrorModal } from './SendErrorModal';
import { SendInputModal } from './SendInputModal';
import { Send } from '.';
export default {
title: 'Send/Components',
component: SendDetailsModal,
} as ComponentMeta<typeof SendDetailsModal>;
export const SendInput = () => (
<SendInputModal
toAddress=""
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
onNext={() => {}}
onClose={() => {}}
onAddressChange={() => {}}
onAmountChange={() => {}}
/>
);
export const SendDetails = () => (
<SendDetailsModal
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
toAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
fee={{ amount: { amount: '0.01', denom: 'NYM' }, fee: { Auto: null } }}
amount={{ amount: '100', denom: 'NYM' }}
onPrev={() => {}}
onSend={() => {}}
onClose={() => {}}
/>
);
export const SendSuccess = () => (
<SendSuccessModal txDetails={{ amount: '100 NYM', txUrl: 'dummtUrl.com' }} onClose={() => {}} />
);
export const SendError = () => <SendErrorModal onClose={() => {}} />;
export const SendFlow = () => (
<MockMainContextProvider>
<Send />
</MockMainContextProvider>
);
@@ -0,0 +1,43 @@
import React from 'react';
import { Stack } from '@mui/material';
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
import { SimpleModal } from '../Modals/SimpleModal';
import { ModalListItem } from '../Modals/ModalListItem';
export const SendDetailsModal = ({
amount,
toAddress,
fromAddress,
fee,
onClose,
onPrev,
onSend,
}: {
fromAddress?: string;
toAddress: string;
fee?: FeeDetails;
amount?: MajorCurrencyAmount;
onClose: () => void;
onPrev: () => void;
onSend: (data: { val: MajorCurrencyAmount; to: string }) => void;
}) => (
<SimpleModal
header="Send details"
open
onClose={onClose}
okLabel="Confirm"
onOk={async () => amount && onSend({ val: amount, to: toAddress })}
onBack={onPrev}
>
<Stack gap={0.5} sx={{ mt: 4 }}>
<ModalListItem label="From" value={fromAddress} divider />
<ModalListItem label="To" value={toAddress} divider />
<ModalListItem label="Amount" value={`${amount?.amount} ${amount?.denom}`} divider />
<ModalListItem
label="Fee for this transaction"
value={!fee ? 'n/a' : `${fee.amount?.amount} ${fee.amount?.denom}`}
divider
/>
</Stack>
</SimpleModal>
);
@@ -0,0 +1,24 @@
import React from 'react';
import { SimpleModal } from '../Modals/SimpleModal';
export const SendErrorModal = ({ onClose }: { onClose: () => void }) => (
<SimpleModal
open
hideCloseIcon
displayErrorIcon
onOk={async () => onClose()}
header="Send"
subHeader="An error occurred while sending. Please try again"
okLabel="Close"
sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}
headerStyles={{
width: '100%',
mb: 3,
textAlign: 'center',
color: 'error.main',
fontSize: 16,
textTransform: 'capitalize',
}}
subHeaderStyles={{ textAlign: 'center', color: 'text.primary', fontSize: 14, fontWeight: 400 }}
/>
);
@@ -0,0 +1,70 @@
import React, { useEffect, useState } from 'react';
import { Stack, TextField, Typography } from '@mui/material';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
import { MajorCurrencyAmount } from '@nymproject/types';
import { validateAmount } from 'src/utils';
import { SimpleModal } from '../Modals/SimpleModal';
import { ModalListItem } from '../Modals/ModalListItem';
export const SendInputModal = ({
fromAddress,
toAddress,
amount,
balance,
error,
onNext,
onClose,
onAmountChange,
onAddressChange,
}: {
fromAddress?: string;
toAddress: string;
amount?: MajorCurrencyAmount;
balance?: string;
error?: string;
onNext: () => void;
onClose: () => void;
onAmountChange: (value: MajorCurrencyAmount) => void;
onAddressChange: (value: string) => void;
}) => {
const [isValid, setIsValid] = useState(false);
const validate = async (value: MajorCurrencyAmount) => {
const isValidAmount = await validateAmount(value.amount, '0');
setIsValid(isValidAmount);
};
useEffect(() => {
if (amount) validate(amount);
}, []);
return (
<SimpleModal header="Send" open onClose={onClose} okLabel="Next" onOk={async () => onNext()} okDisabled={!isValid}>
<Stack gap={2} sx={{ mt: 2 }}>
<TextField
placeholder="Recipient address"
fullWidth
onChange={(e) => onAddressChange(e.target.value)}
value={toAddress}
/>
<CurrencyFormField
placeholder="Amount"
fullWidth
onChanged={(value) => {
onAmountChange(value);
validate(value);
}}
initialValue={amount?.amount}
/>
<Typography fontSize="smaller" sx={{ color: 'error.main' }}>
{error}
</Typography>
</Stack>
<Stack gap={0.5} sx={{ mt: 2 }}>
<ModalListItem label="Account balance" value={balance} divider strong />
<ModalListItem label="Your address" value={fromAddress} divider />
<Typography fontSize="smaller">Est. fee for this transaction will be show on the next page</Typography>
</Stack>
</SimpleModal>
);
};
@@ -0,0 +1,93 @@
import React, { useContext, useState } from 'react';
import { MajorCurrencyAmount } from '@nymproject/types';
import { AppContext, urls } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { send } from 'src/requests';
import { Console } from 'src/utils/console';
import { simulateSend } from 'src/requests/simulate';
import { LoadingModal } from '../Modals/LoadingModal';
import { SendDetailsModal } from './SendDetailsModal';
import { SendErrorModal } from './SendErrorModal';
import { SendInputModal } from './SendInputModal';
import { SendSuccessModal } from './SendSuccessModal';
import { TTransactionDetails } from './types';
export const SendModal = ({ onClose }: { onClose: () => void }) => {
const [toAddress, setToAddress] = useState<string>('');
const [amount, setAmount] = useState<MajorCurrencyAmount>();
const [modal, setModal] = useState<'send' | 'send details'>('send');
const [error, setError] = useState<string>();
const [sendError, setSendError] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [txDetails, setTxDetails] = useState<TTransactionDetails>();
const { clientDetails, userBalance, network } = useContext(AppContext);
const { fee, getFee } = useGetFee();
const handleOnNext = async () => {
if (amount) {
setIsLoading(true);
setError(undefined);
try {
await getFee(simulateSend, { address: toAddress, amount });
setModal('send details');
} catch (e) {
setError(e as string);
} finally {
setIsLoading(false);
}
} else {
setError('An amount is required');
}
};
const handleSend = async ({ val, to }: { val: MajorCurrencyAmount; to: string }) => {
setIsLoading(true);
setError(undefined);
try {
const txResponse = await send({ amount: val, address: to, memo: '', fee: fee?.fee });
setTxDetails({
amount: `${amount?.amount} ${clientDetails?.denom}`,
txUrl: `${urls(network).blockExplorer}/transaction/${txResponse.tx_hash}`,
});
} catch (e) {
Console.error(e as string);
setSendError(true);
} finally {
setIsLoading(false);
}
};
if (isLoading) return <LoadingModal />;
if (sendError) return <SendErrorModal onClose={onClose} />;
if (txDetails) return <SendSuccessModal txDetails={txDetails} onClose={onClose} />;
if (modal === 'send details')
return (
<SendDetailsModal
fromAddress={clientDetails?.client_address}
toAddress={toAddress}
amount={amount}
fee={fee}
onClose={onClose}
onPrev={() => setModal('send')}
onSend={handleSend}
/>
);
return (
<SendInputModal
fromAddress={clientDetails?.client_address}
toAddress={toAddress}
amount={amount}
balance={userBalance.balance?.printable_balance}
onClose={onClose}
onNext={handleOnNext}
error={error}
onAmountChange={(value) => setAmount(value)}
onAddressChange={(value) => setToAddress(value)}
/>
);
};
@@ -0,0 +1,19 @@
import React from 'react';
import { Stack, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { TTransactionDetails } from './types';
import { ConfirmationModal } from '../Modals/ConfirmationModal';
export const SendSuccessModal = ({ txDetails, onClose }: { txDetails: TTransactionDetails; onClose: () => void }) => (
<ConfirmationModal open onConfirm={onClose} onClose={onClose} title="" confirmButton="Done" maxWidth="xs" fullWidth>
<Stack alignItems="center" spacing={2}>
<Typography>You sent</Typography>
{txDetails && (
<>
<Typography variant="h5">{txDetails.amount}</Typography>
<Link href={txDetails.txUrl} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />
</>
)}
</Stack>
</ConfirmationModal>
);
+11
View File
@@ -0,0 +1,11 @@
import React, { useContext } from 'react';
import { AppContext } from 'src/context';
import { SendModal } from './SendModal';
export const Send = () => {
const { showSendModal, handleShowSendModal } = useContext(AppContext);
if (showSendModal) return <SendModal onClose={handleShowSendModal} />;
return null;
};
+4
View File
@@ -0,0 +1,4 @@
export type TTransactionDetails = {
amount: string;
txUrl: string;
};
+1 -1
View File
@@ -64,7 +64,7 @@ export const DelegationContextProvider: FC<{
try {
let tx;
if (tokenPool === 'locked') tx = await vestingDelegateToMixnode(data);
if (tokenPool === 'locked') tx = await vestingDelegateToMixnode({ ...data, fee });
else tx = await delegateToMixnode(data);
return tx;
+8 -1
View File
@@ -46,7 +46,9 @@ export type TAppContext = {
error?: string;
loginType?: TLoginType;
showSettings: boolean;
handleShowSettings?: () => void;
showSendModal: boolean;
handleShowSettings: () => void;
handleShowSendModal: () => void;
setIsLoading: (isLoading: boolean) => void;
setError: (value?: string) => void;
switchNetwork: (network: Network) => void;
@@ -76,6 +78,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [appVersion, setAppVersion] = useState<string>();
const [isAdminAddress, setIsAdminAddress] = useState<boolean>(false);
const [showSettings, setShowSettings] = useState(false);
const [showSendModal, setShowSendModal] = useState(false);
const userBalance = useGetBalance(clientDetails);
const navigate = useNavigate();
@@ -211,6 +214,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const handleShowTerminal = () => setShowTerminal((show) => !show);
const switchNetwork = (_network: Network) => setNetwork(_network);
const handleShowSettings = () => setShowSettings((show) => !show);
const handleShowSendModal = () => setShowSendModal((show) => !show);
const memoizedValue = useMemo(
() => ({
@@ -240,6 +244,8 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
logOut,
onAccountChange,
handleShowSettings,
showSendModal,
handleShowSendModal,
}),
[
appVersion,
@@ -257,6 +263,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
storedAccounts,
showTerminal,
showSettings,
showSendModal,
],
);
+2
View File
@@ -37,6 +37,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
showAdmin: false,
showTerminal: false,
showSettings: false,
showSendModal: true,
network: 'SANDBOX',
loginType: 'mnemonic',
setIsLoading: () => undefined,
@@ -50,6 +51,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
logOut: () => undefined,
onAccountChange: () => undefined,
handleShowSettings: () => undefined,
handleShowSendModal: () => undefined,
}),
[],
);
+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."
/>
)}
</>
+2 -2
View File
@@ -1,4 +1,4 @@
import { MajorCurrencyAmount, SendTxResult, TransactionExecuteResult } from '@nymproject/types';
import { Fee, MajorCurrencyAmount, SendTxResult, TransactionExecuteResult } from '@nymproject/types';
import { EnumNodeType, TBondArgs, TBondGatewayArgs, TBondMixNodeArgs } from '../types';
import { invokeWrapper } from './wrapper';
@@ -15,7 +15,7 @@ export const unbondMixNode = async () => invokeWrapper<TransactionExecuteResult>
export const updateMixnode = async (profitMarginPercent: number) =>
invokeWrapper<TransactionExecuteResult>('update_mixnode', { profitMarginPercent });
export const send = async (args: { amount: MajorCurrencyAmount; address: string; memo: string }) =>
export const send = async (args: { amount: MajorCurrencyAmount; address: string; memo: string; fee?: Fee }) =>
invokeWrapper<SendTxResult>('send', args);
export const unbond = async (type: EnumNodeType) => {
+5 -2
View File
@@ -50,5 +50,8 @@ export const simulateVestingUnbondMixnode = async (args: any) =>
export const simulateVestingUpdateMixnode = async (args: any) =>
invokeWrapper<FeeDetails>('simulate_vesting_update_mixnode', args);
export const simulateWithdrawVestedCoins = async ({ amount }: { amount: MajorCurrencyAmount }) =>
invokeWrapper<FeeDetails>('simulate_withdraw_vested_coins', { amount });
export const simulateWithdrawVestedCoins = async (args: any) =>
invokeWrapper<FeeDetails>('simulate_withdraw_vested_coins', args);
export const simulateSend = async ({ address, amount }: { address: string; amount: MajorCurrencyAmount }) =>
invokeWrapper<FeeDetails>('simulate_send', { address, amount });
+3 -2
View File
@@ -2,15 +2,16 @@ import React from 'react';
import { Route, Routes } from 'react-router-dom';
import { ApplicationLayout } from 'src/layouts';
import { Terminal } from 'src/pages/terminal';
import { Bond, Balance, InternalDocs, Receive, Send, Unbond, DelegationPage, Admin, Settings } from '../pages';
import { Send } from 'src/components/Send';
import { Bond, Balance, InternalDocs, Receive, Unbond, DelegationPage, Admin, Settings } from '../pages';
export const AppRoutes = () => (
<ApplicationLayout>
<Terminal />
<Settings />
<Send />
<Routes>
<Route path="/balance" element={<Balance />} />
<Route path="/send" element={<Send />} />
<Route path="/receive" element={<Receive />} />
<Route path="/bond" element={<Bond />} />
<Route path="/unbond" element={<Unbond />} />
@@ -1,4 +1,4 @@
import type { Fee } from "./Fee";
import type { MajorCurrencyAmount } from "./Currency";
import type { Fee } from './Fee';
import type { MajorCurrencyAmount } from './Currency';
export type FeeDetails = { amount: MajorCurrencyAmount | null, fee: Fee };
export type FeeDetails = { amount: MajorCurrencyAmount | null; fee: Fee };
@@ -13,6 +13,7 @@ export * from './DelegationRecord';
export * from './DelegationResult';
export * from './DelegationSummaryResponse';
export * from './DelegationWithEverything';
export * from './Fee';
export * from './FeeDetails';
export * from './Gas';
export * from './GasInfo';