From f455b7c720bd20f871eea33dece6ae650f04e19b Mon Sep 17 00:00:00 2001 From: Gala Date: Thu, 17 Oct 2024 11:08:05 +0200 Subject: [PATCH] Migrate Legacy Node (Frontend) (#4826) * refactor bonding requests * use migrate node modal * disable node settings for legacy nodes * refine bonded node types * start migration and bonding work * update types and requests * clean up bonding context * move old forms to legacy directory * create nymnode bonding flow --------- Co-authored-by: fmtabbara --- .../components/Balance/VestingTimeline.tsx | 2 +- .../Balance/cards/VestingSchedule.tsx | 2 +- nym-wallet/src/components/Bonding/Bond.tsx | 2 +- .../src/components/Bonding/BondUpdateCard.tsx | 44 ++ .../src/components/Bonding/BondedGateway.tsx | 35 +- .../src/components/Bonding/BondedMixnode.tsx | 31 +- .../src/components/Bonding/BondedNymNode.tsx | 206 +++++++ .../Bonding/BondedNymNodeActions.tsx | 49 ++ .../src/components/Bonding/NodeStats.tsx | 17 +- .../Bonding/forms/BondMixnodeForm.tsx | 50 -- .../Bonding/forms/GatewaySignatureForm.tsx | 94 --- .../Bonding/forms/MixnodeSignatureForm.tsx | 95 --- .../{ => legacyForms}/BondGatewayForm.tsx | 6 +- .../{ => legacyForms}/GatewayAmountForm.tsx | 6 +- .../{ => legacyForms}/GatewayInitForm.tsx | 2 +- .../{ => legacyForms}/MixnodeAmountForm.tsx | 10 +- .../{ => legacyForms}/MixnodeInitForm.tsx | 2 +- .../gatewayValidationSchema.ts | 0 .../mixnodeValidationSchema.ts | 2 +- .../Bonding/forms/nym-node/FormContext.tsx | 98 +++ .../Bonding/forms/nym-node/NymNodeAmount.tsx | 120 ++++ .../Bonding/forms/nym-node/NymNodeData.tsx | 110 ++++ .../forms/nym-node/NymNodeSignature.tsx | 111 ++++ .../forms/nym-node/amountValidationSchema.ts | 58 ++ .../Bonding/modals/BondGatewayModal.tsx | 41 +- .../Bonding/modals/BondMixnodeModal.tsx | 163 ----- .../Bonding/modals/BondNymNodeModal.tsx | 88 +++ .../Bonding/modals/MigrateLegacyNode.tsx | 28 + .../Bonding/modals/NodeSettingsModal.tsx | 2 +- .../Bonding/modals/RedeemRewardsModal.tsx | 3 +- .../components/Bonding/modals/UnbondModal.tsx | 3 +- .../Bonding/modals/UpdateBondAmountModal.tsx | 3 +- .../modals/UpdateBondAmountNymNode.tsx | 153 +++++ nym-wallet/src/components/Bonding/utils.ts | 4 +- nym-wallet/src/components/NodeStatus.tsx | 1 - nym-wallet/src/components/NymCard.tsx | 2 +- nym-wallet/src/context/bonding.tsx | 576 +++--------------- nym-wallet/src/context/mocks/bonding.tsx | 29 +- nym-wallet/src/context/mocks/delegations.tsx | 7 +- nym-wallet/src/context/mocks/rewards.tsx | 4 +- nym-wallet/src/hooks/useGetNodeDetails.ts | 69 +++ nym-wallet/src/pages/Admin/index.tsx | 20 +- nym-wallet/src/pages/bonding/Bonding.tsx | 172 ++++-- .../bonding/node-settings/NodeSettings.tsx | 11 +- .../node-settings/apy-playground/index.tsx | 2 +- .../node-settings/node-settings.constant.tsx | 12 +- .../bonding/node-settings/node-test/index.tsx | 5 +- .../settings-pages/NodeUnbondPage.tsx | 15 +- .../GeneralGatewaySettings.tsx | 7 +- .../GeneralMixnodeSettings.tsx | 6 +- .../GeneralNymNodeSettings.tsx | 182 ++++++ .../general-settings/ParametersSettings.tsx | 16 +- .../settings-pages/general-settings/index.tsx | 11 +- nym-wallet/src/pages/bonding/types.ts | 4 +- .../pages/node-settings/system-variables.tsx | 17 +- nym-wallet/src/requests/actions.ts | 34 +- nym-wallet/src/requests/bond.ts | 56 ++ nym-wallet/src/requests/gatewayDetails.ts | 71 +++ nym-wallet/src/requests/index.ts | 1 + nym-wallet/src/requests/mixnodeDetails.ts | 199 ++++++ nym-wallet/src/requests/nymNodeDetails.ts | 145 +++++ nym-wallet/src/requests/queries.ts | 26 +- nym-wallet/src/requests/simulate.ts | 6 +- nym-wallet/src/requests/utils.ts | 6 +- nym-wallet/src/requests/vesting.ts | 6 +- nym-wallet/src/types/global.ts | 42 +- nym-wallet/src/utils/common.ts | 18 +- .../packages/validator-client/src/index.ts | 4 +- .../validator-client/src/signing-client.ts | 6 +- .../types/src/types/rust/MixNodeRewarding.ts | 4 +- ts-packages/types/src/types/rust/index.ts | 3 +- 71 files changed, 2206 insertions(+), 1229 deletions(-) create mode 100644 nym-wallet/src/components/Bonding/BondUpdateCard.tsx create mode 100644 nym-wallet/src/components/Bonding/BondedNymNode.tsx create mode 100644 nym-wallet/src/components/Bonding/BondedNymNodeActions.tsx delete mode 100644 nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx delete mode 100644 nym-wallet/src/components/Bonding/forms/GatewaySignatureForm.tsx delete mode 100644 nym-wallet/src/components/Bonding/forms/MixnodeSignatureForm.tsx rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/BondGatewayForm.tsx (78%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/GatewayAmountForm.tsx (94%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/GatewayInitForm.tsx (98%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/MixnodeAmountForm.tsx (93%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/MixnodeInitForm.tsx (98%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/gatewayValidationSchema.ts (100%) rename nym-wallet/src/components/Bonding/forms/{ => legacyForms}/mixnodeValidationSchema.ts (98%) create mode 100644 nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx create mode 100644 nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx create mode 100644 nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx create mode 100644 nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx create mode 100644 nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts delete mode 100644 nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx create mode 100644 nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx create mode 100644 nym-wallet/src/components/Bonding/modals/MigrateLegacyNode.tsx create mode 100644 nym-wallet/src/components/Bonding/modals/UpdateBondAmountNymNode.tsx create mode 100644 nym-wallet/src/hooks/useGetNodeDetails.ts create mode 100644 nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralNymNodeSettings.tsx create mode 100644 nym-wallet/src/requests/bond.ts create mode 100644 nym-wallet/src/requests/gatewayDetails.ts create mode 100644 nym-wallet/src/requests/mixnodeDetails.ts create mode 100644 nym-wallet/src/requests/nymNodeDetails.ts diff --git a/nym-wallet/src/components/Balance/VestingTimeline.tsx b/nym-wallet/src/components/Balance/VestingTimeline.tsx index 52e137f4c8..cfb489056d 100644 --- a/nym-wallet/src/components/Balance/VestingTimeline.tsx +++ b/nym-wallet/src/components/Balance/VestingTimeline.tsx @@ -26,7 +26,7 @@ export const VestingTimeline: FCWithChildren<{ percentageComplete: number }> = ( const nextPeriod = typeof currentVestingPeriod === 'object' && !!vestingAccountInfo?.periods - ? Number(vestingAccountInfo?.periods[currentVestingPeriod.in + 1]?.start_time) + ? Number(vestingAccountInfo?.periods[currentVestingPeriod.In + 1]?.start_time) : undefined; return ( diff --git a/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx index e713565680..b26b999970 100644 --- a/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx +++ b/nym-wallet/src/components/Balance/cards/VestingSchedule.tsx @@ -23,7 +23,7 @@ const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = const vestingPeriod = (current?: Period, original?: number) => { if (current === 'After') return 'Complete'; - if (typeof current === 'object' && typeof original === 'number') return `${current.in + 1}/${original}`; + if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`; return 'N/A'; }; diff --git a/nym-wallet/src/components/Bonding/Bond.tsx b/nym-wallet/src/components/Bonding/Bond.tsx index d240b31035..79a19c3396 100644 --- a/nym-wallet/src/components/Bonding/Bond.tsx +++ b/nym-wallet/src/components/Bonding/Bond.tsx @@ -20,7 +20,7 @@ export const Bond = ({ }} > - Bond a mix node or a gateway. Learn how to set up and run a node{' '} + Bond a nym node. Learn how to set up and run a Nym node{' '} here diff --git a/nym-wallet/src/components/Bonding/BondUpdateCard.tsx b/nym-wallet/src/components/Bonding/BondUpdateCard.tsx new file mode 100644 index 0000000000..9cd24d0af8 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondUpdateCard.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { Box, Button, Stack, Tooltip, Typography } from '@mui/material'; +import { NymCard } from 'src/components'; + +export const BondUpdateCard = ({ setSuccesfullUpdate }: { setSuccesfullUpdate: (staus: boolean) => void }) => ( + + + Upgrade your node! + + } + subheader={ + + + It seems like your node is running outdated binaries. + + Update to the latest stable Nym node binary now* + The update takes less than a minute! + + *Without updating, legacy node settings can be changed in the Nym CLI. + + + } + Action={ + + + + + + + + } + /> + +); diff --git a/nym-wallet/src/components/Bonding/BondedGateway.tsx b/nym-wallet/src/components/Bonding/BondedGateway.tsx index 2c1c5f6127..ab8ee84209 100644 --- a/nym-wallet/src/components/Bonding/BondedGateway.tsx +++ b/nym-wallet/src/components/Bonding/BondedGateway.tsx @@ -1,11 +1,13 @@ import React from 'react'; -import { Box, Button, Stack, Typography } from '@mui/material'; +import { Box, Button, Stack, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; -import { TBondedGateway, urls } from 'src/context'; +import { urls } from 'src/context'; import { NymCard } from 'src/components'; import { Network } from 'src/types'; import { IdentityKey } from 'src/components/IdentityKey'; import { useNavigate } from 'react-router-dom'; +import { UpgradeRounded } from '@mui/icons-material'; +import { TBondedGateway } from 'src/requests/gatewayDetails'; import { Node as NodeIcon } from '../../svg-icons/node'; import { Cell, Header, NodeTable } from './NodeTable'; import { BondedGatewayActions, TBondedGatwayActions } from './BondedGatewayAction'; @@ -39,10 +41,12 @@ const headers: Header[] = [ export const BondedGateway = ({ gateway, network, + onShowMigrateToNymNodeModal, onActionSelect, }: { gateway: TBondedGateway; network?: Network; + onShowMigrateToNymNodeModal: () => void; onActionSelect: (action: TBondedGatwayActions) => void; }) => { const { name, bond, ip, identityKey, routingScore } = gateway; @@ -91,16 +95,29 @@ export const BondedGateway = ({ } Action={ - + + + + + + - + } > diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx index e569cd7aec..5d0fc68e64 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnode.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -2,12 +2,14 @@ import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'; import { Link } from '@nymproject/react/link/Link'; -import { isMixnode, Network } from 'src/types'; -import { TBondedMixnode, urls } from 'src/context'; +import { Network } from 'src/types'; +import { urls } from 'src/context'; import { NymCard } from 'src/components'; import { IdentityKey } from 'src/components/IdentityKey'; import { NodeStatus } from 'src/components/NodeStatus'; import { getIntervalAsDate } from 'src/utils'; +import { UpgradeRounded } from '@mui/icons-material'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { Node as NodeIcon } from '../../svg-icons/node'; import { Cell, Header, NodeTable } from './NodeTable'; import { BondedMixnodeActions, TBondedMixnodeActions } from './BondedMixnodeActions'; @@ -60,10 +62,12 @@ const headers: Header[] = [ export const BondedMixnode = ({ mixnode, network, + onShowMigrateToNymNodeModal, onActionSelect, }: { mixnode: TBondedMixnode; network?: Network; + onShowMigrateToNymNodeModal: () => void; onActionSelect: (action: TBondedMixnodeActions) => void; }) => { const [nextEpoch, setNextEpoch] = useState(); @@ -81,6 +85,7 @@ export const BondedMixnode = ({ status, identityKey, host, + isUnbonding, } = mixnode; const getNextInterval = async () => { @@ -165,9 +170,13 @@ export const BondedMixnode = ({ } Action={ - {isMixnode(mixnode) && ( + - )} + + + {nextEpoch instanceof Error ? null : ( Next epoch starts at {nextEpoch} diff --git a/nym-wallet/src/components/Bonding/BondedNymNode.tsx b/nym-wallet/src/components/Bonding/BondedNymNode.tsx new file mode 100644 index 0000000000..6186891e7f --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedNymNode.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Box, Button, Chip, Stack, Tooltip, Typography } from '@mui/material'; +import { Link } from '@nymproject/react/link/Link'; +import { Network } from 'src/types'; +import { urls } from 'src/context'; +import { NymCard } from 'src/components'; +import { IdentityKey } from 'src/components/IdentityKey'; +import { getIntervalAsDate } from 'src/utils'; +import { TBondedNymNode } from 'src/requests/nymNodeDetails'; +import { Node as NodeIcon } from '../../svg-icons/node'; +import { Cell, Header, NodeTable } from './NodeTable'; +import { BondedNymNodeActions, TBondedNymNodeActions } from './BondedNymNodeActions'; + +const textWhenNotName = 'This node has not yet set a name'; + +const headers: Header[] = [ + { + header: 'Stake', + id: 'stake', + sx: { pl: 0 }, + }, + { + header: 'Bond', + id: 'bond', + }, + { + header: 'Stake saturation', + id: 'stake-saturation', + }, + { + header: 'PM', + id: 'profit-margin', + tooltipText: + 'The percentage of the node rewards that you as the node operator will take before the rest of the reward is shared between you and the delegators.', + }, + { + header: 'Operating cost', + id: 'operator-cost', + tooltipText: + 'Monthly operational costs of running your node. The cost also influences how the rewards are split between you and your delegators. ', + }, + { + header: 'Operator rewards', + id: 'operator-rewards', + tooltipText: + 'This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch. You can redeem your rewards at any time.', + }, + { + header: 'No. delegators', + id: 'delegators', + }, + { + id: 'menu-button', + sx: { width: 34, maxWidth: 34 }, + }, +]; + +export const BondedNymNode = ({ + nymnode, + network, + onActionSelect, +}: { + nymnode: TBondedNymNode; + network?: Network; + onActionSelect: (action: TBondedNymNodeActions) => void; +}) => { + const [nextEpoch, setNextEpoch] = useState(); + const navigate = useNavigate(); + const { + name, + nodeId, + stake, + bond, + stakeSaturation, + profitMargin, + operatorRewards, + operatorCost, + delegators, + identityKey, + host, + } = nymnode; + + const getNextInterval = async () => { + try { + const { nextEpoch: newNextEpoch } = await getIntervalAsDate(); + setNextEpoch(newNextEpoch); + } catch { + setNextEpoch(Error()); + } + }; + const cells: Cell[] = [ + { + cell: `${stake.amount} ${stake.denom}`, + id: 'stake-cell', + }, + { + cell: `${bond.amount} ${bond.denom}`, + id: 'bond-cell', + }, + { + cell: `${stakeSaturation}%`, + id: 'stake-saturation-cell', + }, + { + cell: `${profitMargin}%`, + id: 'pm-cell', + }, + { + cell: operatorCost ? `${operatorCost.amount} ${operatorCost.denom}` : '-', + id: 'operator-cost-cell', + }, + { + cell: operatorRewards ? `${operatorRewards.amount} ${operatorRewards.denom}` : '-', + id: 'operator-rewards-cell', + }, + { + cell: delegators, + id: 'delegators-cell', + }, + { + cell: nymnode.isUnbonding ? ( + + ) : ( + + ), + id: 'actions-cell', + align: 'right', + }, + ]; + + useEffect(() => { + getNextInterval(); + }, []); + + return ( + + + + + Nym node + + + {name?.includes(textWhenNotName) ? null : ( + + {name} + + )} + + + + + + + } + Action={ + + + + + + + + + + {nextEpoch instanceof Error ? null : ( + + Next epoch starts at {nextEpoch} + + )} + + } + > + + {network && ( + + Check more stats of your node on the{' '} + + explorer + + + )} + + {/* */} + + ); +}; diff --git a/nym-wallet/src/components/Bonding/BondedNymNodeActions.tsx b/nym-wallet/src/components/Bonding/BondedNymNodeActions.tsx new file mode 100644 index 0000000000..3125bbd2e1 --- /dev/null +++ b/nym-wallet/src/components/Bonding/BondedNymNodeActions.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Typography } from '@mui/material'; +import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu'; +import { Unbond as UnbondIcon, Bond as BondIcon } from '../../svg-icons'; + +export type TBondedNymNodeActions = 'nodeSettings' | 'updateBond' | 'unbond' | 'redeem'; + +export const BondedNymNodeActions = ({ + onActionSelect, + disabledRedeemAndCompound, + disabledUpdateBond, +}: { + onActionSelect: (action: TBondedNymNodeActions) => void; + disabledRedeemAndCompound: boolean; + disabledUpdateBond?: boolean; +}) => { + const [isOpen, setIsOpen] = useState(false); + + const handleOpen = () => setIsOpen(true); + const handleClose = () => setIsOpen(false); + + const handleActionClick = (action: TBondedNymNodeActions) => { + onActionSelect(action); + handleClose(); + }; + + return ( + + {!disabledUpdateBond && ( + } + onClick={() => handleActionClick('updateBond')} + /> + )} + R} + onClick={() => handleActionClick('redeem')} + disabled={disabledRedeemAndCompound} + /> + } + onClick={() => handleActionClick('unbond')} + /> + + ); +}; diff --git a/nym-wallet/src/components/Bonding/NodeStats.tsx b/nym-wallet/src/components/Bonding/NodeStats.tsx index eb22b50727..8c492968c7 100644 --- a/nym-wallet/src/components/Bonding/NodeStats.tsx +++ b/nym-wallet/src/components/Bonding/NodeStats.tsx @@ -1,9 +1,8 @@ import React from 'react'; -import { Stack, Typography, Box, useTheme, Grid, LinearProgress, LinearProgressProps, Button } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import { TBondedMixnode } from 'src/context'; +import { Stack, Typography, Box, useTheme, Grid, LinearProgress, LinearProgressProps } from '@mui/material'; import { Cell, Pie, PieChart, Legend, ResponsiveContainer } from 'recharts'; import { SelectionChance } from '@nymproject/types'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { NymCard } from '../NymCard'; import { InfoTooltip } from '../InfoToolTip'; @@ -50,10 +49,9 @@ const StatRow = ({ export const NodeStats = ({ mixnode }: { mixnode: TBondedMixnode }) => { const { activeSetProbability, routingScore } = mixnode; const theme = useTheme(); - const navigate = useNavigate(); // clamp routing score to [0-100] - const score = Math.min(Math.max(routingScore, 0), 100); + const score = Math.min(Math.max(routingScore || 0, 0), 100); const data = [ { key: 'routingScore', value: score }, @@ -74,10 +72,6 @@ export const NodeStats = ({ mixnode }: { mixnode: TBondedMixnode }) => { } }; - const handleGoToTestNode = () => { - navigate('/bonding/node-settings', { state: 'test-node' }); - }; - const renderLegend = () => ( { Node stats } - Action={ - - } > diff --git a/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx b/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx deleted file mode 100644 index 274878310f..0000000000 --- a/nym-wallet/src/components/Bonding/forms/BondMixnodeForm.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import { Box } from '@mui/material'; -import { CurrencyDenom, TNodeType } from '@nymproject/types'; -import { NodeTypeSelector } from 'src/components'; -import { MixnodeAmount, MixnodeData, Signature } from 'src/pages/bonding/types'; -import MixnodeInitForm from './MixnodeInitForm'; -import MixnodeAmountForm from './MixnodeAmountForm'; -import MixnodeSignatureForm from './MixnodeSignatureForm'; - -export const BondMixnodeForm = ({ - step, - denom, - mixnodeData, - amountData, - hasVestingTokens, - onSelectNodeType, - onValidateMixnodeData, - onValidateAmountData, - onValidateSignature, -}: { - step: 1 | 2 | 3 | 4; - mixnodeData: MixnodeData; - amountData: MixnodeAmount; - denom: CurrencyDenom; - hasVestingTokens: boolean; - onSelectNodeType: (nodeType: TNodeType) => void; - onValidateMixnodeData: (data: MixnodeData) => void; - onValidateAmountData: (data: MixnodeAmount) => Promise; - onValidateSignature: (signature: Signature) => void; -}) => ( - <> - {step === 1 && ( - <> - - - - - - )} - {step === 2 && ( - - )} - {step === 3 && } - -); diff --git a/nym-wallet/src/components/Bonding/forms/GatewaySignatureForm.tsx b/nym-wallet/src/components/Bonding/forms/GatewaySignatureForm.tsx deleted file mode 100644 index fd7c25fcea..0000000000 --- a/nym-wallet/src/components/Bonding/forms/GatewaySignatureForm.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Stack, TextField, Typography } from '@mui/material'; -import { useForm } from 'react-hook-form'; -import { gatewayToTauri } from '../utils'; -import { CopyToClipboard } from '../../CopyToClipboard'; -import { useBondingContext } from '../../../context'; -import { Console } from '../../../utils/console'; -import { ErrorModal } from '../../Modals/ErrorModal'; -import { GatewayData, GatewayAmount, Signature } from '../../../pages/bonding/types'; - -const GatewaySignatureForm = ({ - gateway, - amount, - onNext, -}: { - gateway: GatewayData; - amount: GatewayAmount; - onNext: (data: Signature) => void; -}) => { - const [message, setMessage] = useState(); - const [error, setError] = useState(); - const { generateGatewayMsgPayload } = useBondingContext(); - - const { register, handleSubmit } = useForm(); - - const handleOnNext = (event: { detail: { step: number } }) => { - if (event.detail.step === 3) { - handleSubmit(onNext)(); - } - }; - - useEffect(() => { - window.addEventListener('validate_bond_gateway_step' as any, handleOnNext); - return () => window.removeEventListener('validate_bond_gateway_step' as any, handleOnNext); - }, []); - - const generateMessage = async () => { - try { - setMessage( - await generateGatewayMsgPayload({ - pledge: amount.amount, - gateway: gatewayToTauri(gateway), - tokenPool: amount.tokenPool as 'balance' | 'locked', - }), - ); - } catch (e) { - Console.error(e); - setError('Something went wrong while generating the payload signature'); - } - }; - - useEffect(() => { - generateMessage(); - }, [gateway, amount]); - - if (error) { - return {}} />; - } - - return ( - - - Copy the message below and sign it: -
- If you are using a nym-gateway: -
- nym-gateway sign --id <your-node-id> --contract-msg <payload-generated-by-the-wallet> -
- If you are using a nym-node: -
- nym-node sign --id <your-node-id> --contract-msg <payload-generated-by-the-wallet> -
- Then paste the signature in the next field. -
- - - Copy Message - {message && } - - -
- ); -}; - -export default GatewaySignatureForm; diff --git a/nym-wallet/src/components/Bonding/forms/MixnodeSignatureForm.tsx b/nym-wallet/src/components/Bonding/forms/MixnodeSignatureForm.tsx deleted file mode 100644 index 1cce8d2ccd..0000000000 --- a/nym-wallet/src/components/Bonding/forms/MixnodeSignatureForm.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Stack, TextField, Typography } from '@mui/material'; -import { useForm } from 'react-hook-form'; -import { costParamsToTauri, mixnodeToTauri } from '../utils'; -import { CopyToClipboard } from '../../CopyToClipboard'; -import { useBondingContext } from '../../../context'; -import { Console } from '../../../utils/console'; -import { ErrorModal } from '../../Modals/ErrorModal'; -import { MixnodeAmount, MixnodeData, Signature } from '../../../pages/bonding/types'; - -const MixnodeSignatureForm = ({ - mixnode, - amount, - onNext, -}: { - mixnode: MixnodeData; - amount: MixnodeAmount; - onNext: (data: Signature) => void; -}) => { - const [message, setMessage] = useState(''); - const [error, setError] = useState(); - const { generateMixnodeMsgPayload } = useBondingContext(); - - const { register, handleSubmit } = useForm(); - - const handleOnNext = (event: { detail: { step: number } }) => { - if (event.detail.step === 3) { - handleSubmit(onNext)(); - } - }; - - useEffect(() => { - window.addEventListener('validate_bond_mixnode_step' as any, handleOnNext); - return () => window.removeEventListener('validate_bond_mixnode_step' as any, handleOnNext); - }, []); - - const generateMessage = async () => { - try { - setMessage( - (await generateMixnodeMsgPayload({ - pledge: amount.amount, - mixnode: mixnodeToTauri(mixnode), - costParams: costParamsToTauri(amount), - tokenPool: amount.tokenPool as 'balance' | 'locked', - })) as string, - ); - } catch (e) { - Console.error(e); - setError('Something went wrong while generating the payload signature'); - } - }; - - useEffect(() => { - generateMessage(); - }, [mixnode, amount]); - - if (error) { - return {}} />; - } - - return ( - - - Copy the message below and sign it: -
- If you are using a nym-mixnode: -
- nym-mixnode sign --id <your-node-id> --contract-msg <payload-generated-by-the-wallet> -
- If you are using a nym-node: -
- nym-node sign --id <your-node-id> --contract-msg <payload-generated-by-the-wallet> -
- Then paste the signature in the next field. -
- - - Copy Message - {message && } - - -
- ); -}; - -export default MixnodeSignatureForm; diff --git a/nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx b/nym-wallet/src/components/Bonding/forms/legacyForms/BondGatewayForm.tsx similarity index 78% rename from nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx rename to nym-wallet/src/components/Bonding/forms/legacyForms/BondGatewayForm.tsx index efb812d969..843d604053 100644 --- a/nym-wallet/src/components/Bonding/forms/BondGatewayForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/BondGatewayForm.tsx @@ -2,10 +2,9 @@ import React from 'react'; import { Box } from '@mui/material'; import { NodeTypeSelector } from 'src/components'; import { CurrencyDenom, TNodeType } from '@nymproject/types'; -import { GatewayAmount, GatewayData, Signature } from 'src/pages/bonding/types'; +import { GatewayAmount, GatewayData } from 'src/pages/bonding/types'; import GatewayInitForm from './GatewayInitForm'; import GatewayAmountForm from './GatewayAmountForm'; -import GatewaySignatureForm from './GatewaySignatureForm'; export const BondGatewayForm = ({ step, @@ -16,7 +15,6 @@ export const BondGatewayForm = ({ onSelectNodeType, onValidateGatewayData, onValidateAmountData, - onValidateSignature, }: { step: 1 | 2 | 3 | 4; gatewayData: GatewayData; @@ -26,7 +24,6 @@ export const BondGatewayForm = ({ onSelectNodeType: (nodeType: TNodeType) => void; onValidateGatewayData: (data: GatewayData) => void; onValidateAmountData: (data: GatewayAmount) => Promise; - onValidateSignature: (signature: Signature) => void; }) => ( <> {step === 1 && ( @@ -45,6 +42,5 @@ export const BondGatewayForm = ({ onNext={onValidateAmountData} /> )} - {step === 3 && } ); diff --git a/nym-wallet/src/components/Bonding/forms/GatewayAmountForm.tsx b/nym-wallet/src/components/Bonding/forms/legacyForms/GatewayAmountForm.tsx similarity index 94% rename from nym-wallet/src/components/Bonding/forms/GatewayAmountForm.tsx rename to nym-wallet/src/components/Bonding/forms/legacyForms/GatewayAmountForm.tsx index 2cef9a59da..7135d2b154 100644 --- a/nym-wallet/src/components/Bonding/forms/GatewayAmountForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/GatewayAmountForm.tsx @@ -5,9 +5,9 @@ import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { Box, Stack } from '@mui/material'; import { amountSchema } from './gatewayValidationSchema'; -import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from '../../../utils'; -import { GatewayAmount } from '../../../pages/bonding/types'; -import { TokenPoolSelector } from '../../TokenPoolSelector'; +import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from '../../../../utils'; +import { GatewayAmount } from '../../../../pages/bonding/types'; +import { TokenPoolSelector } from '../../../TokenPoolSelector'; const GatewayAmountForm = ({ denom, diff --git a/nym-wallet/src/components/Bonding/forms/GatewayInitForm.tsx b/nym-wallet/src/components/Bonding/forms/legacyForms/GatewayInitForm.tsx similarity index 98% rename from nym-wallet/src/components/Bonding/forms/GatewayInitForm.tsx rename to nym-wallet/src/components/Bonding/forms/legacyForms/GatewayInitForm.tsx index 670c5a9e0a..5a891700c9 100644 --- a/nym-wallet/src/components/Bonding/forms/GatewayInitForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/GatewayInitForm.tsx @@ -4,7 +4,7 @@ import { clean } from 'semver'; import { Checkbox, FormControlLabel, Stack, TextField } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; -import { GatewayData } from '../../../pages/bonding/types'; +import { GatewayData } from '../../../../pages/bonding/types'; import { gatewayValidationSchema } from './gatewayValidationSchema'; const GatewayInitForm = ({ diff --git a/nym-wallet/src/components/Bonding/forms/MixnodeAmountForm.tsx b/nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeAmountForm.tsx similarity index 93% rename from nym-wallet/src/components/Bonding/forms/MixnodeAmountForm.tsx rename to nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeAmountForm.tsx index f24f9138b7..13176dadf6 100644 --- a/nym-wallet/src/components/Bonding/forms/MixnodeAmountForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeAmountForm.tsx @@ -5,11 +5,11 @@ import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { CurrencyDenom } from '@nymproject/types'; import { amountSchema } from './mixnodeValidationSchema'; -import { MixnodeAmount } from '../../../pages/bonding/types'; -import { AppContext } from '../../../context'; -import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from '../../../utils'; -import { TokenPoolSelector } from '../../TokenPoolSelector'; -import { ModalListItem } from '../../Modals/ModalListItem'; +import { MixnodeAmount } from '../../../../pages/bonding/types'; +import { AppContext } from '../../../../context'; +import { checkHasEnoughFunds, checkHasEnoughLockedTokens } from '../../../../utils'; +import { TokenPoolSelector } from '../../../TokenPoolSelector'; +import { ModalListItem } from '../../../Modals/ModalListItem'; const MixnodeAmountForm = ({ amountData, diff --git a/nym-wallet/src/components/Bonding/forms/MixnodeInitForm.tsx b/nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeInitForm.tsx similarity index 98% rename from nym-wallet/src/components/Bonding/forms/MixnodeInitForm.tsx rename to nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeInitForm.tsx index 3c97bb22ce..0032bb3364 100644 --- a/nym-wallet/src/components/Bonding/forms/MixnodeInitForm.tsx +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/MixnodeInitForm.tsx @@ -5,7 +5,7 @@ import { Checkbox, FormControlLabel, Stack, TextField } from '@mui/material'; import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; import { yupResolver } from '@hookform/resolvers/yup/dist/yup'; import { mixnodeValidationSchema } from './mixnodeValidationSchema'; -import { MixnodeData } from '../../../pages/bonding/types'; +import { MixnodeData } from '../../../../pages/bonding/types'; const MixnodeInitForm = ({ mixnodeData, onNext }: { mixnodeData: MixnodeData; onNext: (data: any) => void }) => { const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); diff --git a/nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/legacyForms/gatewayValidationSchema.ts similarity index 100% rename from nym-wallet/src/components/Bonding/forms/gatewayValidationSchema.ts rename to nym-wallet/src/components/Bonding/forms/legacyForms/gatewayValidationSchema.ts diff --git a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/legacyForms/mixnodeValidationSchema.ts similarity index 98% rename from nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts rename to nym-wallet/src/components/Bonding/forms/legacyForms/mixnodeValidationSchema.ts index a72364377c..c11324457c 100644 --- a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/legacyForms/mixnodeValidationSchema.ts @@ -8,7 +8,7 @@ import { validateRawPort, validateVersion, } from 'src/utils'; -import { TauriContractStateParams } from '../../../types'; +import { TauriContractStateParams } from '../../../../types'; export const mixnodeValidationSchema = Yup.object().shape({ identityKey: Yup.string() diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx new file mode 100644 index 0000000000..6ad0bcadbc --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/nym-node/FormContext.tsx @@ -0,0 +1,98 @@ +import React, { createContext, useContext, useMemo, useState } from 'react'; +import { CurrencyDenom } from '@nymproject/types'; +import { TBondNymNodeArgs, TBondMixNodeArgs } from 'src/types'; + +const defaultNymNodeValues: TBondNymNodeArgs['nymNode'] = { + identity_key: 'H6rXWgsW89QsVyaNSS3qBe9zZFLhBS6Gn3YRkGFSoFW9', + custom_http_port: 1, + host: '1.1.1.1', +}; + +const defaultCostParams = (denom: CurrencyDenom): TBondNymNodeArgs['costParams'] => ({ + interval_operating_cost: { amount: '40', denom }, + profit_margin_percent: '10', +}); + +const defaultAmount = (denom: CurrencyDenom): TBondMixNodeArgs['pledge'] => ({ + amount: '100', + denom, +}); + +interface FormContextType { + step: 1 | 2 | 3 | 4; + setStep: React.Dispatch>; + nymNodeData: TBondNymNodeArgs['nymNode']; + setNymNodeData: React.Dispatch>; + costParams: TBondNymNodeArgs['costParams']; + setCostParams: React.Dispatch>; + amountData: TBondMixNodeArgs['pledge']; + setAmountData: React.Dispatch>; + signature?: string; + setSignature: React.Dispatch>; + onError: (e: string) => void; +} + +const FormContext = createContext({ + step: 1, + setStep: () => {}, + nymNodeData: defaultNymNodeValues, + setNymNodeData: () => {}, + costParams: defaultCostParams('nym'), + setCostParams: () => {}, + amountData: defaultAmount('nym'), + setAmountData: () => {}, + signature: undefined, + setSignature: () => {}, + + onError: () => {}, +}); + +const FormContextProvider = ({ children }: { children: React.ReactNode }) => { + // TODO - Make denom dynamic + const denom = 'nym'; + + const [step, setStep] = useState<1 | 2 | 3 | 4>(1); + const [nymNodeData, setNymNodeData] = useState(defaultNymNodeValues); + const [costParams, setCostParams] = useState(defaultCostParams(denom)); + const [amountData, setAmountData] = useState(defaultAmount(denom)); + const [signature, setSignature] = useState(); + + const onError = (e: string) => { + console.error(e); + }; + + const value = useMemo( + () => ({ + step, + setStep, + nymNodeData, + setNymNodeData, + costParams, + setCostParams, + amountData, + setAmountData, + signature, + setSignature, + onError, + }), + [ + step, + setStep, + nymNodeData, + setNymNodeData, + costParams, + setCostParams, + amountData, + setAmountData, + signature, + setSignature, + onError, + ], + ); + + return {children}; +}; + +export const useFormContext = () => useContext(FormContext); + +export default FormContextProvider; diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx new file mode 100644 index 0000000000..550dd639ad --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeAmount.tsx @@ -0,0 +1,120 @@ +import React from 'react'; +import { Stack, TextField, Box, FormHelperText } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { TBondNymNodeArgs } from 'src/types'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { checkHasEnoughFunds } from 'src/utils'; +import { nymNodeAmountSchema } from './amountValidationSchema'; + +const defaultNymNodeCostParamValues: TBondNymNodeArgs['costParams'] = { + profit_margin_percent: '10', + interval_operating_cost: { amount: '40', denom: 'nym' }, +}; + +const defaultNymNodePledgeValue: TBondNymNodeArgs['pledge'] = { + amount: '100', + denom: 'nym', +}; + +type NymNodeDataProps = { + onClose: () => void; + onBack: () => void; + onNext: () => Promise; + step: number; +}; + +const NymNodeAmount = ({ onClose, onBack, onNext, step }: NymNodeDataProps) => { + const { + formState: { errors }, + register, + getValues, + setValue, + setError, + handleSubmit, + } = useForm({ + mode: 'all', + defaultValues: { + pledge: defaultNymNodePledgeValue, + ...defaultNymNodeCostParamValues, + }, + resolver: yupResolver(nymNodeAmountSchema()), + }); + + console.log(errors, 'errors'); + + const handleRequestValidation = async () => { + const values = getValues(); + + const hasSufficientTokens = await checkHasEnoughFunds(values.pledge.amount); + + if (hasSufficientTokens) { + handleSubmit(onNext)(); + } else { + setError('pledge.amount', { message: 'Not enough tokens' }); + } + }; + + return ( + 0} + > + + { + setValue('pledge.amount', newValue.amount, { shouldValidate: true }); + }} + validationError={errors.pledge?.amount?.message} + denom={defaultNymNodePledgeValue.denom} + initialValue={defaultNymNodePledgeValue.amount} + /> + + + { + setValue('interval_operating_cost', newValue, { shouldValidate: true }); + }} + validationError={errors.interval_operating_cost?.amount?.message} + denom={defaultNymNodeCostParamValues.interval_operating_cost.denom} + initialValue={defaultNymNodeCostParamValues.interval_operating_cost.amount} + /> + + Monthly operational costs of running your node. If your node is in the active set the amount will be paid + back to you from the rewards. + + + + + + The percentage of node rewards that you as the node operator take before rewards are distributed to operator + and delegators. + + + + + ); +}; + +export default NymNodeAmount; diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx new file mode 100644 index 0000000000..e32068a9c6 --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeData.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { Stack, TextField, FormControlLabel, Checkbox } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField'; +import { TBondNymNodeArgs } from 'src/types'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as yup from 'yup'; +import { isValidHostname, validateRawPort } from 'src/utils'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; + +const defaultNymNodeValues: TBondNymNodeArgs['nymNode'] = { + identity_key: 'H6rXWgsW89QsVyaNSS3qBe9zZFLhBS6Gn3YRkGFSoFW9', + custom_http_port: 1, + host: '1.1.1.1', +}; + +const yupValidationSchema = yup.object().shape({ + identity_key: yup.string().required('Identity key is required'), + host: yup + .string() + .required('A host is required') + .test('no-whitespace', 'Host cannot contain whitespace', (value) => !/\s/.test(value || '')) + .test('valid-host', 'A valid host is required', (value) => (value ? isValidHostname(value) : false)), + + custom_http_port: yup + .number() + .required('A custom http port is required') + .test('valid-http', 'A valid http port is required', (value) => (value ? validateRawPort(value) : false)), +}); + +type NymNodeDataProps = { + onClose: () => void; + onBack: () => void; + onNext: () => Promise; + step: number; +}; + +const NymNodeData = ({ onClose, onNext, step }: NymNodeDataProps) => { + const { + formState: { errors }, + register, + setValue, + handleSubmit, + } = useForm({ + mode: 'all', + defaultValues: defaultNymNodeValues, + resolver: yupResolver(yupValidationSchema), + }); + + const [showAdvancedOptions, setShowAdvancedOptions] = React.useState(false); + + const handleNext = async () => { + handleSubmit(onNext)(); + }; + + return ( + 0} + > + + setValue('identity_key', value, { shouldValidate: true })} + showTickOnValid={false} + /> + + + + setShowAdvancedOptions((show) => !show)} checked={showAdvancedOptions} />} + label="Show advanced options" + /> + {showAdvancedOptions && ( + + + + )} + + + ); +}; + +export default NymNodeData; diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx new file mode 100644 index 0000000000..e1e5814d55 --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/nym-node/NymNodeSignature.tsx @@ -0,0 +1,111 @@ +import React, { useEffect, useState } from 'react'; +import { Stack, TextField, Typography } from '@mui/material'; +import { useForm } from 'react-hook-form'; +import { CopyToClipboard } from 'src/components/CopyToClipboard'; +import { ErrorModal } from 'src/components/Modals/ErrorModal'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { useBondingContext } from 'src/context'; +import { TBondNymNodeArgs } from 'src/types'; +import { Signature } from 'src/pages/bonding/types'; + +const NymNodeSignature = ({ + nymNode, + pledge, + costParams, + step, + onNext, + onClose, + onBack, +}: { + nymNode: TBondNymNodeArgs['nymNode']; + pledge: TBondNymNodeArgs['pledge']; + costParams: TBondNymNodeArgs['costParams']; + step: number; + onNext: (data: Signature) => void; + onClose: () => void; + onBack: () => void; +}) => { + const [message, setMessage] = useState(''); + const [error, setError] = useState(); + const { generateNymNodeMsgPayload } = useBondingContext(); + + const { + register, + formState: { errors }, + } = useForm(); + + const generateMessage = async () => { + try { + const msg = await generateNymNodeMsgPayload({ + nymNode, + pledge, + costParams, + }); + + if (msg) { + setMessage(msg); + } + } catch (e) { + console.error(e); + setError('Something went wrong while generating the payload signature'); + } + }; + + useEffect(() => { + generateMessage(); + }, [nymNode, pledge, costParams]); + + const onSubmit = async (data: Signature) => { + onNext(data); + }; + + if (error) { + return {}} />; + } + + return ( + + onSubmit({ + signature: 'signature', + }) + } + onClose={onClose} + header="Bond Nym Node" + subHeader={`Step ${step}/3`} + okLabel="Next" + onBack={onBack} + okDisabled={Object.keys(errors).length > 0} + > + + + Copy the message below and sign it: +
+ If you are using a nym-node: +
+ nym-node sign --id <your-node-id> --contract-msg <payload-generated-by-the-wallet> +
+ Then paste the signature in the next field. +
+ + + Copy Message + {message && } + + +
+
+ ); +}; + +export default NymNodeSignature; diff --git a/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts new file mode 100644 index 0000000000..1868a4f2e2 --- /dev/null +++ b/nym-wallet/src/components/Bonding/forms/nym-node/amountValidationSchema.ts @@ -0,0 +1,58 @@ +import * as Yup from 'yup'; +import { TauriContractStateParams } from 'src/types'; +import { isLessThan, isGreaterThan, validateAmount } from 'src/utils'; + +const operatingCostAndPmValidation = (params?: TauriContractStateParams) => { + const defaultParams = { + profit_margin_percent: { + minimum: parseFloat(params?.profit_margin.minimum || '0%'), + maximum: parseFloat(params?.profit_margin.maximum || '100%'), + }, + + interval_operating_cost: { + minimum: parseFloat(params?.operating_cost.minimum.amount || '0'), + maximum: parseFloat(params?.operating_cost.maximum.amount || '1000000000'), + }, + }; + + return { + profit_margin_percent: Yup.number() + .required('Profit Percentage is required') + .min(defaultParams.profit_margin_percent.minimum) + .max(defaultParams.profit_margin_percent.maximum), + interval_operating_cost: Yup.object().shape({ + amount: Yup.string() + .required('An operating cost is required') + // eslint-disable-next-line prefer-arrow-callback + .test('valid-operating-cost', 'A valid amount is required', async function isValidAmount(this, value) { + if ( + value && + (!Number(value) || + isLessThan(+value, defaultParams.interval_operating_cost.minimum) || + isGreaterThan(+value, Number(defaultParams.interval_operating_cost.maximum))) + ) { + return this.createError({ + message: `A valid amount is required (min ${defaultParams?.interval_operating_cost.minimum} - max ${defaultParams?.interval_operating_cost.maximum})`, + }); + } + return true; + }), + }), + }; +}; + +export const nymNodeAmountSchema = (params?: TauriContractStateParams) => + Yup.object().shape({ + pledge: Yup.object().shape({ + amount: Yup.string() + .required('An amount is required') + .test('valid-amount', 'Pledge error', async function isValidAmount(this, value) { + const isValid = await validateAmount(value || '', '100'); + if (!isValid) { + return this.createError({ message: 'A valid amount is required (min 100)' }); + } + return true; + }), + }), + ...operatingCostAndPmValidation(params), + }); diff --git a/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx index defdac3f95..1060b7c533 100644 --- a/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondGatewayModal.tsx @@ -4,15 +4,11 @@ import { CurrencyDenom, TNodeType } from '@nymproject/types'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; -import { TPoolOption } from 'src/components/TokenPoolSelector'; import { useGetFee } from 'src/hooks/useGetFee'; -import { GatewayAmount, GatewayData, Signature } from 'src/pages/bonding/types'; -import { simulateBondGateway, simulateVestingBondGateway } from 'src/requests'; -import { TBondGatewayArgs } from 'src/types'; +import { GatewayAmount, GatewayData } from 'src/pages/bonding/types'; import { BalanceWarning } from 'src/components/FeeWarning'; import { AppContext } from 'src/context'; -import { BondGatewayForm } from '../forms/BondGatewayForm'; -import { gatewayToTauri } from '../utils'; +import { BondGatewayForm } from '../forms/legacyForms/BondGatewayForm'; const defaultGatewayValues: GatewayData = { identityKey: '', @@ -34,14 +30,12 @@ const defaultAmountValues = (denom: CurrencyDenom) => ({ export const BondGatewayModal = ({ denom, hasVestingTokens, - onBondGateway, onSelectNodeType, onClose, onError, }: { denom: CurrencyDenom; hasVestingTokens: boolean; - onBondGateway: (data: TBondGatewayArgs, tokenPool: TPoolOption) => void; onSelectNodeType: (type: TNodeType) => void; onClose: () => void; onError: (e: string) => void; @@ -49,9 +43,8 @@ export const BondGatewayModal = ({ const [step, setStep] = useState<1 | 2 | 3>(1); const [gatewayData, setGatewayData] = useState(defaultGatewayValues); const [amountData, setAmountData] = useState(defaultAmountValues(denom)); - const [signature, setSignature] = useState(); - const { fee, getFee, resetFeeState, feeError } = useGetFee(); + const { fee, resetFeeState, feeError } = useGetFee(); const { userBalance } = useContext(AppContext); useEffect(() => { @@ -83,32 +76,7 @@ export const BondGatewayModal = ({ setStep(3); }; - const handleUpdateSignature = async (data: Signature) => { - setSignature(data.signature); - - const payload = { - pledge: amountData.amount, - msgSignature: data.signature, - gateway: gatewayToTauri(gatewayData), - }; - - if (amountData.tokenPool === 'balance') { - await getFee(simulateBondGateway, payload); - } else { - await getFee(simulateVestingBondGateway, payload); - } - }; - - const handleConfirm = async () => { - await onBondGateway( - { - pledge: amountData.amount, - msgSignature: signature as string, - gateway: gatewayToTauri(gatewayData), - }, - amountData.tokenPool as TPoolOption, - ); - }; + const handleConfirm = async () => {}; if (fee) { return ( @@ -154,7 +122,6 @@ export const BondGatewayModal = ({ hasVestingTokens={hasVestingTokens} onValidateGatewayData={handleUpdateGatwayData} onValidateAmountData={handleUpdateAmountData} - onValidateSignature={handleUpdateSignature} onSelectNodeType={onSelectNodeType} />
diff --git a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx deleted file mode 100644 index 4e4c39f9cf..0000000000 --- a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { CurrencyDenom, TNodeType } from '@nymproject/types'; -import { ConfirmTx } from 'src/components/ConfirmTX'; -import { ModalListItem } from 'src/components/Modals/ModalListItem'; -import { SimpleModal } from 'src/components/Modals/SimpleModal'; -import { TPoolOption } from 'src/components/TokenPoolSelector'; -import { useGetFee } from 'src/hooks/useGetFee'; -import { MixnodeAmount, MixnodeData, Signature } from 'src/pages/bonding/types'; -import { simulateBondMixnode, simulateVestingBondMixnode } from 'src/requests'; -import { TBondMixNodeArgs } from 'src/types'; -import { BalanceWarning } from 'src/components/FeeWarning'; -import { AppContext } from 'src/context'; -import { BondMixnodeForm } from '../forms/BondMixnodeForm'; -import { costParamsToTauri, mixnodeToTauri } from '../utils'; - -const defaultMixnodeValues: MixnodeData = { - identityKey: '', - sphinxKey: '', - ownerSignature: '', - host: '', - version: '', - mixPort: 1789, - verlocPort: 1790, - httpApiPort: 8000, -}; - -const defaultAmountValues = (denom: CurrencyDenom) => ({ - amount: { amount: '100', denom }, - operatorCost: { amount: '40', denom }, - profitMargin: '10', - tokenPool: 'balance', -}); - -export const BondMixnodeModal = ({ - denom, - hasVestingTokens, - onBondMixnode, - onSelectNodeType, - onClose, - onError, -}: { - denom: CurrencyDenom; - hasVestingTokens: boolean; - onBondMixnode: (data: TBondMixNodeArgs, tokenPool: TPoolOption) => void; - onSelectNodeType: (type: TNodeType) => void; - onClose: () => void; - onError: (e: string) => void; -}) => { - const [step, setStep] = useState<1 | 2 | 3 | 4>(1); - const [mixnodeData, setMixnodeData] = useState(defaultMixnodeValues); - const [amountData, setAmountData] = useState(defaultAmountValues(denom)); - const [signature, setSignature] = useState(); - - const { fee, getFee, resetFeeState, feeError } = useGetFee(); - const { userBalance } = useContext(AppContext); - - useEffect(() => { - if (feeError) { - onError(feeError); - } - }, [feeError]); - - const validateStep = async (s: number) => { - const event = new CustomEvent('validate_bond_mixnode_step', { detail: { step: s } }); - window.dispatchEvent(event); - }; - - const handleBack = () => { - if (step === 2) { - setStep(1); - } else if (step === 3) { - setStep(2); - } - }; - - const handleUpdateMixnodeData = (data: MixnodeData) => { - setMixnodeData(data); - setStep(2); - }; - - const handleUpdateAmountData = async (data: MixnodeAmount) => { - setAmountData({ ...data }); - setStep(3); - }; - - const handleUpdateSignature = async (data: Signature) => { - setSignature(data.signature); - - const payload = { - pledge: amountData.amount, - msgSignature: data.signature, - mixnode: mixnodeToTauri(mixnodeData), - costParams: costParamsToTauri(amountData), - }; - - if (amountData.tokenPool === 'balance') { - await getFee(simulateBondMixnode, payload); - } else { - await getFee(simulateVestingBondMixnode, payload); - } - }; - - const handleConfirm = async () => { - await onBondMixnode( - { - pledge: amountData.amount, - msgSignature: signature as string, - mixnode: mixnodeToTauri(mixnodeData), - costParams: costParamsToTauri(amountData), - }, - amountData.tokenPool as TPoolOption, - ); - }; - - if (fee) { - return ( - - - - {fee.amount?.amount && userBalance.balance && ( - - )} - - ); - } - - return ( - { - await validateStep(step); - }} - onBack={step === 2 || step === 3 ? handleBack : undefined} - onClose={onClose} - header="Bond mixnode" - subHeader={`Step ${step}/3`} - okLabel="Next" - > - - - ); -}; diff --git a/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx new file mode 100644 index 0000000000..bfed31e765 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/BondNymNodeModal.tsx @@ -0,0 +1,88 @@ +import React, { useContext, useEffect } from 'react'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { Signature } from 'src/pages/bonding/types'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { AppContext } from 'src/context'; +import FormContextProvider, { useFormContext } from '../forms/nym-node/FormContext'; +import NymNodeData from '../forms/nym-node/NymNodeData'; +import NymNodeAmount from '../forms/nym-node/NymNodeAmount'; +import NymNodeSignature from '../forms/nym-node/NymNodeSignature'; + +export const BondNymNodeModal = ({ onClose }: { onClose: () => void }) => { + const { fee, resetFeeState, feeError } = useGetFee(); + const { userBalance } = useContext(AppContext); + const { setStep, step, onError, setSignature, amountData, costParams, nymNodeData } = useFormContext(); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + const handleUpdateMixnodeData = async () => { + setStep(2); + }; + + const handleUpdateSignature = async (data: Signature) => { + setSignature(data.signature); + }; + + const handleConfirm = async () => {}; + + if (fee) { + return ( + + + + {fee.amount?.amount && userBalance.balance && ( + + )} + + ); + } + + if (step === 1) { + return ; + } + + if (step === 2) { + return setStep(1)} onNext={async () => setStep(3)} step={step} />; + } + + if (step === 3) { + return ( + setStep(2)} + step={step} + /> + ); + } + + return null; +}; + +export const BondNymNodeModalWithState = ({ open, onClose }: { open: boolean; onClose: () => void }) => { + if (!open) { + return null; + } + + return ( + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/modals/MigrateLegacyNode.tsx b/nym-wallet/src/components/Bonding/modals/MigrateLegacyNode.tsx new file mode 100644 index 0000000000..1a04601200 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/MigrateLegacyNode.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Box, Typography } from '@mui/material'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; + +const MigrateLegacyNode = ({ + open, + onClose, + handleMigrate, +}: { + open: boolean; + onClose: () => void; + handleMigrate: () => Promise; +}) => ( + Migrate Legacy Node} + onOk={handleMigrate} + okLabel="Migrate" + onClose={onClose} + sx={{ maxWidth: 500 }} + > + + You have a legacy node that needs to be migrated to the new system. + + +); + +export default MigrateLegacyNode; diff --git a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx index 0aa53e7368..8d87405d13 100644 --- a/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/NodeSettingsModal.tsx @@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react'; import { Box, Button, FormHelperText, TextField, Typography } from '@mui/material'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { Node as NodeIcon } from 'src/svg-icons/node'; -import { TBondedMixnode } from 'src/context'; import { Tabs } from 'src/components/Tabs'; import { ModalListItem } from 'src/components/Modals/ModalListItem'; import { attachDefaultOperatingCost, isDecimal, toPercentFloatString } from 'src/utils'; @@ -11,6 +10,7 @@ import { ConfirmTx } from 'src/components/ConfirmTX'; import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams } from 'src/requests'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { FeeDetails } from '@nymproject/types'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; // Now we are using the node setting page instead of this modal export const NodeSettings = ({ diff --git a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx index 11279278f6..8e00ddbfa5 100644 --- a/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/RedeemRewardsModal.tsx @@ -5,9 +5,10 @@ import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { ModalFee } from 'src/components/Modals/ModalFee'; import { useGetFee } from 'src/hooks/useGetFee'; import { simulateClaimOperatorReward, simulateVestingClaimOperatorReward } from 'src/requests'; -import { AppContext, TBondedMixnode } from 'src/context'; +import { AppContext } from 'src/context'; import { BalanceWarning } from 'src/components/FeeWarning'; import { Box } from '@mui/material'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; export const RedeemRewardsModal = ({ node, diff --git a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx index 41eeb45211..51e779a563 100644 --- a/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UnbondModal.tsx @@ -1,9 +1,10 @@ import * as React from 'react'; import { useEffect } from 'react'; import { Typography } from '@mui/material'; -import { TBondedGateway, TBondedMixnode } from 'src/context'; import { useGetFee } from 'src/hooks/useGetFee'; import { isGateway, isMixnode } from 'src/types'; +import { TBondedGateway } from 'src/requests/gatewayDetails'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { ModalFee } from '../../Modals/ModalFee'; import { ModalListItem } from '../../Modals/ModalListItem'; import { SimpleModal } from '../../Modals/SimpleModal'; diff --git a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx index 2148313b70..978675bd9b 100644 --- a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountModal.tsx @@ -10,8 +10,9 @@ import { useGetFee } from 'src/hooks/useGetFee'; import { decCoinToDisplay, validateAmount } from 'src/utils'; import { simulateUpdateBond, simulateVestingUpdateBond } from 'src/requests'; import { TSimulateUpdateBondArgs, TUpdateBondArgs } from 'src/types'; -import { AppContext, TBondedMixnode } from 'src/context'; +import { AppContext } from 'src/context'; import { BalanceWarning } from 'src/components/FeeWarning'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { TPoolOption } from '../../TokenPoolSelector'; export const UpdateBondAmountModal = ({ diff --git a/nym-wallet/src/components/Bonding/modals/UpdateBondAmountNymNode.tsx b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountNymNode.tsx new file mode 100644 index 0000000000..e634b1c354 --- /dev/null +++ b/nym-wallet/src/components/Bonding/modals/UpdateBondAmountNymNode.tsx @@ -0,0 +1,153 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Box, Stack } from '@mui/material'; +import Big from 'big.js'; +import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; +import { ModalListItem } from 'src/components/Modals/ModalListItem'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { DecCoin } from '@nymproject/types'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { decCoinToDisplay, validateAmount } from 'src/utils'; +import { simulateUpdateBond } from 'src/requests'; +import { TSimulateUpdateBondArgs, TUpdateBondArgs } from 'src/types'; +import { AppContext } from 'src/context'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { TBondedNymNode } from 'src/requests/nymNodeDetails'; +import { TPoolOption } from '../../TokenPoolSelector'; + +export const UpdateBondAmountNymNode = ({ + node, + onUpdateBond, + onClose, + onError, +}: { + node: TBondedNymNode; + onUpdateBond: (data: TUpdateBondArgs, tokenPool: TPoolOption) => Promise; + onClose: () => void; + onError: (e: string) => void; +}) => { + const { bond: currentBond, stakeSaturation, uncappedStakeSaturation } = node; + + const { fee, getFee, resetFeeState, feeError } = useGetFee(); + const [newBond, setNewBond] = useState(); + const [errorAmount, setErrorAmount] = useState(false); + + const { printBalance, userBalance } = useContext(AppContext); + + useEffect(() => { + if (feeError) { + onError(feeError); + } + }, [feeError]); + + const handleConfirm = async () => { + if (!newBond) { + return; + } + + await onUpdateBond( + { + currentPledge: currentBond, + newPledge: newBond, + fee: fee?.fee, + }, + 'balance', + ); + }; + + const handleAmountChanged = async (value: DecCoin) => { + const { amount } = value; + setNewBond(value); + if (!amount || !Number(amount)) { + setErrorAmount(true); + } else if (Big(amount).eq(currentBond.amount)) { + setErrorAmount(true); + } else { + const validAmount = await validateAmount(amount, '1'); + if (!validAmount) { + setErrorAmount(true); + return; + } + setErrorAmount(false); + } + }; + + const handleOnOk = async () => { + if (!newBond) { + return; + } + + await getFee(simulateUpdateBond, { + currentPledge: currentBond, + newPledge: newBond, + }); + }; + + const newBondToDisplay = () => { + const coin = decCoinToDisplay(newBond as DecCoin); + return `${coin.amount} ${coin.denom}`; + }; + + if (fee) + return ( + + + + {userBalance.balance?.amount.amount && fee?.amount?.amount && ( + + + + )} + + ); + + return ( + + + + { + handleAmountChanged(value); + }} + fullWidth + validationError={errorAmount ? 'Please enter a valid amount' : undefined} + /> + + + + + + {uncappedStakeSaturation ? ( + + ) : ( + + )} + + + + + ); +}; diff --git a/nym-wallet/src/components/Bonding/utils.ts b/nym-wallet/src/components/Bonding/utils.ts index 6e7b05a651..bb3be55e60 100644 --- a/nym-wallet/src/components/Bonding/utils.ts +++ b/nym-wallet/src/components/Bonding/utils.ts @@ -1,4 +1,4 @@ -import { Gateway, MixNode, MixNodeCostParams } from '@nymproject/types'; +import { Gateway, MixNode, NodeCostParams } from '@nymproject/types'; import { GatewayData, MixnodeAmount, MixnodeData } from '../../pages/bonding/types'; import { toPercentFloatString } from '../../utils'; @@ -14,7 +14,7 @@ export function mixnodeToTauri(data: MixnodeData): MixNode { }; } -export function costParamsToTauri(data: MixnodeAmount): MixNodeCostParams { +export function costParamsToTauri(data: MixnodeAmount): NodeCostParams { return { profit_margin_percent: toPercentFloatString(data.profitMargin), interval_operating_cost: { diff --git a/nym-wallet/src/components/NodeStatus.tsx b/nym-wallet/src/components/NodeStatus.tsx index 00ec32197d..99f92bcd18 100644 --- a/nym-wallet/src/components/NodeStatus.tsx +++ b/nym-wallet/src/components/NodeStatus.tsx @@ -32,5 +32,4 @@ export const NodeStatus = ({ status }: { status: MixnodeStatus }) => { default: return null; } - return null; }; diff --git a/nym-wallet/src/components/NymCard.tsx b/nym-wallet/src/components/NymCard.tsx index 768f0811ab..ae3d84fe23 100644 --- a/nym-wallet/src/components/NymCard.tsx +++ b/nym-wallet/src/components/NymCard.tsx @@ -20,7 +20,7 @@ export const NymCard: FCWithChildren<{ dataTestid?: string; sx?: SxProps; sxTitle?: SxProps; - children: React.ReactNode; + children?: React.ReactNode; }> = ({ title, subheader, Action, Icon, noPadding, borderless, children, dataTestid, sx, sxTitle }) => ( Promise; - bondMixnode: (data: TBondMixNodeArgs, tokenPool: TokenPool) => Promise; - bondGateway: (data: TBondGatewayArgs, tokenPool: TokenPool) => Promise; - unbond: (fee?: FeeDetails) => Promise; - updateBondAmount: (data: TUpdateBondArgs, tokenPool: TokenPool) => Promise; - redeemRewards: (fee?: FeeDetails) => Promise; - updateMixnode: (pm: string, fee?: FeeDetails) => Promise; - generateMixnodeMsgPayload: (data: TBondMixnodeSignatureArgs) => Promise; - generateGatewayMsgPayload: (data: TBondGatewaySignatureArgs) => Promise; + bondedNode?: TBondedNode | null; isVestingAccount: boolean; + refresh: () => void; + unbond: (fee?: FeeDetails) => Promise; + updateBondAmount: (data: TUpdateBondArgs) => Promise; + redeemRewards: (fee?: FeeDetails) => Promise; + generateNymNodeMsgPayload: (data: TNymNodeSignatureArgs) => Promise; migrateVestedMixnode: () => Promise; + migrateLegacyNode: () => Promise; }; export const BondingContext = createContext({ isLoading: true, refresh: async () => undefined, - bondMixnode: async () => { - throw new Error('Not implemented'); - }, - bondGateway: async () => { - throw new Error('Not implemented'); - }, unbond: async () => { throw new Error('Not implemented'); }, @@ -150,29 +50,27 @@ export const BondingContext = createContext({ redeemRewards: async () => { throw new Error('Not implemented'); }, - updateMixnode: async () => { - throw new Error('Not implemented'); - }, - generateMixnodeMsgPayload: async () => { - throw new Error('Not implemented'); - }, - generateGatewayMsgPayload: async () => { + generateNymNodeMsgPayload: async () => { throw new Error('Not implemented'); }, migrateVestedMixnode: async () => { throw new Error('Not implemented'); }, + migrateLegacyNode: async () => { + throw new Error('Not implemented'); + }, isVestingAccount: false, }); export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Element => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); - const [bondedNode, setBondedNode] = useState(); + const [isVestingAccount, setIsVestingAccount] = useState(false); - const { userBalance, clientDetails } = useContext(AppContext); - const { ownership, isLoading: isOwnershipLoading } = useCheckOwnership(); + const { userBalance, clientDetails, network } = useContext(AppContext); + + const { bondedNode, isLoading: isBondedNodeLoading } = useGetNodeDetails(clientDetails?.client_address, network); useEffect(() => { userBalance.fetchBalance(); @@ -186,317 +84,19 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen const resetState = () => { setError(undefined); - setBondedNode(undefined); - }; - - /** - * Fetch mixnode **optional** data. - * ⚠ The underlying queries are allowed to fail. - */ - const fetchMixnodeDetails = async (mixId: number, host: string, port: number) => { - const details: { - status: MixnodeStatus; - stakeSaturation: string; - estimatedRewards?: DecCoin; - uptime: number; - averageUptime?: number; - setProbability?: InclusionProbabilityResponse; - nodeDescription?: TNodeDescription | undefined; - operatorRewards?: DecCoin; - uncappedSaturation?: number; - } = { - status: 'not_found', - stakeSaturation: '0', - uptime: 0, - }; - - const statusReq: TauriReq = { - name: 'getMixnodeStatus', - request: () => getMixnodeStatus(mixId), - onFulfilled: (value) => { - details.status = value.status; - }, - }; - - const uptimeReq: TauriReq = { - name: 'getMixnodeUptime', - request: () => getMixnodeUptime(mixId), - onFulfilled: (value) => { - details.uptime = value; - }, - }; - - const stakeSaturationReq: TauriReq = { - name: 'getMixnodeStakeSaturation', - request: () => getMixnodeStakeSaturation(mixId), - onFulfilled: (value) => { - details.stakeSaturation = decimalToPercentage(value.saturation); - const rawUncappedSaturation = decimalToFloatApproximation(value.uncapped_saturation); - if (rawUncappedSaturation && rawUncappedSaturation > 1) { - details.uncappedSaturation = Math.round(rawUncappedSaturation * 100); - } - }, - }; - - const rewardReq: TauriReq = { - name: 'getMixnodeRewardEstimation', - request: () => getMixnodeRewardEstimation(mixId), - onFulfilled: (value) => { - const estimatedRewards = unymToNym(value.estimation.total_node_reward); - if (estimatedRewards) { - details.estimatedRewards = { - amount: estimatedRewards, - denom: 'nym', - }; - } - }, - }; - - const inclusionReq: TauriReq = { - name: 'getInclusionProbability', - request: () => getInclusionProbability(mixId), - onFulfilled: (value) => { - details.setProbability = value; - }, - }; - - const avgUptimeReq: TauriReq = { - name: 'getMixnodeAvgUptime', - request: () => getMixnodeAvgUptime(), - onFulfilled: (value) => { - details.averageUptime = value as number | undefined; - }, - }; - - const nodeDescReq: TauriReq = { - name: 'getNodeDescription', - request: () => getNodeDescriptionRequest(host, port), - onFulfilled: (value) => { - details.nodeDescription = value; - }, - }; - - const operatorRewardsReq: TauriReq = { - name: 'getPendingOperatorRewards', - request: () => getPendingOperatorRewards(clientDetails?.client_address || ''), - onFulfilled: (value) => { - details.operatorRewards = decCoinToDisplay(value); - }, - }; - - await fireRequests([ - statusReq, - uptimeReq, - stakeSaturationReq, - rewardReq, - inclusionReq, - avgUptimeReq, - nodeDescReq, - operatorRewardsReq, - ]); - - return details; - }; - - /** - * Fetch gateway **optional** data. - * ⚠ The underlying queries are allowed to fail. - */ - const fetchGatewayDetails = async (identityKey: string, host: string, port: number) => { - const details: { - routingScore?: { current: number; average: number } | undefined; - nodeDescription?: TNodeDescription | undefined; - } = {}; - - const reportReq: TauriReq = { - name: 'getGatewayReport', - request: () => getGatewayReport(identityKey), - onFulfilled: (value) => { - details.routingScore = { current: value.most_recent, average: value.last_day }; - }, - }; - - const nodeDescReq: TauriReq = { - name: 'getNodeDescription', - request: () => getNodeDescriptionRequest(host, port), - onFulfilled: (value) => { - details.nodeDescription = value; - }, - }; - - await fireRequests([reportReq, nodeDescReq]); - - return details; - }; - - const calculateStake = (pledge: string, delegations: string) => { - let stake; - try { - stake = unymToNym(Big(pledge).plus(delegations)); - } catch (e: any) { - Console.warn(`not a valid decimal number: ${e}`); - } - return stake; - }; - - const refresh = useCallback(async () => { - setIsLoading(true); - setError(undefined); - - if (ownership.hasOwnership && ownership.nodeType === EnumNodeType.mixnode && clientDetails) { - try { - const data = await getMixnodeBondDetails(); - if (data) { - const { - bond_information, - rewarding_details, - bond_information: { mix_id }, - } = data; - - const { - status, - stakeSaturation, - uncappedSaturation: uncappedStakeSaturation, - estimatedRewards, - uptime, - operatorRewards, - averageUptime, - nodeDescription, - setProbability, - } = await fetchMixnodeDetails( - mix_id, - bond_information.mix_node.host, - bond_information.mix_node.http_api_port, - ); - - setBondedNode({ - id: data.bond_information.mix_id, - name: nodeDescription?.name, - mixId: mix_id, - identityKey: bond_information.mix_node.identity_key, - stake: { - amount: calculateStake(rewarding_details.operator, rewarding_details.delegates), - denom: bond_information.original_pledge.denom, - }, - bond: decCoinToDisplay(bond_information.original_pledge), - profitMargin: toPercentIntegerString(rewarding_details.cost_params.profit_margin_percent), - delegators: rewarding_details.unique_delegations, - proxy: bond_information.proxy, - operatorRewards, - uptime, - status, - stakeSaturation, - uncappedStakeSaturation, - operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost), - host: bond_information.mix_node.host.replace(/\s/g, ''), - routingScore: averageUptime, - activeSetProbability: setProbability?.in_active, - standbySetProbability: setProbability?.in_reserve, - estimatedRewards, - 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, - isUnbonding: bond_information.is_unbonding, - } as TBondedMixnode); - } - } catch (e: any) { - Console.warn(e); - setError(`While fetching current bond state, an error occurred: ${e}`); - } - } - - if (ownership.hasOwnership && ownership.nodeType === EnumNodeType.gateway) { - try { - const data = await getGatewayBondDetails(); - if (data) { - const { gateway, proxy } = data; - const { nodeDescription, routingScore } = await fetchGatewayDetails( - gateway.identity_key, - data.gateway.host, - data.gateway.clients_port, - ); - setBondedNode({ - name: nodeDescription?.name, - identityKey: gateway.identity_key, - mixPort: gateway.mix_port, - httpApiPort: gateway.clients_port, - host: gateway.host, - ip: gateway.host, - location: gateway.location, - bond: decCoinToDisplay(data.pledge_amount), - proxy, - routingScore, - version: gateway.version, - } as TBondedGateway); - } - } catch (e: any) { - Console.warn(e); - setError(`While fetching current bond state, an error occurred: ${e}`); - } - } - - if (!ownership.hasOwnership) { - resetState(); - } setIsLoading(false); - }, [ownership]); - - useEffect(() => { - refresh(); - }, [ownership, refresh]); - - const bondMixnode = async (data: TBondMixNodeArgs, tokenPool: TokenPool) => { - let tx: TransactionExecuteResult | undefined; - setIsLoading(true); - try { - if (tokenPool === 'balance') { - tx = await bondMixNodeRequest(data); - await userBalance.fetchBalance(); - } - if (tokenPool === 'locked') { - tx = await vestingBondMixNode(data); - await userBalance.fetchTokenAllocation(); - } - return tx; - } catch (e: any) { - Console.warn(e); - setError(`an error occurred: ${e}`); - } finally { - setIsLoading(false); - } - return undefined; }; - const bondGateway = async (data: TBondGatewayArgs, tokenPool: TokenPool) => { - let tx: TransactionExecuteResult | undefined; - setIsLoading(true); - try { - if (tokenPool === 'balance') { - tx = await bondGatewayRequest(data); - await userBalance.fetchBalance(); - } - if (tokenPool === 'locked') { - tx = await vestingBondGateway(data); - await userBalance.fetchTokenAllocation(); - } - return tx; - } catch (e: any) { - Console.warn(e); - setError(`an error occurred: ${e}`); - } finally { - setIsLoading(false); - } - return undefined; + const refresh = () => { + resetState(); }; const unbond = async (fee?: FeeDetails) => { let tx; setIsLoading(true); try { - if (bondedNode && isMixnode(bondedNode) && bondedNode.proxy) tx = await vestingUnbondMixnode(fee?.fee); + if (bondedNode && isNymNode(bondedNode)) tx = await unbondNymNodeRequest(fee?.fee); if (bondedNode && isMixnode(bondedNode) && !bondedNode.proxy) tx = await unbondMixnodeRequest(fee?.fee); - if (bondedNode && isGateway(bondedNode) && bondedNode.proxy) tx = await vestingUnbondGateway(fee?.fee); if (bondedNode && isGateway(bondedNode) && !bondedNode.proxy) tx = await unbondGatewayRequest(fee?.fee); } catch (e) { Console.warn(e); @@ -507,35 +107,11 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen return tx; }; - const updateMixnode = async (pm: string, fee?: FeeDetails) => { - let tx; - setIsLoading(true); - - // TODO: this will have to be updated with allowing users to provide their operating cost in the form - const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(pm)); - - try { - // JS: this check is not entirely valid. you can have proxy field set whilst not using the vesting contract, - // you have to check if proxy exists AND if it matches the known vesting contract address! - if (bondedNode?.proxy) { - tx = await updateMixnodeVestingCostParamsRequest(defaultCostParams, fee?.fee); - } else { - tx = await updateMixnodeCostParamsRequest(defaultCostParams, fee?.fee); - } - } catch (e: any) { - Console.warn(e); - setError(`an error occurred: ${e}`); - } finally { - setIsLoading(false); - } - return tx; - }; - const redeemRewards = async (fee?: FeeDetails) => { let tx; setIsLoading(true); try { - if (bondedNode?.proxy) tx = await vestingClaimOperatorReward(fee?.fee); + if (bondedNode && !isNymNode(bondedNode)) tx = await vestingClaimOperatorReward(fee?.fee); else tx = await claimOperatorReward(fee?.fee); } catch (e: any) { setError(`an error occurred: ${e}`); @@ -545,18 +121,12 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen return tx; }; - const updateBondAmount = async (data: TUpdateBondArgs, tokenPool: TokenPool) => { + const updateBondAmount = async (data: TUpdateBondArgs) => { let tx: TransactionExecuteResult | undefined; setIsLoading(true); try { - if (tokenPool === 'balance') { - tx = await updateBondReq(data); - await userBalance.fetchBalance(); - } - if (tokenPool === 'locked') { - tx = await vestingUpdateBondReq(data); - await userBalance.fetchTokenAllocation(); - } + tx = await updateBondReq(data); + await userBalance.fetchBalance(); return tx; } catch (e: any) { @@ -568,40 +138,26 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen return undefined; }; - const generateMixnodeMsgPayload = async (data: TBondMixnodeSignatureArgs) => { - let message; + const generateNymNodeMsgPayload = async (data: TNymNodeSignatureArgs) => { setIsLoading(true); - try { - if (data.tokenPool === 'locked') { - message = await vestingGenerateMixnodeMsgPayloadReq(data); - } else { - message = await generateMixnodeMsgPayloadReq(data); - } - } catch (e) { - Console.warn(e); - setError(`an error occurred: ${e}`); - } finally { - setIsLoading(false); - } - return message; - }; - const generateGatewayMsgPayload = async (data: TBondGatewaySignatureArgs) => { - let message; - setIsLoading(true); try { - if (data.tokenPool === 'locked') { - message = await vestingGenerateGatewayMsgPayloadReq(data); - } else { - message = await generateGatewayMsgPayloadReq(data); - } + const message = await generateNymNodeMsgPayloadReq({ + nymNode: data.nymNode, + pledge: data.pledge, + costParams: { + ...data.costParams, + profit_margin_percent: toPercentFloatString(data.costParams.profit_margin_percent), + }, + }); + return message; } catch (e) { Console.warn(e); setError(`an error occurred: ${e}`); } finally { setIsLoading(false); } - return message; + return undefined; }; const migrateVestedMixnode = async () => { @@ -611,24 +167,36 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen return tx; }; + const migrateLegacyNode = async () => { + setIsLoading(true); + let tx: TransactionExecuteResult | undefined; + + if (bondedNode && isMixnode(bondedNode)) { + tx = await migrateLegacyMixnodeReq(); + } + if (bondedNode && isGateway(bondedNode)) { + tx = await migrateLegacyGatewayReq(); + } + + setIsLoading(false); + return tx; + }; + const memoizedValue = useMemo( () => ({ - isLoading: isLoading || isOwnershipLoading, + isLoading: isLoading || isBondedNodeLoading, error, - bondMixnode, bondedNode, - bondGateway, unbond, - updateMixnode, refresh, redeemRewards, updateBondAmount, - generateMixnodeMsgPayload, - generateGatewayMsgPayload, + generateNymNodeMsgPayload, migrateVestedMixnode, + migrateLegacyNode, isVestingAccount, }), - [isLoading, isOwnershipLoading, error, bondedNode, isVestingAccount], + [isLoading, error, bondedNode, isVestingAccount, isBondedNodeLoading], ); return {children}; diff --git a/nym-wallet/src/context/mocks/bonding.tsx b/nym-wallet/src/context/mocks/bonding.tsx index e961272c66..2ef46237f9 100644 --- a/nym-wallet/src/context/mocks/bonding.tsx +++ b/nym-wallet/src/context/mocks/bonding.tsx @@ -1,9 +1,10 @@ import { FeeDetails, TransactionExecuteResult } from '@nymproject/types'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import type { Network } from 'src/types'; -import { BondingContext, TBondedGateway, TBondedMixnode } from '../bonding'; +import type { Network, TNymNodeSignatureArgs } from 'src/types'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; +import { TBondedGateway } from 'src/requests/gatewayDetails'; +import { BondingContext } from '../bonding'; import { mockSleep } from './utils'; -import { TBondGatewaySignatureArgs, TBondMixnodeSignatureArgs } from '../../types'; const SLEEP_MS = 1000; @@ -30,10 +31,11 @@ const bondedMixnodeMock: TBondedMixnode = { version: '1.0.2', isUnbonding: false, uptime: 1, + proxy: null, + uncappedStakeSaturation: 100, }; const bondedGatewayMock: TBondedGateway = { - id: 1, name: 'Monster node', identityKey: 'WayM2fYbtN6kxMwp1TrmQ4VwPks3URR5pBgWPWhzT98F', ip: '112.43.234.57', @@ -41,17 +43,18 @@ const bondedGatewayMock: TBondedGateway = { host: '1.2.34.5 ', httpApiPort: 8000, mixPort: 1789, - verlocPort: 1790, version: '1.0.2', routingScore: { average: 100, current: 100, }, + location: 'Germany', + proxy: null, }; const TxResultMock: TransactionExecuteResult = { logs_json: '', - data_json: '', + msg_responses_json: '', transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354', gas_info: { gas_wanted: { gas_units: BigInt(1) }, @@ -170,15 +173,7 @@ export const MockBondingContextProvider = ({ return feeMock; }; - const generateMixnodeMsgPayload = async (_data: TBondMixnodeSignatureArgs) => { - setIsLoading(true); - await mockSleep(SLEEP_MS); - triggerStateUpdate(); - setIsLoading(false); - return '77dcaba7f41409984f4ebce4a386f59b10f1e65ed5514d1acdccae30174bd84b'; - }; - - const generateGatewayMsgPayload = async (_data: TBondGatewaySignatureArgs) => { + const generateNymNodeMsgPayload = async (_data: TNymNodeSignatureArgs) => { setIsLoading(true); await mockSleep(SLEEP_MS); triggerStateUpdate(); @@ -204,10 +199,10 @@ export const MockBondingContextProvider = ({ updateMixnode, updateBondAmount, checkOwnership, - generateMixnodeMsgPayload, - generateGatewayMsgPayload, + generateNymNodeMsgPayload, isVestingAccount: false, migrateVestedMixnode: async () => undefined, + migrateLegacyNode: async () => undefined, }), [isLoading, error, bondedMixnode, bondedGateway, trigger, fee], ); diff --git a/nym-wallet/src/context/mocks/delegations.tsx b/nym-wallet/src/context/mocks/delegations.tsx index 0c984d7369..f2aea600bc 100644 --- a/nym-wallet/src/context/mocks/delegations.tsx +++ b/nym-wallet/src/context/mocks/delegations.tsx @@ -108,7 +108,7 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { return { logs_json: '', - data_json: '', + msg_responses_json: '', gas_info: { gas_wanted: { gas_units: BigInt(1) }, gas_used: { gas_units: BigInt(1) }, @@ -183,7 +183,7 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { return { logs_json: '', - data_json: '', + msg_responses_json: '', transaction_hash: '', gas_info: { gas_wanted: { gas_units: BigInt(1) }, @@ -195,8 +195,9 @@ export const MockDelegationContextProvider: FCWithChildren = ({ children }) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const undelegateVesting = async (mix_id: number, _fee?: FeeDetails) => ({ - logs_json: '', + msg_responses_json: '', data_json: '', + logs_json: '', transaction_hash: '', gas_info: { gas_wanted: { gas_units: BigInt(1) }, diff --git a/nym-wallet/src/context/mocks/rewards.tsx b/nym-wallet/src/context/mocks/rewards.tsx index c89fdb00b3..fe84e5cfe2 100644 --- a/nym-wallet/src/context/mocks/rewards.tsx +++ b/nym-wallet/src/context/mocks/rewards.tsx @@ -65,8 +65,8 @@ export const MockRewardsContextProvider: FCWithChildren = ({ children }) => { amount: '1', denom: 'nym', }, - data_json: '[]', logs_json: '[]', + msg_responses_json: '[]', gas_info: { gas_wanted: { gas_units: BigInt(1) }, gas_used: { gas_units: BigInt(1) }, @@ -78,7 +78,7 @@ export const MockRewardsContextProvider: FCWithChildren = ({ children }) => { amount: '1', denom: 'nym', }, - data_json: '[]', + msg_responses_json: '[]', logs_json: '[]', gas_info: { gas_wanted: { gas_units: BigInt(1) }, diff --git a/nym-wallet/src/hooks/useGetNodeDetails.ts b/nym-wallet/src/hooks/useGetNodeDetails.ts new file mode 100644 index 0000000000..9049b87ce5 --- /dev/null +++ b/nym-wallet/src/hooks/useGetNodeDetails.ts @@ -0,0 +1,69 @@ +import { useEffect, useState } from 'react'; +import { TBondedNode } from 'src/context'; +import { getGatewayDetails } from 'src/requests/gatewayDetails'; +import { getMixnodeDetails } from 'src/requests/mixnodeDetails'; +import { getNymNodeDetails } from 'src/requests/nymNodeDetails'; +import { fireRequests, TauriReq } from 'src/utils'; + +const useGetNodeDetails = (clientAddress?: string, network?: string) => { + const [bondedNode, setBondedNode] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + + const getNodeDetails = async (address: string) => { + setIsError(false); + setBondedNode(null); + setIsLoading(true); + + // Check if the address has a Nym node bonded + const nymnode: TauriReq = { + name: 'getNymNodeBondDetails', + request: () => getNymNodeDetails(address), + onFulfilled: (value) => { + if (value) { + setBondedNode(value); + } + }, + }; + + // Check if the address has a Mix node bonded + const mixnode: TauriReq = { + name: 'getMixnodeDetails', + request: () => getMixnodeDetails(address), + onFulfilled: (value) => { + if (value) { + setBondedNode(value); + } + }, + }; + + // Check if the address has a Gateway bonded + const gateway: TauriReq = { + name: 'getGatewayDetails', + request: () => getGatewayDetails(), + onFulfilled: (value) => { + if (value) { + setBondedNode(value); + } + }, + }; + + await fireRequests([nymnode, mixnode, gateway]); + + setIsLoading(false); + }; + + useEffect(() => { + if (clientAddress) { + getNodeDetails(clientAddress); + } + }, [clientAddress, network]); + + return { + bondedNode, + isLoading, + isError, + }; +}; + +export default useGetNodeDetails; diff --git a/nym-wallet/src/pages/Admin/index.tsx b/nym-wallet/src/pages/Admin/index.tsx index 978360f254..7a0876c5a3 100644 --- a/nym-wallet/src/pages/Admin/index.tsx +++ b/nym-wallet/src/pages/Admin/index.tsx @@ -26,29 +26,15 @@ const AdminForm: FCWithChildren<{ - - - diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index 3ab472d1e4..843ada1f5a 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -2,55 +2,76 @@ import React, { useContext, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { FeeDetails } from '@nymproject/types'; import { Alert, AlertTitle, Box, Button, Typography } from '@mui/material'; -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 { UpdateBondAmountModal } from 'src/components/Bonding/modals/UpdateBondAmountModal'; import { BondOversaturatedModal } from 'src/components/Bonding/modals/BondOversaturatedModal'; import { ConfirmationDetailProps, ConfirmationDetailsModal } from 'src/components/Bonding/modals/ConfirmationModal'; 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, TUpdateBondArgs } from 'src/types'; +import { isGateway, isMixnode, isNymNode, TUpdateBondArgs } from 'src/types'; import { BondedGateway } from 'src/components/Bonding/BondedGateway'; import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal'; import { VestingWarningModal } from 'src/components/VestingWarningModal'; +import MigrateLegacyNode from 'src/components/Bonding/modals/MigrateLegacyNode'; +import { BondedNymNode } from 'src/components/Bonding/BondedNymNode'; +import { UpdateBondAmountNymNode } from 'src/components/Bonding/modals/UpdateBondAmountNymNode'; +import { BondNymNodeModalWithState } from 'src/components/Bonding/modals/BondNymNodeModal'; import { BondingContextProvider, useBondingContext } from '../../context'; export const Bonding = () => { const [showModal, setShowModal] = useState< - 'bond-mixnode' | 'bond-gateway' | 'update-bond' | 'update-bond-oversaturated' | 'unbond' | 'redeem' + | 'bond-mixnode' + | 'bond-nymnode' + | 'bond-gateway' + | 'update-bond' + | 'update-bond-oversaturated' + | 'unbond' + | 'redeem' + | 'update-bond-nymnode' >(); - const [confirmationDetails, setConfirmationDetails] = useState(); - const [uncappedSaturation, setUncappedSaturation] = useState(); - const [showMigrationModal, setShowMigrationModal] = useState(false); - const { - network, - clientDetails, - userBalance: { originalVesting }, - } = useContext(AppContext); + + const { network } = useContext(AppContext); const navigate = useNavigate(); const { bondedNode, - bondMixnode, - bondGateway, - redeemRewards, isLoading, - updateBondAmount, error, + redeemRewards, + updateBondAmount, refresh, migrateVestedMixnode, + migrateLegacyNode, } = useBondingContext(); + const shouldShowMigrateLegacyNodeModal = () => { + if (!bondedNode) { + return false; + } + if (isMixnode(bondedNode) && !bondedNode.isUnbonding) { + return true; + } + if (isGateway(bondedNode)) { + return true; + } + return false; + }; + + const [confirmationDetails, setConfirmationDetails] = useState(); + const [uncappedSaturation, setUncappedSaturation] = useState(); + const [showMigrationModal, setShowMigrationModal] = useState(false); + const [showMigrateLegacyNodeModal, setShowMigrateLegacyNodeModal] = useState(false); + useEffect(() => { if (bondedNode && isMixnode(bondedNode) && bondedNode.uncappedStakeSaturation) { setUncappedSaturation(bondedNode.uncappedStakeSaturation); } + + setShowMigrateLegacyNodeModal(shouldShowMigrateLegacyNodeModal()); }, [bondedNode]); const handleMigrateVestedMixnode = async () => { @@ -65,6 +86,18 @@ export const Bonding = () => { } }; + const handleMigrateLegacyNode = async () => { + setShowMigrateLegacyNodeModal(false); + const tx = await migrateLegacyNode(); + if (tx) { + setConfirmationDetails({ + status: 'success', + title: 'Migration successful', + txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, + }); + } + }; + const handleCloseModal = async () => { setShowModal(undefined); refresh(); @@ -79,35 +112,10 @@ export const Bonding = () => { }); }; - const handleBondMixnode = async (data: TBondMixNodeArgs, tokenPool: TPoolOption) => { - setShowModal(undefined); - const tx = await bondMixnode(data, tokenPool); - if (tx) { - 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); - if (tx) { - setConfirmationDetails({ - status: 'success', - title: 'Bond successful', - txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, - }); - } - }; - - const handleUpdateBond = async (data: TUpdateBondArgs, tokenPool: TPoolOption) => { + const handleUpdateBond = async (data: TUpdateBondArgs) => { setShowModal(undefined); - const tx = await updateBondAmount(data, tokenPool); + const tx = await updateBondAmount(data); if (tx) { setConfirmationDetails({ status: 'success', @@ -154,13 +162,34 @@ export const Bonding = () => { return undefined; }; + const handleBondedNymNodeAction = async (action: TBondedMixnodeActions) => { + switch (action) { + case 'unbond': { + navigate('/bonding/node-settings', { state: 'unbond' }); + break; + } + case 'updateBond': { + setShowModal('update-bond-nymnode'); + break; + } + case 'redeem': { + setShowModal('redeem'); + break; + } + default: { + return undefined; + } + } + return undefined; + }; + if (error) { return refresh()} />; } return ( - {bondedNode?.proxy && ( + {bondedNode && !isNymNode(bondedNode) && bondedNode?.proxy && ( Your bonded node is using tokens from the vesting contract! @@ -187,41 +216,41 @@ export const Bonding = () => { }} /> - {!bondedNode && setShowModal('bond-mixnode')} />} + setShowMigrateLegacyNodeModal(false)} + handleMigrate={handleMigrateLegacyNode} + /> + + {!bondedNode && setShowModal('bond-nymnode')} />} + + {bondedNode && isNymNode(bondedNode) && ( + handleBondedNymNodeAction(action)} + /> + )} {bondedNode && isMixnode(bondedNode) && ( setShowMigrateLegacyNodeModal(true)} onActionSelect={(action) => handleBondedMixnodeAction(action)} /> )} {bondedNode && isGateway(bondedNode) && ( - - )} - - {showModal === 'bond-mixnode' && ( - setShowModal('bond-gateway')} - onClose={() => setShowModal(undefined)} - onError={handleError} + setShowMigrateLegacyNodeModal(true)} + onActionSelect={handleBondedMixnodeAction} /> )} - {showModal === 'bond-gateway' && ( - setShowModal('bond-mixnode')} - onClose={() => setShowModal(undefined)} - onError={handleError} - /> - )} + {showModal === 'update-bond-oversaturated' && uncappedSaturation && ( { /> )} + {showModal === 'update-bond-nymnode' && bondedNode && isNymNode(bondedNode) && ( + setShowModal(undefined)} + onError={handleError} + /> + )} + {showModal === 'redeem' && bondedNode && isMixnode(bondedNode) && ( { - {isMixnode(bondedNode) ? 'Node' : 'Gateway'} Settings + Nym Node Settings { }} > - You should NOT shutdown your {isMixnode(bondedNode) ? 'mix node' : 'gateway'} until the unbond process is - complete + You should NOT shutdown your node until the unbond process is complete )} diff --git a/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx index 65edb35bc5..c288dc96e7 100644 --- a/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/apy-playground/index.tsx @@ -4,10 +4,10 @@ import { ResultsTable } from 'src/components/RewardsPlayground/ResultsTable'; import { getDelegationSummary } from 'src/requests'; import { NodeDetails } from 'src/components/RewardsPlayground/NodeDetail'; import { CalculateArgs, Inputs } from 'src/components/RewardsPlayground/Inputs'; -import { TBondedMixnode } from 'src/context'; import { useSnackbar } from 'notistack'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { Console } from 'src/utils/console'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; import { computeEstimate, computeStakeSaturation, handleCalculatePeriodRewards } from './utils'; export type DefaultInputValues = { diff --git a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx index 74e8d7cf5b..b308c03716 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-settings.constant.tsx @@ -1,7 +1,13 @@ -export const makeNavItems = (isMixnode: boolean) => { - const navItems: NavItems[] = ['General', 'Unbond']; +import { TBondedNode } from 'src/context'; +import { isNymNode } from 'src/types'; - if (isMixnode) navItems.splice(1, 0, 'Test my node', 'Playground'); +export const makeNavItems = (bondedNode: TBondedNode) => { + const navItems: NavItems[] = ['Unbond']; + + if (isNymNode(bondedNode)) { + // add these items to the beginning of the array "General", "Test my node", "Playground" + navItems.unshift('General', 'Test my node', 'Playground'); + } return navItems; }; diff --git a/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx b/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx index ba9bf522f3..2e1dc533f8 100644 --- a/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/node-test/index.tsx @@ -10,6 +10,7 @@ import { ErrorModal } from 'src/components/Modals/ErrorModal'; import { PrintResults } from 'src/components/TestNode/PrintResults'; import { MAINNET_VALIDATOR_URL, QA_VALIDATOR_URL } from 'src/constants'; import { TestStatus } from 'src/components/TestNode/types'; +import { isMixnode } from 'src/types'; export const NodeTestPage = () => { const [nodeTestClient, setNodeTestClient] = useState(); @@ -37,7 +38,7 @@ export const NodeTestPage = () => { }; const handleTestNode = async () => { - if (nodeTestClient && bondedNode) { + if (nodeTestClient && bondedNode && isMixnode(bondedNode)) { setResults(undefined); setTestDate(format(new Date(), 'dd/MM/yyyy HH:mm')); setIsLoading(true); @@ -87,7 +88,7 @@ export const NodeTestPage = () => { {isLoading && } {error && setError(undefined)} />} - {printResults && results && ( + {printResults && results && bondedNode && isMixnode(bondedNode) && ( Promise; onError: (e: string) => void; @@ -15,6 +15,9 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { const [confirmField, setConfirmField] = useState(''); const [isConfirmed, setIsConfirmed] = useState(false); // TODO: Check what happens with a gateway + + const shouldDisplayWarning = isMixnode(bondedNode) || isNymNode(bondedNode); + return ( @@ -24,7 +27,7 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { Unbond - {isMixnode(bondedNode) && ( + {shouldDisplayWarning && ( theme.palette.nym.text.muted }}> Remember you should only unbond if you want to remove your node from the network for good. @@ -35,7 +38,7 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { - {isMixnode(bondedNode) && ( + {shouldDisplayWarning && ( )} @@ -68,7 +71,7 @@ export const NodeUnbondPage = ({ bondedNode, onConfirm, onError }: Props) => { - {isConfirmed && ( + {isConfirmed && !isNymNode(bondedNode) && ( { diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx index 892c1e245c..7f8bdb6d08 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralGatewaySettings.tsx @@ -10,16 +10,17 @@ import { updateGatewayConfig, vestingUpdateGatewayConfig, } from 'src/requests'; -import { TBondedGateway, useBondingContext } from 'src/context/bonding'; +import { useBondingContext } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; import { Console } from 'src/utils/console'; import { Alert } from 'src/components/Alert'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { useGetFee } from 'src/hooks/useGetFee'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; -import { updateGatewayValidationSchema } from 'src/components/Bonding/forms/gatewayValidationSchema'; +import { updateGatewayValidationSchema } from 'src/components/Bonding/forms/legacyForms/gatewayValidationSchema'; import { BalanceWarning } from 'src/components/FeeWarning'; import { AppContext } from 'src/context'; +import { TBondedGateway } from 'src/requests/gatewayDetails'; export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGateway }) => { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); @@ -56,7 +57,6 @@ export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGate location, version: clean(version) as string, clients_port: httpApiPort, - verloc_port: bondedNode.verlocPort, }; if (bondedNode.proxy) { @@ -206,7 +206,6 @@ export const GeneralGatewaySettings = ({ bondedNode }: { bondedNode: TBondedGate clients_port: data.httpApiPort, location: bondedNode.location!, version: data.version, - verloc_port: bondedNode.verlocPort, }), )} sx={{ m: 3 }} diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx index 509c605174..08304622c7 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralMixnodeSettings.tsx @@ -6,9 +6,8 @@ import { Box, Button, Divider, Grid, Stack, TextField, Typography } from '@mui/m import { useTheme } from '@mui/material/styles'; import { isMixnode } from 'src/types'; import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests'; -import { TBondedGateway, TBondedMixnode } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; -import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; +import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/legacyForms/mixnodeValidationSchema'; import { Console } from 'src/utils/console'; import { Alert } from 'src/components/Alert'; import { vestingUpdateMixnodeConfig } from 'src/requests/vesting'; @@ -17,8 +16,9 @@ import { useGetFee } from 'src/hooks/useGetFee'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { BalanceWarning } from 'src/components/FeeWarning'; import { AppContext } from 'src/context'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; -export const GeneralMixnodeSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => { +export const GeneralMixnodeSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }) => { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const { getFee, fee, resetFeeState } = useGetFee(); const { userBalance } = useContext(AppContext); diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralNymNodeSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralNymNodeSettings.tsx new file mode 100644 index 0000000000..76721d9937 --- /dev/null +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/GeneralNymNodeSettings.tsx @@ -0,0 +1,182 @@ +import React, { useContext, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { Box, Button, Divider, Grid, Stack, TextField, Typography } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { updateNymNodeConfig } from 'src/requests'; +import { SimpleModal } from 'src/components/Modals/SimpleModal'; +import { bondedInfoParametersValidationSchema } from 'src/components/Bonding/forms/legacyForms/mixnodeValidationSchema'; +import { Console } from 'src/utils/console'; +import { Alert } from 'src/components/Alert'; +import { ConfirmTx } from 'src/components/ConfirmTX'; +import { useGetFee } from 'src/hooks/useGetFee'; +import { LoadingModal } from 'src/components/Modals/LoadingModal'; +import { BalanceWarning } from 'src/components/FeeWarning'; +import { AppContext } from 'src/context'; +import { TBondedNymNode } from 'src/requests/nymNodeDetails'; + +export const GeneralNymNodeSettings = ({ bondedNode }: { bondedNode: TBondedNymNode }) => { + const [openConfirmationModal, setOpenConfirmationModal] = useState(false); + const { fee, resetFeeState } = useGetFee(); + const { userBalance } = useContext(AppContext); + + const theme = useTheme(); + + const { + register, + handleSubmit, + formState: { errors, isSubmitting, isDirty, isValid }, + } = useForm({ + resolver: yupResolver(bondedInfoParametersValidationSchema), + mode: 'onChange', + defaultValues: { + host: bondedNode.host, + customHttpPort: bondedNode.customHttpPort, + }, + }); + + const onSubmit = async (data: { host?: string; customHttpPort?: number | null }) => { + resetFeeState(); + const { host, customHttpPort } = data; + if (host && customHttpPort) { + const NymNodeConfigParams = { + host, + custom_http_port: customHttpPort, + }; + try { + await updateNymNodeConfig(NymNodeConfigParams); + setOpenConfirmationModal(true); + } catch (error) { + Console.error(error); + } + } + }; + + return ( + + {fee && ( + onSubmit(d))} + onPrev={resetFeeState} + onClose={resetFeeState} + > + {fee.amount?.amount && userBalance?.balance?.amount.amount && ( + + + + )} + + )} + {isSubmitting && } + + + Changing these values will ONLY change the data about your node on the blockchain. + + Remember to change your node’s config file with the same values too. + + } + bgColor={`${theme.palette.nym.nymWallet.text.blue}0D !important`} + dismissable + /> + + + + + Port + + + + + + + + + + + + + Host + + + + + + + + + + + + + + + + + + { + 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, + }} + subHeaderStyles={{ + width: '100%', + mb: 1, + textAlign: 'center', + color: 'main', + fontSize: 14, + }} + /> + + ); +}; diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx index 4f2032ac30..6f8ed79ec9 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/ParametersSettings.tsx @@ -14,7 +14,7 @@ import { Typography, } from '@mui/material'; import { useTheme } from '@mui/material/styles'; -import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types'; +import { CurrencyDenom, NodeCostParams } from '@nymproject/types'; import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField'; import { isMixnode } from 'src/types'; import { @@ -22,11 +22,9 @@ import { simulateUpdateMixnodeCostParams, simulateVestingUpdateMixnodeCostParams, updateMixnodeCostParams, - vestingUpdateMixnodeCostParams, } from 'src/requests'; -import { TBondedMixnode } from 'src/context/bonding'; import { SimpleModal } from 'src/components/Modals/SimpleModal'; -import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/mixnodeValidationSchema'; +import { bondedNodeParametersValidationSchema } from 'src/components/Bonding/forms/legacyForms/mixnodeValidationSchema'; import { Console } from 'src/utils/console'; import { getIntervalAsDate } from 'src/utils'; import { Alert } from 'src/components/Alert'; @@ -36,6 +34,7 @@ import { useGetFee } from 'src/hooks/useGetFee'; import { ConfirmTx } from 'src/components/ConfirmTX'; import { LoadingModal } from 'src/components/Modals/LoadingModal'; import { InfoOutlined } from '@mui/icons-material'; +import { TBondedMixnode } from 'src/requests/mixnodeDetails'; const operatorCostHint = `This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch.You can redeem your rewards at any time. `; @@ -45,7 +44,7 @@ const profitMarginHint = export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => { const [openConfirmationModal, setOpenConfirmationModal] = useState(false); const [intervalTime, setIntervalTime] = useState(); - const [pendingUpdates, setPendingUpdates] = useState(); + const [pendingUpdates, setPendingUpdates] = useState(); const { clientDetails } = useContext(AppContext); const theme = useTheme(); @@ -115,11 +114,8 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }, }; try { - if (bondedNode.proxy) { - await vestingUpdateMixnodeCostParams(mixNodeCostParams); - } else { - await updateMixnodeCostParams(mixNodeCostParams); - } + await updateMixnodeCostParams(mixNodeCostParams); + await getPendingEvents(); reset(); setOpenConfirmationModal(true); diff --git a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx index b49958ae42..7c1f4568b4 100644 --- a/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx +++ b/nym-wallet/src/pages/bonding/node-settings/settings-pages/general-settings/index.tsx @@ -1,12 +1,13 @@ import React, { useState } from 'react'; import { Box, Button, Divider, Grid } from '@mui/material'; -import { isGateway, isMixnode } from 'src/types'; -import { TBondedMixnode, TBondedGateway } from 'src/context/bonding'; +import { isGateway, isMixnode, isNymNode } from 'src/types'; +import { TBondedNode } from 'src/context/bonding'; import { GeneralMixnodeSettings } from './GeneralMixnodeSettings'; import { ParametersSettings } from './ParametersSettings'; import { GeneralGatewaySettings } from './GeneralGatewaySettings'; +import { GeneralNymNodeSettings } from './GeneralNymNodeSettings'; -const makeGeneralNav = (bondedNode: TBondedMixnode | TBondedGateway) => { +const makeGeneralNav = (bondedNode: TBondedNode) => { const navItems = ['Info']; if (isMixnode(bondedNode)) { navItems.push('Parameters'); @@ -15,7 +16,7 @@ const makeGeneralNav = (bondedNode: TBondedMixnode | TBondedGateway) => { return navItems; }; -export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => { +export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedNode }) => { const [navSelection, setNavSelection] = useState(0); const getSettings = () => { @@ -23,10 +24,12 @@ export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode case 0: { if (isMixnode(bondedNode)) return ; if (isGateway(bondedNode)) return ; + if (isNymNode(bondedNode)) return ; break; } case 1: { if (isMixnode(bondedNode)) return ; + if (isNymNode(bondedNode)) return null; break; } default: diff --git a/nym-wallet/src/pages/bonding/types.ts b/nym-wallet/src/pages/bonding/types.ts index 0e2b4a6c8d..d2c8d3ae9f 100644 --- a/nym-wallet/src/pages/bonding/types.ts +++ b/nym-wallet/src/pages/bonding/types.ts @@ -1,4 +1,4 @@ -import { DecCoin, MixNodeCostParams, TNodeType, TransactionExecuteResult } from '@nymproject/types'; +import { DecCoin, NodeCostParams, TNodeType, TransactionExecuteResult } from '@nymproject/types'; import { TPoolOption } from 'src/components'; export type FormStep = 1 | 2 | 3 | 4; @@ -61,5 +61,5 @@ export interface BondState { export interface ChangeMixCostParams { mix_id: number; - new_costs: MixNodeCostParams; + new_costs: NodeCostParams; } diff --git a/nym-wallet/src/pages/node-settings/system-variables.tsx b/nym-wallet/src/pages/node-settings/system-variables.tsx index 761d98444c..13fb9a60c6 100644 --- a/nym-wallet/src/pages/node-settings/system-variables.tsx +++ b/nym-wallet/src/pages/node-settings/system-variables.tsx @@ -7,8 +7,7 @@ import { yupResolver } from '@hookform/resolvers/yup'; import { InclusionProbabilityResponse, SelectionChance } from '@nymproject/types'; import { validationSchema } from './validationSchema'; import { InfoTooltip } from '../../components'; -import { useCheckOwnership } from '../../hooks/useCheckOwnership'; -import { updateMixnodeCostParams, vestingUpdateMixnodeCostParams } from '../../requests'; +import { updateMixnodeCostParams } from '../../requests'; import { AppContext } from '../../context'; import { Console } from '../../utils/console'; import { attachDefaultOperatingCost, toPercentFloatString } from '../../utils'; @@ -74,7 +73,6 @@ export const SystemVariables = ({ }) => { const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>(); const { mixnodeDetails } = useContext(AppContext); - const { ownership } = useCheckOwnership(); const { register, @@ -106,12 +104,6 @@ export const SystemVariables = ({ await updateMixnodeCostParams(defaultCostParams); }; - const vestingUpdateMixnodeProfitMargin = async (profitMarginPercent: string) => { - // TODO: this will have to be updated with allowing users to provide their operating cost in the form - const defaultCostParams = await attachDefaultOperatingCost(toPercentFloatString(profitMarginPercent)); - await vestingUpdateMixnodeCostParams(defaultCostParams); - }; - if (!mixnodeDetails) return null; return ( @@ -175,12 +167,7 @@ export const SystemVariables = ({