Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 58ef536507 | |||
| af1c2cfa2b | |||
| c582d6dcba |
@@ -9,7 +9,7 @@ MIX_DENOM_DISPLAY=nym
|
||||
STAKE_DENOM=unyx
|
||||
STAKE_DENOM_DISPLAY=nyx
|
||||
DENOMS_EXPONENT=6
|
||||
MIXNET_CONTRACT_ADDRESS=n1rjzps6qrmdqmf0xz4cn4x4rcmqeqzq6hnzqg4wcvd0r2lyasdq5sepn5s8
|
||||
MIXNET_CONTRACT_ADDRESS=n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep
|
||||
VESTING_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
|
||||
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Button, Stack, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { TBondedMixnode, urls } from 'src/context';
|
||||
@@ -56,11 +55,8 @@ export const BondedMixnode = ({
|
||||
network?: Network;
|
||||
onActionSelect: (action: TBondedMixnodeActions) => void;
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { name, stake, bond, stakeSaturation, profitMargin, operatorRewards, delegators, status, identityKey } =
|
||||
mixnode;
|
||||
|
||||
const cells: Cell[] = [
|
||||
{
|
||||
cell: `${stake.amount} ${stake.denom}`,
|
||||
@@ -118,16 +114,14 @@ export const BondedMixnode = ({
|
||||
</Stack>
|
||||
}
|
||||
Action={
|
||||
mixnode.type === 'mixnode' && (
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => navigate('/bonding/node-settings')}
|
||||
startIcon={<NodeIcon />}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
)
|
||||
<Button
|
||||
variant="text"
|
||||
color="secondary"
|
||||
onClick={() => onActionSelect('nodeSettings')}
|
||||
startIcon={<NodeIcon />}
|
||||
>
|
||||
Settings
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<NodeTable headers={headers} cells={cells} />
|
||||
|
||||
@@ -12,7 +12,6 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
|
||||
//Now we are using the node setting page instead of this modal
|
||||
export const NodeSettings = ({
|
||||
currentPm,
|
||||
isVesting,
|
||||
@@ -20,13 +19,13 @@ export const NodeSettings = ({
|
||||
onClose,
|
||||
onError,
|
||||
}: {
|
||||
currentPm?: TBondedMixnode['profitMargin'];
|
||||
isVesting?: boolean;
|
||||
currentPm: TBondedMixnode['profitMargin'];
|
||||
isVesting: boolean;
|
||||
onConfirm: (profitMargin: string, fee?: FeeDetails) => Promise<void>;
|
||||
onClose: () => void;
|
||||
onError: (err: string) => void;
|
||||
}) => {
|
||||
const [pm, setPm] = useState(currentPm?.toString());
|
||||
const [pm, setPm] = useState(currentPm.toString());
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const { fee, getFee, resetFeeState, isFeeLoading, feeError } = useGetFee();
|
||||
@@ -53,15 +52,13 @@ export const NodeSettings = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (pm) {
|
||||
// TODO: this will have to be updated with allowing users to provide their operating cost in the form
|
||||
const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(pm));
|
||||
// TODO: this will have to be updated with allowing users to provide their operating cost in the form
|
||||
const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(pm));
|
||||
|
||||
if (isVesting) {
|
||||
await getFee(simulateVestingUpdateMixnodeCostParams, defaultCostParams);
|
||||
} else {
|
||||
await getFee(simulateUpdateMixnodeCostParams, defaultCostParams);
|
||||
}
|
||||
if (isVesting) {
|
||||
await getFee(simulateVestingUpdateMixnodeCostParams, defaultCostParams);
|
||||
} else {
|
||||
await getFee(simulateUpdateMixnodeCostParams, defaultCostParams);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,7 +74,7 @@ export const NodeSettings = ({
|
||||
|
||||
if (isFeeLoading) return <LoadingModal />;
|
||||
|
||||
if (fee && pm)
|
||||
if (fee)
|
||||
return (
|
||||
<ConfirmTx
|
||||
open
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import { Box, Button, Modal, Stack, SxProps, Typography } from '@mui/material';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ErrorOutline from '@mui/icons-material/ErrorOutline';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { StyledBackButton } from 'src/components/StyledBackButton';
|
||||
import { modalStyle } from './styles';
|
||||
|
||||
@@ -10,10 +9,8 @@ export const SimpleModal: React.FC<{
|
||||
open: boolean;
|
||||
hideCloseIcon?: boolean;
|
||||
displayErrorIcon?: boolean;
|
||||
displayInfoIcon?: boolean;
|
||||
headerStyles?: SxProps;
|
||||
subHeaderStyles?: SxProps;
|
||||
buttonFullWidth?: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: () => Promise<void>;
|
||||
onBack?: () => void;
|
||||
@@ -27,10 +24,8 @@ export const SimpleModal: React.FC<{
|
||||
open,
|
||||
hideCloseIcon,
|
||||
displayErrorIcon,
|
||||
displayInfoIcon,
|
||||
headerStyles,
|
||||
subHeaderStyles,
|
||||
buttonFullWidth,
|
||||
onClose,
|
||||
okDisabled,
|
||||
onOk,
|
||||
@@ -45,7 +40,6 @@ export const SimpleModal: React.FC<{
|
||||
<Modal open={open} onClose={onClose} BackdropProps={backdropProps}>
|
||||
<Box sx={{ border: (t) => `1px solid ${t.palette.nym.nymWallet.modal.border}`, ...modalStyle, ...sx }}>
|
||||
{displayErrorIcon && <ErrorOutline color="error" sx={{ mb: 3 }} />}
|
||||
{displayInfoIcon && <InfoOutlinedIcon sx={{ mb: 2, color: (theme) => theme.palette.nym.nymWallet.text.blue }} />}
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
{typeof header === 'string' ? (
|
||||
<Typography fontSize={20} fontWeight={600} sx={{ color: 'text.primary', ...headerStyles }}>
|
||||
@@ -70,8 +64,8 @@ export const SimpleModal: React.FC<{
|
||||
{children}
|
||||
|
||||
{(onOk || onBack) && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 2, width: buttonFullWidth ? '100%' : null }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} />}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 1 }}>
|
||||
{onBack && <StyledBackButton onBack={onBack} sx={{ mt: 3 }} />}
|
||||
{onOk && (
|
||||
<Button variant="contained" fullWidth size="large" onClick={onOk} disabled={okDisabled} sx={{ mt: 3 }}>
|
||||
{okLabel}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Tab, Tabs as MuiTabs, SxProps } from '@mui/material';
|
||||
import { Tab, Tabs as MuiTabs } from '@mui/material';
|
||||
|
||||
export const Tabs: React.FC<{
|
||||
tabs: string[];
|
||||
@@ -7,9 +7,7 @@ export const Tabs: React.FC<{
|
||||
disabled?: boolean;
|
||||
onChange?: (event: React.SyntheticEvent, tab: number) => void;
|
||||
disableActiveTabHighlight?: boolean;
|
||||
tabSx?: SxProps;
|
||||
tabIndicatorStyles?: {};
|
||||
}> = ({ tabs, selectedTab, disabled, disableActiveTabHighlight, onChange, tabSx, tabIndicatorStyles }) => (
|
||||
}> = ({ tabs, selectedTab, disabled, disableActiveTabHighlight, onChange }) => (
|
||||
<MuiTabs
|
||||
value={selectedTab}
|
||||
onChange={onChange}
|
||||
@@ -18,15 +16,17 @@ export const Tabs: React.FC<{
|
||||
borderTop: '1px solid',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: (theme) => theme.palette.nym.nymWallet.background.greyStroke,
|
||||
...tabSx,
|
||||
}}
|
||||
textColor="inherit"
|
||||
TabIndicatorProps={{
|
||||
style: {
|
||||
opacity: disableActiveTabHighlight ? 0 : 1,
|
||||
...tabIndicatorStyles,
|
||||
},
|
||||
}}
|
||||
TabIndicatorProps={
|
||||
disableActiveTabHighlight
|
||||
? {
|
||||
style: {
|
||||
opacity: 0,
|
||||
},
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
{tabs.map((tabName) => (
|
||||
<Tab key={tabName} label={tabName} sx={{ textTransform: 'capitalize' }} disabled={disabled} />
|
||||
|
||||
@@ -35,7 +35,6 @@ import { attachDefaultOperatingCost, toPercentFloatString, toPercentIntegerStrin
|
||||
|
||||
// TODO add relevant data
|
||||
export type TBondedMixnode = {
|
||||
type: 'mixnode';
|
||||
name?: string;
|
||||
identityKey: string;
|
||||
stake: DecCoin;
|
||||
@@ -46,26 +45,15 @@ export type TBondedMixnode = {
|
||||
delegators: number;
|
||||
status: MixnodeStatus;
|
||||
proxy?: string;
|
||||
host: string;
|
||||
httpApiPort: number;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export interface TBondedGateway {
|
||||
type: 'gateway';
|
||||
name: string;
|
||||
identityKey: string;
|
||||
ip: string;
|
||||
bond: DecCoin;
|
||||
location?: string; // TODO not yet available, only available in Network Explorer API
|
||||
proxy?: string;
|
||||
host: string;
|
||||
httpApiPort: number;
|
||||
mixPort: number;
|
||||
verlocPort: number;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export type TokenPool = 'locked' | 'balance';
|
||||
@@ -167,33 +155,26 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
Console.warn(`get_operator_rewards request failed: ${e}`);
|
||||
}
|
||||
if (data) {
|
||||
const { bond_information, rewarding_details } = data;
|
||||
const { status, stakeSaturation } = await getAdditionalMixnodeDetails(bond_information.id);
|
||||
const { status, stakeSaturation } = await getAdditionalMixnodeDetails(data.bond_information.id);
|
||||
const nodeDescription = await getNodeDescription(
|
||||
bond_information.mix_node.host,
|
||||
bond_information.mix_node.http_api_port,
|
||||
data.bond_information.mix_node.host,
|
||||
data.bond_information.mix_node.http_api_port,
|
||||
);
|
||||
setBondedNode({
|
||||
type: ownership.nodeType,
|
||||
name: nodeDescription?.name,
|
||||
identityKey: bond_information.mix_node.identity_key,
|
||||
ip: bond_information.id,
|
||||
identityKey: data.bond_information.mix_node.identity_key,
|
||||
ip: '',
|
||||
stake: {
|
||||
amount: calculateStake(rewarding_details.operator, data.rewarding_details.delegates).toString(),
|
||||
denom: bond_information.original_pledge.denom,
|
||||
amount: calculateStake(data.rewarding_details.operator, data.rewarding_details.delegates).toString(),
|
||||
denom: data.bond_information.original_pledge.denom,
|
||||
},
|
||||
bond: bond_information.original_pledge,
|
||||
profitMargin: toPercentIntegerString(rewarding_details.cost_params.profit_margin_percent),
|
||||
delegators: rewarding_details.unique_delegations,
|
||||
proxy: bond_information.proxy,
|
||||
bond: data.bond_information.original_pledge,
|
||||
profitMargin: toPercentIntegerString(data.rewarding_details.cost_params.profit_margin_percent),
|
||||
delegators: data.rewarding_details.unique_delegations,
|
||||
proxy: data.bond_information.proxy,
|
||||
operatorRewards,
|
||||
status,
|
||||
stakeSaturation,
|
||||
host: bond_information.mix_node.host.replace(/\s/g, ''),
|
||||
httpApiPort: bond_information.mix_node.http_api_port,
|
||||
mixPort: bond_information.mix_node.mix_port,
|
||||
verlocPort: bond_information.mix_node.verloc_port,
|
||||
version: bond_information.mix_node.version,
|
||||
} as TBondedMixnode);
|
||||
}
|
||||
} catch (e: any) {
|
||||
@@ -209,7 +190,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
|
||||
const nodeDescription = await getNodeDescription(data.gateway.host, data.gateway.clients_port);
|
||||
|
||||
setBondedNode({
|
||||
type: ownership.nodeType,
|
||||
name: nodeDescription?.name,
|
||||
identityKey: data.gateway.identity_key,
|
||||
ip: data.gateway.host,
|
||||
|
||||
@@ -7,7 +7,6 @@ import { mockSleep } from './utils';
|
||||
const SLEEP_MS = 1000;
|
||||
|
||||
const bondedMixnodeMock: TBondedMixnode = {
|
||||
type: 'mixnode',
|
||||
name: 'Monster node',
|
||||
identityKey: '7mjM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
|
||||
stake: { denom: 'nym', amount: '1234' },
|
||||
@@ -17,24 +16,13 @@ const bondedMixnodeMock: TBondedMixnode = {
|
||||
operatorRewards: { denom: 'nym', amount: '1234' },
|
||||
delegators: 5423,
|
||||
status: 'active',
|
||||
host: '1.2.34.5 ',
|
||||
httpApiPort: 8000,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
version: '1.0.2',
|
||||
};
|
||||
|
||||
const bondedGatewayMock: TBondedGateway = {
|
||||
type: 'gateway',
|
||||
name: 'Monster node',
|
||||
identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F',
|
||||
ip: '112.43.234.57',
|
||||
bond: { denom: 'nym', amount: '1234' },
|
||||
host: '1.2.34.5 ',
|
||||
httpApiPort: 8000,
|
||||
mixPort: 1789,
|
||||
verlocPort: 1790,
|
||||
version: '1.0.2',
|
||||
};
|
||||
|
||||
const TxResultMock: TransactionExecuteResult = {
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { Bond } from 'src/components/Bonding/Bond';
|
||||
import { BondedMixnode } from 'src/components/Bonding/BondedMixnode';
|
||||
import { TBondedMixnodeActions } from 'src/components/Bonding/BondedMixnodeActions';
|
||||
import { BondGatewayModal } from 'src/components/Bonding/modals/BondGatewayModal';
|
||||
import { BondMixnodeModal } from 'src/components/Bonding/modals/BondMixnodeModal';
|
||||
import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal';
|
||||
import { UnbondModal } from 'src/components/Bonding/modals/UnbondModal';
|
||||
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { AppContext, urls } from 'src/context/main';
|
||||
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
|
||||
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
|
||||
import { BondingContextProvider, useBondingContext, TBondedMixnode } from '../../context';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
const Bonding = () => {
|
||||
const [showModal, setShowModal] = useState<'bond-mixnode' | 'bond-gateway' | 'bond-more' | 'unbond' | 'redeem'>();
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>();
|
||||
|
||||
const {
|
||||
network,
|
||||
clientDetails,
|
||||
userBalance: { originalVesting },
|
||||
} = useContext(AppContext);
|
||||
|
||||
const {
|
||||
bondedNode,
|
||||
bondMixnode,
|
||||
bondGateway,
|
||||
unbond,
|
||||
updateMixnode,
|
||||
redeemRewards,
|
||||
// compoundRewards,
|
||||
isLoading,
|
||||
checkOwnership,
|
||||
} = useBondingContext();
|
||||
|
||||
const handleCloseModal = async () => {
|
||||
setShowModal(undefined);
|
||||
await checkOwnership();
|
||||
};
|
||||
|
||||
const handleError = (error: string) => {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: error,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBondMixnode = async (data: TBondMixNodeArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondMixnode(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const handleBondGateway = async (data: TBondGatewayArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondGateway(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUnbond = async (fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await unbond(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Unbond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRedeemReward = async (fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await redeemRewards(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Rewards redeemed successfully',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBondedMixnodeAction = (action: TBondedMixnodeActions) => {
|
||||
switch (action) {
|
||||
case 'bondMore': {
|
||||
setShowModal('bond-more');
|
||||
break;
|
||||
}
|
||||
case 'unbond': {
|
||||
setShowModal('unbond');
|
||||
break;
|
||||
}
|
||||
case 'redeem': {
|
||||
setShowModal('redeem');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
{!bondedNode && <Bond disabled={isLoading} onBond={() => setShowModal('bond-mixnode')} />}
|
||||
|
||||
{bondedNode && isMixnode(bondedNode) && (
|
||||
<BondedMixnode
|
||||
mixnode={bondedNode}
|
||||
network={network}
|
||||
onActionSelect={(action) => handleBondedMixnodeAction(action)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{bondedNode && isGateway(bondedNode) && (
|
||||
<BondedGateway gateway={bondedNode} onActionSelect={handleBondedMixnodeAction} network={network} />
|
||||
)}
|
||||
{showModal === 'bond-mixnode' && (
|
||||
<BondMixnodeModal
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
hasVestingTokens={Boolean(originalVesting)}
|
||||
onBondMixnode={handleBondMixnode}
|
||||
onSelectNodeType={() => setShowModal('bond-gateway')}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'bond-gateway' && (
|
||||
<BondGatewayModal
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
hasVestingTokens={Boolean(originalVesting)}
|
||||
onBondGateway={handleBondGateway}
|
||||
onSelectNodeType={() => setShowModal('bond-mixnode')}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'unbond' && bondedNode && (
|
||||
<UnbondModal
|
||||
node={bondedNode}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onConfirm={handleUnbond}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && (
|
||||
<RedeemRewardsModal
|
||||
node={bondedNode}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onConfirm={handleRedeemReward}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmationDetails && confirmationDetails.status === 'success' && (
|
||||
<ConfirmationDetailsModal
|
||||
title={confirmationDetails.title}
|
||||
subtitle={confirmationDetails.subtitle || 'This operation can take up to one hour to process'}
|
||||
status={confirmationDetails.status}
|
||||
txUrl={confirmationDetails.txUrl}
|
||||
onClose={() => {
|
||||
setConfirmationDetails(undefined);
|
||||
handleCloseModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmationDetails && confirmationDetails.status === 'error' && (
|
||||
<ErrorModal open message={confirmationDetails.subtitle} onClose={() => setConfirmationDetails(undefined)} />
|
||||
)}
|
||||
|
||||
{isLoading && <LoadingModal />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const BondingPage = () => (
|
||||
<BondingContextProvider>
|
||||
<Bonding />
|
||||
</BondingContextProvider>
|
||||
);
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { BondingPage } from './Bonding';
|
||||
import { BondingPage } from './index';
|
||||
import { MockBondingContextProvider } from '../../context/mocks/bonding';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,2 +1,237 @@
|
||||
export * from './Bonding';
|
||||
export * from './node-settings';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { Bond } from 'src/components/Bonding/Bond';
|
||||
import { BondedMixnode } from 'src/components/Bonding/BondedMixnode';
|
||||
import { TBondedMixnodeActions } from 'src/components/Bonding/BondedMixnodeActions';
|
||||
import { BondGatewayModal } from 'src/components/Bonding/modals/BondGatewayModal';
|
||||
import { BondMixnodeModal } from 'src/components/Bonding/modals/BondMixnodeModal';
|
||||
import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal';
|
||||
import { NodeSettings } from 'src/components/Bonding/modals/NodeSettingsModal';
|
||||
import { UnbondModal } from 'src/components/Bonding/modals/UnbondModal';
|
||||
import { ErrorModal } from 'src/components/Modals/ErrorModal';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { AppContext, urls } from 'src/context/main';
|
||||
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
|
||||
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
|
||||
import { Box } from '@mui/material';
|
||||
import { BondingContextProvider, useBondingContext } from '../../context';
|
||||
|
||||
const Bonding = () => {
|
||||
const [showModal, setShowModal] = useState<
|
||||
'bond-mixnode' | 'bond-gateway' | 'bond-more' | 'unbond' | 'redeem' | 'compound' | 'node-settings'
|
||||
>();
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>();
|
||||
|
||||
const {
|
||||
network,
|
||||
clientDetails,
|
||||
userBalance: { originalVesting },
|
||||
} = useContext(AppContext);
|
||||
|
||||
const {
|
||||
bondedNode,
|
||||
bondMixnode,
|
||||
bondGateway,
|
||||
unbond,
|
||||
updateMixnode,
|
||||
redeemRewards,
|
||||
isLoading,
|
||||
checkOwnership,
|
||||
error,
|
||||
} = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: error,
|
||||
});
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const handleCloseModal = async () => {
|
||||
setShowModal(undefined);
|
||||
await checkOwnership();
|
||||
};
|
||||
|
||||
const handleError = (e: string) => {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: e,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBondMixnode = async (data: TBondMixNodeArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondMixnode(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const handleBondGateway = async (data: TBondGatewayArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondGateway(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUnbond = async (fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await unbond(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Unbond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateProfitMargin = async (profitMargin: string, fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await updateMixnode(profitMargin, fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Profit margin update successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRedeemReward = async (fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await redeemRewards(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Rewards redeemed successfully',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBondedMixnodeAction = (action: TBondedMixnodeActions) => {
|
||||
switch (action) {
|
||||
case 'bondMore': {
|
||||
setShowModal('bond-more');
|
||||
break;
|
||||
}
|
||||
case 'unbond': {
|
||||
setShowModal('unbond');
|
||||
break;
|
||||
}
|
||||
case 'redeem': {
|
||||
setShowModal('redeem');
|
||||
break;
|
||||
}
|
||||
case 'nodeSettings': {
|
||||
setShowModal('node-settings');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
{!bondedNode && <Bond disabled={isLoading} onBond={() => setShowModal('bond-mixnode')} />}
|
||||
|
||||
{bondedNode && isMixnode(bondedNode) && (
|
||||
<BondedMixnode
|
||||
mixnode={bondedNode}
|
||||
network={network}
|
||||
onActionSelect={(action) => handleBondedMixnodeAction(action)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{bondedNode && isGateway(bondedNode) && (
|
||||
<BondedGateway gateway={bondedNode} onActionSelect={handleBondedMixnodeAction} network={network} />
|
||||
)}
|
||||
{showModal === 'bond-mixnode' && (
|
||||
<BondMixnodeModal
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
hasVestingTokens={Boolean(originalVesting)}
|
||||
onBondMixnode={handleBondMixnode}
|
||||
onSelectNodeType={() => setShowModal('bond-gateway')}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'bond-gateway' && (
|
||||
<BondGatewayModal
|
||||
denom={clientDetails?.display_mix_denom || 'nym'}
|
||||
hasVestingTokens={Boolean(originalVesting)}
|
||||
onBondGateway={handleBondGateway}
|
||||
onSelectNodeType={() => setShowModal('bond-mixnode')}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'unbond' && bondedNode && (
|
||||
<UnbondModal
|
||||
node={bondedNode}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onConfirm={handleUnbond}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && (
|
||||
<RedeemRewardsModal
|
||||
node={bondedNode}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onConfirm={handleRedeemReward}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'node-settings' && bondedNode && isMixnode(bondedNode) && (
|
||||
<NodeSettings
|
||||
currentPm={bondedNode.profitMargin}
|
||||
isVesting={Boolean(bondedNode.proxy)}
|
||||
onConfirm={handleUpdateProfitMargin}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmationDetails && confirmationDetails.status === 'success' && (
|
||||
<ConfirmationDetailsModal
|
||||
title={confirmationDetails.title}
|
||||
subtitle={confirmationDetails.subtitle || 'This operation can take up to one hour to process'}
|
||||
status={confirmationDetails.status}
|
||||
txUrl={confirmationDetails.txUrl}
|
||||
onClose={() => {
|
||||
setConfirmationDetails(undefined);
|
||||
handleCloseModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{confirmationDetails && confirmationDetails.status === 'error' && (
|
||||
<ErrorModal open message={confirmationDetails.subtitle} onClose={() => setConfirmationDetails(undefined)} />
|
||||
)}
|
||||
|
||||
{isLoading && <LoadingModal />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const BondingPage = () => (
|
||||
<BondingContextProvider>
|
||||
<Bonding />
|
||||
</BondingContextProvider>
|
||||
);
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Typography, TextField, Grid, Alert, IconButton } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { TBondedMixnode, TBondedGateway } from '../../../../context/bonding';
|
||||
import { SimpleModal } from '../../../../components/Modals/SimpleModal';
|
||||
|
||||
const getNumberlength = (number: number) => {
|
||||
return number.toString().length;
|
||||
};
|
||||
|
||||
// TODO: adding ip regex that works well
|
||||
const ipRegex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
// TODO: only accept valid nym wallet versions
|
||||
const appVersionRegex = /^\d+(?:\.\d+){2}$/gm;
|
||||
|
||||
export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
|
||||
const { mixPort, verlocPort, httpApiPort, host, version } = bondedNode;
|
||||
|
||||
const [buttonActive, setButtonActive] = useState<boolean>(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||
const [mixPortUpdated, setMixPortUpdated] = useState<number>(mixPort);
|
||||
const [verlocPortUpdated, setVerlocPortUpdated] = useState<number>(verlocPort);
|
||||
const [httpApiPortUpdated, setHttpApiPortUpdated] = useState<number>(httpApiPort);
|
||||
const [hostUpdated, setHostUpdated] = useState<string>(host);
|
||||
const [versionUpdated, setVersionUpdated] = useState<string>(version);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
setButtonActive(true);
|
||||
if (
|
||||
mixPortUpdated === mixPort &&
|
||||
verlocPortUpdated === verlocPort &&
|
||||
httpApiPortUpdated === httpApiPort &&
|
||||
hostUpdated === host &&
|
||||
versionUpdated === version
|
||||
) {
|
||||
setButtonActive(false);
|
||||
}
|
||||
if (
|
||||
getNumberlength(mixPortUpdated) !== 4 ||
|
||||
getNumberlength(verlocPortUpdated) !== 4 ||
|
||||
getNumberlength(httpApiPortUpdated) !== 4 ||
|
||||
!versionUpdated.match(appVersionRegex)
|
||||
) {
|
||||
setButtonActive(false);
|
||||
}
|
||||
}, [mixPortUpdated, verlocPortUpdated, httpApiPortUpdated, hostUpdated, versionUpdated]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { value, id } = e.target;
|
||||
const numNewValue = parseInt(value) || 0;
|
||||
|
||||
switch (id) {
|
||||
case 'mixPort':
|
||||
setMixPortUpdated(numNewValue);
|
||||
break;
|
||||
case 'verlocPort':
|
||||
setVerlocPortUpdated(numNewValue);
|
||||
break;
|
||||
case 'httpApiPort':
|
||||
setHttpApiPortUpdated(numNewValue);
|
||||
break;
|
||||
case 'host':
|
||||
setHostUpdated(value);
|
||||
break;
|
||||
case 'version':
|
||||
setVersionUpdated(value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container xs>
|
||||
{open && (
|
||||
<Alert
|
||||
severity="info"
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{
|
||||
px: 2,
|
||||
borderRadius: 0,
|
||||
bgcolor: 'background.default',
|
||||
color: (theme) => theme.palette.nym.nymWallet.text.blue,
|
||||
'& .MuiAlert-icon': { color: (theme) => theme.palette.nym.nymWallet.text.blue, mr: 1 },
|
||||
}}
|
||||
>
|
||||
<strong>Your changes will be ONLY saved on the display.</strong> Remember to change the values on your node’s
|
||||
config file too.
|
||||
</Alert>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
|
||||
<Grid item>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Port
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Change profit margin of your node
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
<TextField
|
||||
id="mixPort"
|
||||
type="input"
|
||||
label="Mix Port"
|
||||
value={mixPortUpdated}
|
||||
onChange={(e) => handleChange(e)}
|
||||
inputProps={{ maxLength: 4 }}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item width={1}>
|
||||
<TextField
|
||||
id="verlocPort"
|
||||
type="input"
|
||||
label="Verloc Port"
|
||||
value={verlocPortUpdated}
|
||||
onChange={(e) => handleChange(e)}
|
||||
inputProps={{ maxLength: 4 }}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item width={1}>
|
||||
<TextField
|
||||
id="httpApiPort"
|
||||
type="input"
|
||||
label="HTTP port"
|
||||
value={httpApiPortUpdated}
|
||||
onChange={(e) => handleChange(e)}
|
||||
inputProps={{ maxLength: 4 }}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
|
||||
<Grid item>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Host
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Lock wallet after certain time
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
<TextField
|
||||
id="host"
|
||||
type="input"
|
||||
label="host"
|
||||
value={hostUpdated}
|
||||
onChange={(e) => handleChange(e)}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
|
||||
<Grid item>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Version
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Lock wallet after certain time
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1}>
|
||||
<TextField
|
||||
id="version"
|
||||
type="input"
|
||||
label="Version"
|
||||
value={versionUpdated}
|
||||
onChange={(e) => handleChange(e)}
|
||||
fullWidth
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Grid container justifyContent="end">
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={!buttonActive}
|
||||
onClick={() => setOpenConfirmationModal(true)}
|
||||
sx={{ m: 3, width: '320px' }}
|
||||
>
|
||||
Save all display changes
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<SimpleModal
|
||||
open={openConfirmationModal}
|
||||
header="Your changes were ONLY saved on the display"
|
||||
subHeader="Remember to change the values
|
||||
on your node’s config file too."
|
||||
okLabel="close"
|
||||
hideCloseIcon
|
||||
displayInfoIcon
|
||||
onOk={async () => {
|
||||
await setOpenConfirmationModal(false);
|
||||
}}
|
||||
buttonFullWidth
|
||||
sx={{
|
||||
width: '450px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
headerStyles={{
|
||||
width: '100%',
|
||||
mb: 1,
|
||||
textAlign: 'center',
|
||||
color: theme.palette.nym.nymWallet.text.blue,
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
subHeaderStyles={{
|
||||
width: '100%',
|
||||
mb: 1,
|
||||
textAlign: 'center',
|
||||
color: 'main',
|
||||
fontSize: 14,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -1,207 +0,0 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Button, Divider, Typography, TextField, InputAdornment, Grid, Alert, IconButton } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { TBondedMixnode, TBondedGateway } from '../../../../context/bonding';
|
||||
import { SimpleModal } from '../../../../components/Modals/SimpleModal';
|
||||
|
||||
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
|
||||
const { bond, type } = bondedNode;
|
||||
|
||||
const [buttonActive, setButtonActive] = useState<boolean>(false);
|
||||
const [open, setOpen] = useState(true);
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
|
||||
const [profitMarginPercent, setProfitMarginPercent] = useState<string>(
|
||||
bondedNode.type === 'mixnode' ? bondedNode.profitMargin : '',
|
||||
);
|
||||
const [operatorCost, setOperatorCost] = useState<number>(parseInt(bond.amount));
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
type === 'mixnode' &&
|
||||
bondedNode.profitMargin === profitMarginPercent &&
|
||||
operatorCost === parseInt(bond.amount)
|
||||
) {
|
||||
setButtonActive(false);
|
||||
} else {
|
||||
setButtonActive(true);
|
||||
}
|
||||
}, [profitMarginPercent, operatorCost]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
const { value, id } = e.target;
|
||||
const numNewValue = parseInt(value) || 0;
|
||||
switch (id) {
|
||||
case 'profitMargin':
|
||||
setProfitMarginPercent(value);
|
||||
break;
|
||||
case 'operatorCost':
|
||||
setOperatorCost(numNewValue);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Something could be useful to update the profitMargin
|
||||
// const handleUpdateProfitMargin = async (profitMargin: number, fee?: FeeDetails) => {
|
||||
// setShowModal(undefined);
|
||||
// const tx = await updateMixnode(profitMargin, fee);
|
||||
// setConfirmationDetails({
|
||||
// status: 'success',
|
||||
// title: 'Profit margin update successful',
|
||||
// txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
// });
|
||||
// };
|
||||
|
||||
return (
|
||||
<Grid container xs>
|
||||
{open && (
|
||||
<Alert
|
||||
severity="info"
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="close"
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{
|
||||
width: 1,
|
||||
px: 2,
|
||||
borderRadius: 0,
|
||||
bgcolor: 'background.default',
|
||||
color: (theme) => theme.palette.nym.nymWallet.text.blue,
|
||||
'& .MuiAlert-icon': { color: (theme) => theme.palette.nym.nymWallet.text.blue, mr: 1 },
|
||||
}}
|
||||
>
|
||||
<strong>Profit margin can be changed once a month, your changes will be applied in the next interval</strong>
|
||||
</Alert>
|
||||
)}
|
||||
<Grid container direction="column">
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
||||
<Grid item direction="column">
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Profit Margin
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Profit margin can be changed once a month
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
{type === 'mixnode' && (
|
||||
<Grid item width={1} spacing={3}>
|
||||
<TextField
|
||||
id="profitMargin"
|
||||
type="input"
|
||||
label="Profit margin"
|
||||
value={profitMarginPercent}
|
||||
onChange={(e) => handleChange(e)}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<span>%</span>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
|
||||
<Grid item direction="column">
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
Operator cost
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
mb: 2,
|
||||
color: (t) => (t.palette.mode === 'light' ? t.palette.nym.text.muted : 'text.primary'),
|
||||
}}
|
||||
>
|
||||
Lock Wallet after a certain time
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
|
||||
<Grid item width={1} spacing={3}>
|
||||
<TextField
|
||||
id="operatorCost"
|
||||
type="input"
|
||||
label="Operator cost"
|
||||
value={operatorCost}
|
||||
onChange={(e) => handleChange(e)}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<span>{bond.denom.toUpperCase()}</span>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Divider flexItem />
|
||||
<Grid container justifyContent="end">
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={!buttonActive}
|
||||
onClick={() => setOpenConfirmationModal(true)}
|
||||
sx={{ m: 3, width: '320px' }}
|
||||
>
|
||||
Save all display changes
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<SimpleModal
|
||||
open={openConfirmationModal}
|
||||
header="Your changes will take place
|
||||
in the next interval"
|
||||
okLabel="close"
|
||||
hideCloseIcon
|
||||
displayInfoIcon
|
||||
onOk={async () => {
|
||||
await setOpenConfirmationModal(false);
|
||||
}}
|
||||
buttonFullWidth
|
||||
sx={{
|
||||
width: '320px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
headerStyles={{
|
||||
width: '100%',
|
||||
mb: 1,
|
||||
textAlign: 'center',
|
||||
color: theme.palette.nym.nymWallet.text.blue,
|
||||
fontSize: 16,
|
||||
textTransform: 'capitalize',
|
||||
}}
|
||||
subHeaderStyles={{
|
||||
m: 0,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Divider, Grid } from '@mui/material';
|
||||
import { TBondedMixnode, TBondedGateway } from '../../../../context/bonding';
|
||||
import { InfoSettings } from './InfoSettings';
|
||||
import { ParametersSettings } from './ParametersSettings';
|
||||
|
||||
const nodeGeneralNav = ['Info', 'Parameters'];
|
||||
|
||||
export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
|
||||
const [settingsCard, setSettingsCard] = useState<string>(nodeGeneralNav[0]);
|
||||
//TODO: Check what happens with a gateway
|
||||
return (
|
||||
<Box sx={{ pl: 3, pt: 3 }}>
|
||||
<Grid container direction="row" spacing={3}>
|
||||
<Grid item container direction="column" xs={3}>
|
||||
{nodeGeneralNav.map((item) => (
|
||||
<Button
|
||||
size="small"
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
color: settingsCard === item ? 'primary.main' : 'inherit',
|
||||
justifyContent: 'start',
|
||||
':hover': {
|
||||
bgcolor: 'transparent',
|
||||
color: 'primary.main',
|
||||
},
|
||||
}}
|
||||
key={item}
|
||||
onClick={() => setSettingsCard(item)}
|
||||
>
|
||||
{item}
|
||||
</Button>
|
||||
))}
|
||||
</Grid>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
{settingsCard === nodeGeneralNav[0] && <InfoSettings bondedNode={bondedNode} />}
|
||||
{settingsCard === nodeGeneralNav[1] && <ParametersSettings bondedNode={bondedNode} />}
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,149 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { Box, Typography, Stack, Button, Divider } from '@mui/material';
|
||||
import { Close } from '@mui/icons-material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal';
|
||||
import { Node as NodeIcon } from 'src/svg-icons/node';
|
||||
import { NymCard } from '../../../components';
|
||||
import { PageLayout } from '../../../layouts';
|
||||
import { Tabs } from 'src/components/Tabs';
|
||||
import { useBondingContext, BondingContextProvider } from '../../../context';
|
||||
import { AppContext, urls } from 'src/context/main';
|
||||
|
||||
import { NodeGeneralSettings } from './general-settings';
|
||||
import { UnbondModal } from '../../../components/Bonding/modals/UnbondModal';
|
||||
import { nodeSettingsNav } from './node-settings.constant';
|
||||
|
||||
export const NodeSettings = () => {
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>();
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const handleChange = (event: React.SyntheticEvent, tab: number) => {
|
||||
setValue(tab);
|
||||
};
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
|
||||
const { bondedNode, unbond } = useBondingContext();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleCloseUnboundModal = () => {
|
||||
if (nodeSettingsNav.length === 1) {
|
||||
navigate('/bonding');
|
||||
} else if (nodeSettingsNav[0] === 'Unbond') {
|
||||
setValue(1);
|
||||
} else {
|
||||
setValue(0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnbond = async (fee?: FeeDetails) => {
|
||||
const tx = await unbond(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Unbond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleError = (error: string) => {
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: error,
|
||||
});
|
||||
};
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard
|
||||
borderless
|
||||
noPadding
|
||||
title={
|
||||
<Stack gap={2} sx={{ py: 0 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<NodeIcon />
|
||||
<Typography variant="h6" fontWeight={600}>
|
||||
Node Settings
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Tabs
|
||||
tabs={nodeSettingsNav}
|
||||
selectedTab={value}
|
||||
onChange={handleChange}
|
||||
tabSx={{
|
||||
bgcolor: 'transparent',
|
||||
borderBottom: 'none',
|
||||
borderTop: 'none',
|
||||
'& button': {
|
||||
p: 0,
|
||||
mr: 4,
|
||||
minWidth: 'none',
|
||||
fontSize: 16,
|
||||
},
|
||||
'& button:hover': {
|
||||
color: theme.palette.nym.highlight,
|
||||
opacity: 1,
|
||||
},
|
||||
}}
|
||||
tabIndicatorStyles={{ height: 4, bottom: '6px', borderRadius: '2px' }}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
}
|
||||
Action={
|
||||
<Button
|
||||
size="small"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
}}
|
||||
onClick={() => navigate('/bonding')}
|
||||
startIcon={<Close />}
|
||||
></Button>
|
||||
}
|
||||
>
|
||||
<Divider />
|
||||
{nodeSettingsNav[value] === 'General' && bondedNode && <NodeGeneralSettings bondedNode={bondedNode} />}
|
||||
{nodeSettingsNav[value] === 'Unbond' && bondedNode && (
|
||||
<UnbondModal
|
||||
node={bondedNode}
|
||||
onClose={handleCloseUnboundModal}
|
||||
onConfirm={handleUnbond}
|
||||
onError={handleError}
|
||||
/>
|
||||
)}
|
||||
{confirmationDetails && confirmationDetails.status === 'success' && (
|
||||
<ConfirmationDetailsModal
|
||||
title={confirmationDetails.title}
|
||||
subtitle={confirmationDetails.subtitle || 'This operation can take up to one hour to process'}
|
||||
status={confirmationDetails.status}
|
||||
txUrl={confirmationDetails.txUrl}
|
||||
onClose={() => {
|
||||
setConfirmationDetails(undefined);
|
||||
navigate('/bonding');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export const NodeSettingsPage = () => (
|
||||
<BondingContextProvider>
|
||||
<NodeSettings />
|
||||
</BondingContextProvider>
|
||||
);
|
||||
@@ -1,2 +0,0 @@
|
||||
// If we want to hide a tab we can remove the tab from the bellow array
|
||||
export const nodeSettingsNav = ['General', 'Unbond'];
|
||||
@@ -4,7 +4,7 @@ import { ApplicationLayout } from 'src/layouts';
|
||||
import { Terminal } from 'src/pages/terminal';
|
||||
import { Send } from 'src/components/Send';
|
||||
import { Receive } from '../components/Receive';
|
||||
import { Balance, InternalDocs, DelegationPage, Admin, BondingPage, NodeSettingsPage } from '../pages';
|
||||
import { Balance, InternalDocs, DelegationPage, Admin, BondingPage } from '../pages';
|
||||
|
||||
export const AppRoutes = () => (
|
||||
<ApplicationLayout>
|
||||
@@ -14,7 +14,6 @@ export const AppRoutes = () => (
|
||||
<Routes>
|
||||
<Route path="/balance" element={<Balance />} />
|
||||
<Route path="/bonding" element={<BondingPage />} />
|
||||
<Route path="/bonding/node-settings" element={<NodeSettingsPage />} />
|
||||
<Route path="/delegation" element={<DelegationPage />} />
|
||||
<Route path="/docs" element={<InternalDocs />} />
|
||||
<Route path="/admin" element={<Admin />} />
|
||||
|
||||
Vendored
-2
@@ -31,7 +31,6 @@ declare module '@mui/material/styles' {
|
||||
highlight: string;
|
||||
success: string;
|
||||
info: string;
|
||||
red: string;
|
||||
fee: string;
|
||||
background: { light: string; dark: string };
|
||||
text: {
|
||||
@@ -58,7 +57,6 @@ declare module '@mui/material/styles' {
|
||||
warn: string;
|
||||
contrast: string;
|
||||
grey: string;
|
||||
blue: string;
|
||||
};
|
||||
topNav: {
|
||||
background: string;
|
||||
|
||||
@@ -23,7 +23,6 @@ const nymPalette: NymPalette = {
|
||||
highlight: '#FB6E4E',
|
||||
success: '#21D073',
|
||||
info: '#60D7EF',
|
||||
red: '#DA465B',
|
||||
fee: '#967FF0',
|
||||
background: { light: '#F4F6F8', dark: '#1D2125' },
|
||||
text: {
|
||||
@@ -50,7 +49,6 @@ const darkMode: NymPaletteVariant = {
|
||||
warn: '#FFE600',
|
||||
contrast: '#1D2125',
|
||||
grey: '#5B6174',
|
||||
blue: '#60D7EF',
|
||||
},
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
@@ -81,7 +79,6 @@ const lightMode: NymPaletteVariant = {
|
||||
warn: '#FFE600',
|
||||
contrast: '#FFFFFF',
|
||||
grey: '#3A4053',
|
||||
blue: '#514EFB',
|
||||
},
|
||||
topNav: {
|
||||
background: '#111826',
|
||||
@@ -288,16 +285,6 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiToolbar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
minWidth: 0,
|
||||
'@media (min-width: 0px)': {
|
||||
minHeight: 'fit-content',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
palette,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
reports
|
||||
allure-results
|
||||
node_modules
|
||||
.vscode
|
||||
.idea
|
||||
@@ -0,0 +1,2 @@
|
||||
build
|
||||
coverage
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": false
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "wallet-ui-test",
|
||||
"version": "1.0.0",
|
||||
"description": "wallet ui tests for the nym wallet",
|
||||
"scripts": {
|
||||
"test": "wdio run wdio.conf.ts",
|
||||
"format": "prettier --write \"./**/*.ts\""
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"-": "^0.0.1",
|
||||
"@wdio/cli": "^7.16.16",
|
||||
"save-dev": "^0.0.1-security",
|
||||
"ts-node": "^10.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@wdio/local-runner": "^7.16.16",
|
||||
"@wdio/mocha-framework": "^7.16.15",
|
||||
"@wdio/spec-reporter": "^7.16.14",
|
||||
"prettier": "2.5.1",
|
||||
"typescript": "^4.6.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
class WalletLogin {
|
||||
get errorValidation(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='error']")
|
||||
}
|
||||
|
||||
get signInAccount(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='signIn']")
|
||||
}
|
||||
|
||||
get createAccount(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='createAccount']")
|
||||
}
|
||||
|
||||
get getMnemonicPhrase(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='mnemonic-phrase']")
|
||||
}
|
||||
|
||||
get signInButtonReturn(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='sign-in-button']")
|
||||
}
|
||||
|
||||
get backButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='backButton']")
|
||||
}
|
||||
|
||||
get mnemonic(): Promise<WebdriverIO.Element> {
|
||||
return $('#mui-1')
|
||||
}
|
||||
|
||||
get selectTextArea(): Promise<WebdriverIO.Element> {
|
||||
//bit nasty using the xpath - but it's not liking the elements
|
||||
return $("//*[@id='mui-1']")
|
||||
}
|
||||
|
||||
get accountBalance(): Promise<WebdriverIO.Element> {
|
||||
//bit nasty using the xpath - but it's not liking the elements
|
||||
return $("[data-testid='refresh-success']")
|
||||
}
|
||||
|
||||
enterMnemonic = async (mnemonic: string): Promise<void> => {
|
||||
await (await this.selectTextArea).click()
|
||||
await (await this.mnemonic).addValue(mnemonic)
|
||||
}
|
||||
}
|
||||
export default new WalletLogin()
|
||||
@@ -0,0 +1,39 @@
|
||||
class WalletReceive {
|
||||
get receiveNymHeader(): Promise<WebdriverIO.Element> {
|
||||
return $(
|
||||
'#root > div > div:nth-child(2) > div:nth-child(2) > div > div > div > div.MuiCardHeader-root > div > span',
|
||||
)
|
||||
}
|
||||
get receiveNymText(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='receive-nym']")
|
||||
}
|
||||
get walletAddress(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='client-address']")
|
||||
}
|
||||
get copyButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='copy-button']")
|
||||
}
|
||||
get qrCode(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='qr-code']")
|
||||
}
|
||||
|
||||
WaitForButtonChangeOnCopy = async (): Promise<void> => {
|
||||
await (await this.copyButton).click()
|
||||
|
||||
await (await this.copyButton).waitForDisplayed({ timeout: 1500 })
|
||||
|
||||
await (
|
||||
await this.copyButton
|
||||
).waitUntil(
|
||||
async function () {
|
||||
return (await this.getText()) === 'COPIED'
|
||||
},
|
||||
{
|
||||
timeout: 1500,
|
||||
timeoutMsg: 'expected text to be different after 1.5s',
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default new WalletReceive()
|
||||
@@ -0,0 +1,52 @@
|
||||
class WalletSend {
|
||||
get fromAddress() {
|
||||
return $('#from')
|
||||
}
|
||||
get toAddress(): Promise<WebdriverIO.Element> {
|
||||
return $('#to')
|
||||
}
|
||||
get amount(): Promise<WebdriverIO.Element> {
|
||||
return $('#amount')
|
||||
}
|
||||
get nextButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='button")
|
||||
}
|
||||
get sendHeader(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='Send punk']")
|
||||
}
|
||||
get accountBalance(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='account-balance']")
|
||||
}
|
||||
get amountReviewAndSend(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='Amount']")
|
||||
}
|
||||
get toAddressReviewAndSend(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='To']")
|
||||
}
|
||||
get fromAddressReviewAndSend(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='From']")
|
||||
}
|
||||
get transferFeeAmount(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='Transfer fee']")
|
||||
}
|
||||
get reviewAndSendBackButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='back-button']")
|
||||
}
|
||||
get sendButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='button']")
|
||||
}
|
||||
get transactionComplete(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='transaction-complete']")
|
||||
}
|
||||
get transactionCompleteRecipient(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='to-address']")
|
||||
}
|
||||
get transactionCompleteAmount(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='send-amount']")
|
||||
}
|
||||
get finishButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='button']")
|
||||
}
|
||||
}
|
||||
|
||||
export default new WalletSend()
|
||||
@@ -0,0 +1,22 @@
|
||||
class WallentUndelegate {
|
||||
get transactionFee(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='fee-amount']")
|
||||
}
|
||||
get mixNodeRadioButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[value='mixnode']")
|
||||
}
|
||||
get gatewayRadionButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[value='gateway']")
|
||||
}
|
||||
get nodeIdentity(): Promise<WebdriverIO.Element> {
|
||||
return $('#mui-55011')
|
||||
}
|
||||
get identityHelper(): Promise<WebdriverIO.Element> {
|
||||
return $('#identity-helper-text')
|
||||
}
|
||||
get delegateButton(): Promise<WebdriverIO.Element> {
|
||||
return $("[data-testid='submit-button']")
|
||||
}
|
||||
}
|
||||
|
||||
export default new WallentUndelegate()
|
||||
@@ -0,0 +1,44 @@
|
||||
import walletLogin from '../pageobjects/wallet.login'
|
||||
|
||||
describe('Wallet login functionality', () => {
|
||||
it.skip('submitting with no mnemonic throws error', async () => {
|
||||
//sign into account
|
||||
await (await walletLogin.signInAccount).click()
|
||||
|
||||
//submit sign in with no mnemominc
|
||||
await (await walletLogin.signInAccount).click()
|
||||
|
||||
await (await walletLogin.errorValidation).waitForDisplayed({ timeout: 1500 })
|
||||
let getErrorWarning = await (await walletLogin.errorValidation).getText()
|
||||
|
||||
await (await walletLogin.backButton).click()
|
||||
|
||||
//assert that the error was thrown
|
||||
const errorText = 'mnemonic has a word count that is not a multiple of 6: 0'
|
||||
expect(getErrorWarning).toStrictEqual(errorText)
|
||||
})
|
||||
|
||||
it('should login with valid credentials', async () => {
|
||||
//create account
|
||||
await (await walletLogin.createAccount).click()
|
||||
|
||||
//allow time for the api to create the wallet address
|
||||
await browser.pause(500)
|
||||
await (await walletLogin.getMnemonicPhrase).waitForDisplayed({ timeout: 1500 })
|
||||
|
||||
//retrieve mnemonic - copy
|
||||
let mnemonicPhrase = await (await walletLogin.getMnemonicPhrase).getText()
|
||||
|
||||
await (await walletLogin.signInButtonReturn).click()
|
||||
|
||||
//input new wallet mnemonic and be inside the app
|
||||
await walletLogin.enterMnemonic(mnemonicPhrase)
|
||||
|
||||
await (await walletLogin.signInAccount).click()
|
||||
|
||||
await (await walletLogin.accountBalance).waitForDisplayed({ timeout: 1500 })
|
||||
let balance = await (await walletLogin.accountBalance).getText()
|
||||
//new accounts will always default to mainnet
|
||||
expect(balance).toStrictEqual('0 NYM')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"types": ["node", "webdriverio/async", "@wdio/mocha-framework", "expect-webdriverio"],
|
||||
"target": "ES5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
const os = require('os')
|
||||
const path = require('path')
|
||||
const { spawn, spawnSync } = require('child_process')
|
||||
//insert path to binary
|
||||
const nym_path = '../target/debug/nym_wallet'
|
||||
|
||||
let tauriDriver: any
|
||||
|
||||
exports.config = {
|
||||
autoCompileOpts: {
|
||||
autoCompile: true,
|
||||
tsNodeOpts: {
|
||||
transpileOnly: true,
|
||||
project: 'test/tsconfig.json',
|
||||
},
|
||||
},
|
||||
specs: ['./test/specs/**/*.ts'],
|
||||
// Patterns to exclude.
|
||||
exclude: [
|
||||
// 'path/to/excluded/files'
|
||||
],
|
||||
maxInstances: 1,
|
||||
capabilities: [
|
||||
{
|
||||
maxInstances: 1,
|
||||
'tauri:options': {
|
||||
application: nym_path,
|
||||
},
|
||||
},
|
||||
],
|
||||
//
|
||||
// ===================
|
||||
// Test Configurations
|
||||
// ===================
|
||||
// Define all options that are relevant for the WebdriverIO instance here
|
||||
//
|
||||
// Level of logging verbosity: trace | debug | info | warn | error | silent
|
||||
logLevel: 'info',
|
||||
bail: 0,
|
||||
framework: 'mocha',
|
||||
reporters: ['spec'],
|
||||
mochaOpts: {
|
||||
ui: 'bdd',
|
||||
timeout: 60000,
|
||||
},
|
||||
// ===================
|
||||
// Test Reporters
|
||||
// ===================
|
||||
// reporters: [
|
||||
// [
|
||||
// "allure",
|
||||
// {
|
||||
// outputDir: "allure-results",
|
||||
// disableWebdriverStepsReporting: true,
|
||||
// disableWebdriverScreenshotsReporting: true,
|
||||
// },
|
||||
// ],
|
||||
// ],
|
||||
|
||||
// this is documentented in the readme - you will need to build the project first
|
||||
// ensure the rust project is built since we expect this binary to exist for the webdriver sessions
|
||||
//onPrepare: () => spawnSync("cargo", ["build", "--release"]),
|
||||
|
||||
// ensure we are running `tauri-driver` before the session starts so that we can proxy the webdriver requests
|
||||
beforeSession: () =>
|
||||
(tauriDriver = spawn(path.resolve(os.homedir(), '.cargo', 'bin', 'tauri-driver'), [], {
|
||||
stdio: [null, process.stdout, process.stderr],
|
||||
})),
|
||||
|
||||
// afterTest: function (
|
||||
// test,
|
||||
// context,
|
||||
// { error, result, duration, passed, retries }
|
||||
// ) {
|
||||
// if (error) {
|
||||
// browser.takeScreenshot();
|
||||
// }
|
||||
// },
|
||||
|
||||
// clean up the `tauri-driver` process we spawned at the start of the session
|
||||
afterSession: () => tauriDriver.kill(),
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user