diff --git a/nym-wallet/src/components/ActionsMenu.tsx b/nym-wallet/src/components/ActionsMenu.tsx index 63e8193833..227af88407 100644 --- a/nym-wallet/src/components/ActionsMenu.tsx +++ b/nym-wallet/src/components/ActionsMenu.tsx @@ -13,7 +13,7 @@ export const ActionsMenu: React.FC<{ open: boolean; onOpen: () => void; onClose: return ( <> - + t.palette.nym.nymWallet.text.main }} /> {children} diff --git a/nym-wallet/src/components/Bonding/BondedGateway.tsx b/nym-wallet/src/components/Bonding/BondedGateway.tsx index 26849bbb7c..15a5d26367 100644 --- a/nym-wallet/src/components/Bonding/BondedGateway.tsx +++ b/nym-wallet/src/components/Bonding/BondedGateway.tsx @@ -37,11 +37,11 @@ export const BondedGateway = ({ const cells: Cell[] = [ { cell: ip, - id: 'stake-saturation-cell', + id: 'ip-cell', }, { cell: `${bond.amount} ${bond.denom}`, - id: 'stake-cell', + id: 'bond-cell', sx: { pl: 0 }, }, diff --git a/nym-wallet/src/components/Bonding/BondedMixnode.tsx b/nym-wallet/src/components/Bonding/BondedMixnode.tsx index 974cd23ba4..3faee0e4b9 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnode.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnode.tsx @@ -30,6 +30,11 @@ const headers: Header[] = [ 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: 'Operator cost', + id: 'operator-cost', + // tooltipText: 'TODO', // TODO + }, { header: 'Operator rewards', id: 'operator-rewards', @@ -55,8 +60,18 @@ export const BondedMixnode = ({ network?: Network; onActionSelect: (action: TBondedMixnodeActions) => void; }) => { - const { name, stake, bond, stakeSaturation, profitMargin, operatorRewards, delegators, status, identityKey } = - mixnode; + const { + name, + stake, + bond, + stakeSaturation, + profitMargin, + operatorRewards, + operatorCost, + delegators, + status, + identityKey, + } = mixnode; const cells: Cell[] = [ { cell: `${stake.amount} ${stake.denom}`, @@ -74,6 +89,10 @@ export const BondedMixnode = ({ cell: `${profitMargin}%`, id: 'pm-cell', }, + { + cell: operatorCost ? `${operatorCost} USD` : '-', + id: 'operator-cost-cell', + }, { cell: operatorRewards ? `${operatorRewards.amount} ${operatorRewards.denom}` : '-', id: 'operator-rewards-cell', @@ -120,7 +139,7 @@ export const BondedMixnode = ({ onClick={() => onActionSelect('nodeSettings')} startIcon={} > - Settings + Node Settings } > diff --git a/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx b/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx index 83cc2a0198..7cbb352526 100644 --- a/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx +++ b/nym-wallet/src/components/Bonding/BondedMixnodeActions.tsx @@ -1,9 +1,9 @@ import React, { useState } from 'react'; import { Typography } from '@mui/material'; import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu'; -import { Unbond as UnbondIcon } from '../../svg-icons'; +import { Unbond as UnbondIcon, Bond as BondIcon } from '../../svg-icons'; -export type TBondedMixnodeActions = 'nodeSettings' | 'bondMore' | 'unbond' | 'redeem' | 'compound'; +export type TBondedMixnodeActions = 'nodeSettings' | 'bondMore' | 'unbond' | 'redeem'; export const BondedMixnodeActions = ({ onActionSelect, @@ -24,25 +24,22 @@ export const BondedMixnodeActions = ({ return ( + } + onClick={() => handleActionClick('bondMore')} + /> + R} + onClick={() => handleActionClick('redeem')} + disabled={disabledRedeemAndCompound} + /> } onClick={() => handleActionClick('unbond')} /> - C} - description={disabledRedeemAndCompound ? 'No rewards to compound' : 'Add your rewards to you balance'} - onClick={() => handleActionClick('compound')} - disabled={disabledRedeemAndCompound} - /> - R} - description={disabledRedeemAndCompound ? 'No rewards to redeem' : 'Add your rewards to you balance'} - onClick={() => handleActionClick('redeem')} - disabled={disabledRedeemAndCompound} - /> ); }; diff --git a/nym-wallet/src/context/bonding.tsx b/nym-wallet/src/context/bonding.tsx index c1f68b7756..54a4a32749 100644 --- a/nym-wallet/src/context/bonding.tsx +++ b/nym-wallet/src/context/bonding.tsx @@ -13,6 +13,9 @@ import { bondGateway as bondGatewayRequest, bondMixNode as bondMixNodeRequest, claimOperatorReward, + getGatewayBondDetails, + getMixnodeBondDetails, + getMixnodeRewardEstimation, unbondGateway as unbondGatewayRequest, unbondMixNode as unbondMixnodeRequest, vestingBondGateway, @@ -22,8 +25,6 @@ import { updateMixnodeCostParams as updateMixnodeCostParamsRequest, vestingUpdateMixnodeCostParams as updateMixnodeVestingCostParamsRequest, getNodeDescription as getNodeDescriptioRequest, - getGatewayBondDetails, - getMixnodeBondDetails, getMixnodeStatus, getPendingOperatorRewards, getMixnodeStakeSaturation, @@ -45,6 +46,8 @@ export type TBondedMixnode = { delegators: number; status: MixnodeStatus; proxy?: string; + operatorCost?: number; + host: string; }; export interface TBondedGateway { @@ -66,6 +69,7 @@ export type TBondingContext = { bondMixnode: (data: TBondMixNodeArgs, tokenPool: TokenPool) => Promise; bondGateway: (data: TBondGatewayArgs, tokenPool: TokenPool) => Promise; unbond: (fee?: FeeDetails) => Promise; + bondMore: (signature: string, amount: DecCoin, fee?: FeeDetails) => Promise; redeemRewards: (fee?: FeeDetails) => Promise; updateMixnode: (pm: string, fee?: FeeDetails) => Promise; checkOwnership: () => Promise; @@ -86,6 +90,9 @@ export const BondingContext = createContext({ unbond: async () => { throw new Error('Not implemented'); }, + bondMore: async () => { + throw new Error('Not implemented'); + }, redeemRewards: async () => { throw new Error('Not implemented'); }, @@ -113,10 +120,12 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod }; const getAdditionalMixnodeDetails = async (mixId: number) => { - const additionalDetails: { status: MixnodeStatus; stakeSaturation: string } = { + const additionalDetails: { status: MixnodeStatus; stakeSaturation: string; operatorCost?: number } = { status: 'not_found', stakeSaturation: '0', + operatorCost: 0, }; + try { const statusResponse = await getMixnodeStatus(mixId); additionalDetails.status = statusResponse.status; @@ -130,6 +139,12 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod } catch (e) { Console.log(e); } + try { + const rewardEstimation = await getMixnodeRewardEstimation(mixId); + additionalDetails.operatorCost = rewardEstimation.estimated_operator_cost; + } catch (e) { + Console.log(e); + } return additionalDetails; }; @@ -155,7 +170,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod Console.warn(`get_operator_rewards request failed: ${e}`); } if (data) { - const { status, stakeSaturation } = await getAdditionalMixnodeDetails(data.bond_information.id); + const { status, stakeSaturation, operatorCost } = await getAdditionalMixnodeDetails(data.bond_information.id); const nodeDescription = await getNodeDescription( data.bond_information.mix_node.host, data.bond_information.mix_node.http_api_port, @@ -175,6 +190,8 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod operatorRewards, status, stakeSaturation, + operatorCost, + host: data.bond_information.mix_node.host, } as TBondedMixnode); } } catch (e: any) { diff --git a/nym-wallet/src/context/mocks/bonding.tsx b/nym-wallet/src/context/mocks/bonding.tsx index 56072b1563..77e7e4d6f3 100644 --- a/nym-wallet/src/context/mocks/bonding.tsx +++ b/nym-wallet/src/context/mocks/bonding.tsx @@ -16,6 +16,8 @@ const bondedMixnodeMock: TBondedMixnode = { operatorRewards: { denom: 'nym', amount: '1234' }, delegators: 5423, status: 'active', + operatorCost: 2, + host: '1.2.3.4', }; const bondedGatewayMock: TBondedGateway = { @@ -122,14 +124,6 @@ export const MockBondingContextProvider = ({ return TxResultMock; }; - const compoundRewards = async (): Promise => { - setIsLoading(true); - await mockSleep(SLEEP_MS); - triggerStateUpdate(); - setIsLoading(false); - return TxResultMock; - }; - const updateMixnode = async (): Promise => { setIsLoading(true); await mockSleep(SLEEP_MS); @@ -165,7 +159,6 @@ export const MockBondingContextProvider = ({ unbond, refresh, redeemRewards, - compoundRewards, fee, feeLoading, getFee, diff --git a/nym-wallet/src/pages/bonding/index.tsx b/nym-wallet/src/pages/bonding/index.tsx index b0e3c20884..3d168c7637 100644 --- a/nym-wallet/src/pages/bonding/index.tsx +++ b/nym-wallet/src/pages/bonding/index.tsx @@ -1,5 +1,5 @@ import React, { useContext, useEffect, useState } from 'react'; -import { FeeDetails } from '@nymproject/types'; +import { FeeDetails, DecCoin } from '@nymproject/types'; import { TPoolOption } from 'src/components'; import { Bond } from 'src/components/Bonding/Bond'; import { BondedMixnode } from 'src/components/Bonding/BondedMixnode'; @@ -17,17 +17,18 @@ import { BondedGateway } from 'src/components/Bonding/BondedGateway'; import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal'; import { Box } from '@mui/material'; import { BondingContextProvider, useBondingContext } from '../../context'; +import { BondMoreModal } from '../../components/Bonding/modals/BondMoreModal'; const Bonding = () => { const [showModal, setShowModal] = useState< - 'bond-mixnode' | 'bond-gateway' | 'bond-more' | 'unbond' | 'redeem' | 'compound' | 'node-settings' + 'bond-mixnode' | 'bond-gateway' | 'bond-more' | 'unbond' | 'redeem' | 'node-settings' >(); const [confirmationDetails, setConfirmationDetails] = useState(); const { network, clientDetails, - userBalance: { originalVesting }, + userBalance: { originalVesting, balance, tokenAllocation }, } = useContext(AppContext); const { @@ -35,6 +36,7 @@ const Bonding = () => { bondMixnode, bondGateway, unbond, + bondMore, updateMixnode, redeemRewards, isLoading, @@ -143,6 +145,23 @@ const Bonding = () => { return undefined; }; + const handleBondMore = async ({ + signature, + additionalBond, + }: { + additionalBond: DecCoin; + signature: string; + tokenPool: TPoolOption; + }) => { + setShowModal(undefined); + const tx = await bondMore(signature, additionalBond); + setConfirmationDetails({ + status: 'success', + title: 'Bond more successful', + txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`, + }); + }; + return ( {!bondedNode && setShowModal('bond-mixnode')} />} @@ -180,6 +199,16 @@ const Bonding = () => { /> )} + {showModal === 'bond-more' && bondedNode && ( + 0} + onConfirm={handleBondMore} + onClose={() => setShowModal(undefined)} + /> + )} + {showModal === 'unbond' && bondedNode && ( export const getMixnodeStakeSaturation = async (mixId: number) => invokeWrapper('mixnode_stake_saturation', { mixId }); -// export const getMixnodeRewardEstimation = async (mixId: number) => -// invokeWrapper('mixnode_reward_estimation', { identity }); +export const getMixnodeRewardEstimation = async (mixId: number) => + invokeWrapper('mixnode_reward_estimation', { mixId }); export const getMixnodeStatus = async (mixId: number) => invokeWrapper('mixnode_status', { mixId }); diff --git a/nym-wallet/src/theme/mui-theme.d.ts b/nym-wallet/src/theme/mui-theme.d.ts index 2a6cd001a8..b0868079f7 100644 --- a/nym-wallet/src/theme/mui-theme.d.ts +++ b/nym-wallet/src/theme/mui-theme.d.ts @@ -40,6 +40,7 @@ declare module '@mui/material/styles' { grey: string; }; linkHover: string; + border: { menu: string }; } interface NymPaletteVariant { diff --git a/nym-wallet/src/theme/theme.tsx b/nym-wallet/src/theme/theme.tsx index 494ef6408a..ef6c9dc4e6 100644 --- a/nym-wallet/src/theme/theme.tsx +++ b/nym-wallet/src/theme/theme.tsx @@ -32,6 +32,9 @@ const nymPalette: NymPalette = { grey: '#5B6174', }, linkHover: '#AF4D36', + border: { + menu: '#E8E9EB', + }, }; const darkMode: NymPaletteVariant = { @@ -285,6 +288,15 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => { }, }, }, + MuiMenu: { + styleOverrides: { + list: ({ _, theme }) => ({ + backgroundColor: theme.palette.mode === 'dark' ? darkMode.background.main : undefined, + border: `1px solid ${theme.palette.nym.border.menu}`, + borderRadius: '8px', + }), + }, + }, }, palette, }; diff --git a/ts-packages/types/src/types/rust/RewardEstimationResponse.ts b/ts-packages/types/src/types/rust/RewardEstimationResponse.ts new file mode 100644 index 0000000000..eb9e8ba991 --- /dev/null +++ b/ts-packages/types/src/types/rust/RewardEstimationResponse.ts @@ -0,0 +1,8 @@ +export type RewardEstimationResponse = { + estimated_total_node_reward: number; + estimated_operator_reward: number; + estimated_delegators_reward: number; + estimated_node_profit: number; + estimated_operator_cost: number; + as_at: number; +}; diff --git a/ts-packages/types/src/types/rust/index.ts b/ts-packages/types/src/types/rust/index.ts index 40290d9523..59dcf28565 100644 --- a/ts-packages/types/src/types/rust/index.ts +++ b/ts-packages/types/src/types/rust/index.ts @@ -40,6 +40,7 @@ export * from './PendingIntervalEventData'; export * from './PendingUndelegate'; export * from './Period'; export * from './PledgeData'; +export * from './RewardEstimationResponse'; export * from './RewardedSetNodeStatus'; export * from './RewardingParams'; export * from './RpcTransactionResponse';