feat(wallet): update bond amount (#3338)
This commit is contained in:
@@ -94,6 +94,12 @@ pub enum BackendError {
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
WalletFileNotFound,
|
||||
#[error("Invalid update pledge request, the new bond amount is the same as the current one")]
|
||||
WalletPledgeUpdateNoOp,
|
||||
#[error(
|
||||
"Invalid update pledge request, the new bond is a different currency from the current one"
|
||||
)]
|
||||
WalletPledgeUpdateInvalidCurrency,
|
||||
#[error("The wallet file has a malformed name")]
|
||||
WalletFileMalformedFilename,
|
||||
#[error("Unable to archive wallet file")]
|
||||
|
||||
@@ -58,6 +58,7 @@ fn main() {
|
||||
mixnet::admin::update_contract_settings,
|
||||
mixnet::bond::bond_gateway,
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::update_pledge,
|
||||
mixnet::bond::pledge_more,
|
||||
mixnet::bond::decrease_pledge,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
@@ -114,6 +115,7 @@ fn main() {
|
||||
vesting::rewards::vesting_claim_operator_reward,
|
||||
vesting::bond::vesting_bond_gateway,
|
||||
vesting::bond::vesting_bond_mixnode,
|
||||
vesting::bond::vesting_update_pledge,
|
||||
vesting::bond::vesting_pledge_more,
|
||||
vesting::bond::vesting_decrease_pledge,
|
||||
vesting::bond::vesting_unbond_gateway,
|
||||
@@ -152,6 +154,7 @@ fn main() {
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_update_pledge,
|
||||
simulate::mixnet::simulate_pledge_more,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode_config,
|
||||
@@ -164,6 +167,7 @@ fn main() {
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_pledge,
|
||||
simulate::vesting::simulate_vesting_pledge_more,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode_config,
|
||||
|
||||
@@ -17,6 +17,7 @@ use nym_types::transaction::TransactionExecuteResult;
|
||||
use nym_validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
use nym_validator_client::nyxd::Fee;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
@@ -133,6 +134,55 @@ pub async fn bond_mixnode(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn update_pledge(
|
||||
current_pledge: DecCoin,
|
||||
new_pledge: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let dec_delta = guard.calculate_coin_delta(¤t_pledge, &new_pledge)?;
|
||||
let delta = guard.attempt_convert_to_base_coin(dec_delta.clone())?;
|
||||
log::info!(
|
||||
">>> Pledge update, current pledge {}, new pledge {}",
|
||||
¤t_pledge,
|
||||
&new_pledge,
|
||||
);
|
||||
|
||||
let res = match new_pledge.amount.cmp(¤t_pledge.amount) {
|
||||
Ordering::Greater => {
|
||||
log::info!(
|
||||
"Pledge increase, calculated additional pledge {}, fee = {:?}",
|
||||
&dec_delta,
|
||||
fee,
|
||||
);
|
||||
guard.current_client()?.nyxd.pledge_more(delta, fee).await?
|
||||
}
|
||||
Ordering::Less => {
|
||||
log::info!(
|
||||
"Pledge reduction, calculated reduction pledge {}, fee = {:?}",
|
||||
&dec_delta,
|
||||
fee,
|
||||
);
|
||||
guard
|
||||
.current_client()?
|
||||
.nyxd
|
||||
.decrease_pledge(delta, fee)
|
||||
.await?
|
||||
}
|
||||
Ordering::Equal => return Err(BackendError::WalletPledgeUpdateNoOp),
|
||||
};
|
||||
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pledge_more(
|
||||
fee: Option<Fee>,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::WalletState;
|
||||
@@ -93,6 +95,48 @@ pub async fn simulate_pledge_more(
|
||||
simulate_mixnet_operation(ExecuteMsg::PledgeMore {}, Some(additional_pledge), &state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_update_pledge(
|
||||
current_pledge: DecCoin,
|
||||
new_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let dec_delta = guard.calculate_coin_delta(¤t_pledge, &new_pledge)?;
|
||||
log::info!(
|
||||
">>> Simulate pledge update, current pledge {}, new pledge {}",
|
||||
¤t_pledge,
|
||||
&new_pledge,
|
||||
);
|
||||
|
||||
match new_pledge.amount.cmp(¤t_pledge.amount) {
|
||||
Ordering::Greater => {
|
||||
log::info!(
|
||||
"Simulate pledge increase, calculated additional pledge {}",
|
||||
dec_delta,
|
||||
);
|
||||
simulate_mixnet_operation(ExecuteMsg::PledgeMore {}, Some(dec_delta), &state).await
|
||||
}
|
||||
Ordering::Less => {
|
||||
log::info!(
|
||||
"Simulate pledge reduction, calculated reduction pledge {}",
|
||||
dec_delta,
|
||||
);
|
||||
simulate_mixnet_operation(
|
||||
ExecuteMsg::DecreasePledge {
|
||||
decrease_by: guard
|
||||
.attempt_convert_to_base_coin(dec_delta.clone())?
|
||||
.into(),
|
||||
},
|
||||
Some(dec_delta),
|
||||
&state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ordering::Equal => Err(BackendError::WalletPledgeUpdateNoOp),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_mixnode(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::WalletState;
|
||||
@@ -92,6 +94,59 @@ pub async fn simulate_vesting_bond_mixnode(
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_update_pledge(
|
||||
current_pledge: DecCoin,
|
||||
new_pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
match new_pledge.amount.cmp(¤t_pledge.amount) {
|
||||
Ordering::Greater => {
|
||||
let additional_pledge = guard
|
||||
.attempt_convert_to_base_coin(DecCoin {
|
||||
amount: new_pledge.amount - current_pledge.amount,
|
||||
denom: current_pledge.denom,
|
||||
})?
|
||||
.into();
|
||||
log::info!(
|
||||
">>> Simulate pledge more, calculated additional pledge {}",
|
||||
additional_pledge,
|
||||
);
|
||||
simulate_vesting_operation(
|
||||
ExecuteMsg::PledgeMore {
|
||||
amount: additional_pledge,
|
||||
},
|
||||
None,
|
||||
&state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ordering::Less => {
|
||||
let decrease_pledge = guard
|
||||
.attempt_convert_to_base_coin(DecCoin {
|
||||
amount: current_pledge.amount - new_pledge.amount,
|
||||
denom: current_pledge.denom,
|
||||
})?
|
||||
.into();
|
||||
log::info!(
|
||||
">>> Simulate decrease pledge, calculated decrease pledge {}",
|
||||
decrease_pledge,
|
||||
);
|
||||
simulate_vesting_operation(
|
||||
ExecuteMsg::DecreasePledge {
|
||||
amount: decrease_pledge,
|
||||
},
|
||||
None,
|
||||
&state,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ordering::Equal => Err(BackendError::WalletPledgeUpdateNoOp),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_pledge_more(
|
||||
additional_pledge: DecCoin,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nyxd_client;
|
||||
use crate::state::WalletState;
|
||||
@@ -124,6 +126,59 @@ pub async fn vesting_bond_mixnode(
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_update_pledge(
|
||||
current_pledge: DecCoin,
|
||||
new_pledge: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let dec_delta = guard.calculate_coin_delta(¤t_pledge, &new_pledge)?;
|
||||
let delta = guard.attempt_convert_to_base_coin(dec_delta.clone())?;
|
||||
log::info!(
|
||||
">>> Pledge update, current pledge {}, new pledge {}",
|
||||
¤t_pledge,
|
||||
&new_pledge,
|
||||
);
|
||||
|
||||
let res = match new_pledge.amount.cmp(¤t_pledge.amount) {
|
||||
Ordering::Greater => {
|
||||
log::info!(
|
||||
"Pledge increase with locked tokens, calculated additional pledge {}, fee = {:?}",
|
||||
dec_delta,
|
||||
fee,
|
||||
);
|
||||
guard
|
||||
.current_client()?
|
||||
.nyxd
|
||||
.vesting_pledge_more(delta, fee)
|
||||
.await?
|
||||
}
|
||||
Ordering::Less => {
|
||||
log::info!(
|
||||
"Pledge reduction with locked tokens, calculated reduction pledge {}, fee = {:?}",
|
||||
dec_delta,
|
||||
fee,
|
||||
);
|
||||
guard
|
||||
.current_client()?
|
||||
.nyxd
|
||||
.vesting_decrease_pledge(delta, fee)
|
||||
.await?
|
||||
}
|
||||
Ordering::Equal => return Err(BackendError::WalletPledgeUpdateNoOp),
|
||||
};
|
||||
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_pledge_more(
|
||||
fee: Option<Fee>,
|
||||
|
||||
@@ -460,6 +460,34 @@ impl WalletStateInner {
|
||||
pub fn remove_validator_url(&mut self, url: config::ValidatorConfigEntry, network: Network) {
|
||||
self.config.remove_validator_url(url, network)
|
||||
}
|
||||
|
||||
pub fn calculate_coin_delta(
|
||||
&self,
|
||||
coin1: &DecCoin,
|
||||
coin2: &DecCoin,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
if coin1.denom != coin2.denom {
|
||||
return Err(BackendError::WalletPledgeUpdateInvalidCurrency);
|
||||
}
|
||||
|
||||
match coin1.amount.cmp(&coin2.amount) {
|
||||
std::cmp::Ordering::Greater => {
|
||||
let delta = DecCoin {
|
||||
amount: coin1.amount - coin2.amount,
|
||||
denom: coin1.denom.clone(),
|
||||
};
|
||||
Ok(delta)
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
let delta = DecCoin {
|
||||
amount: coin2.amount - coin1.amount,
|
||||
denom: coin1.denom.clone(),
|
||||
};
|
||||
Ok(delta)
|
||||
}
|
||||
std::cmp::Ordering::Equal => Ok(coin1.to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_status_for_urls(
|
||||
|
||||
@@ -3,16 +3,16 @@ import { Typography } from '@mui/material';
|
||||
import { ActionsMenu, ActionsMenuItem } from 'src/components/ActionsMenu';
|
||||
import { Unbond as UnbondIcon, Bond as BondIcon } from '../../svg-icons';
|
||||
|
||||
export type TBondedMixnodeActions = 'nodeSettings' | 'bondMore' | 'unbond' | 'redeem';
|
||||
export type TBondedMixnodeActions = 'nodeSettings' | 'updateBond' | 'unbond' | 'redeem';
|
||||
|
||||
export const BondedMixnodeActions = ({
|
||||
onActionSelect,
|
||||
disabledRedeemAndCompound,
|
||||
disabledBondMore,
|
||||
disabledUpdateBond,
|
||||
}: {
|
||||
onActionSelect: (action: TBondedMixnodeActions) => void;
|
||||
disabledRedeemAndCompound: boolean;
|
||||
disabledBondMore?: boolean;
|
||||
disabledUpdateBond?: boolean;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -26,11 +26,11 @@ export const BondedMixnodeActions = ({
|
||||
|
||||
return (
|
||||
<ActionsMenu open={isOpen} onOpen={handleOpen} onClose={handleClose}>
|
||||
{!disabledBondMore && (
|
||||
{!disabledUpdateBond && (
|
||||
<ActionsMenuItem
|
||||
title="Bond More"
|
||||
title="Change bond amount"
|
||||
Icon={<BondIcon fontSize="inherit" />}
|
||||
onClick={() => handleActionClick('bondMore')}
|
||||
onClick={() => handleActionClick('updateBond')}
|
||||
/>
|
||||
)}
|
||||
<ActionsMenuItem
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Box, Stack } from '@mui/material';
|
||||
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 { TPoolOption } from 'src/components/TokenPoolSelector';
|
||||
import { ConfirmTx } from 'src/components/ConfirmTX';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { validateAmount } from 'src/utils';
|
||||
import { simulateBondMore, simulateVestingBondMore } from 'src/requests';
|
||||
import { TBondMoreArgs } from 'src/types';
|
||||
import { TBondedMixnode } from 'src/context';
|
||||
|
||||
export const BondMoreModal = ({
|
||||
node,
|
||||
userBalance,
|
||||
onBondMore,
|
||||
onClose,
|
||||
onError,
|
||||
}: {
|
||||
node: TBondedMixnode;
|
||||
userBalance?: string;
|
||||
onBondMore: (data: TBondMoreArgs, tokenPool: TPoolOption) => Promise<void>;
|
||||
onClose: () => void;
|
||||
onError: (e: string) => void;
|
||||
}) => {
|
||||
const { bond: currentBond, proxy } = node;
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
const [additionalBond, setAdditionalBond] = useState<DecCoin>({ amount: '0', denom: currentBond.denom });
|
||||
const [errorAmount, setErrorAmount] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (feeError) {
|
||||
onError(feeError);
|
||||
}
|
||||
}, [feeError]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
const data = { additionalPledge: additionalBond };
|
||||
const tokenPool = proxy ? 'locked' : 'balance';
|
||||
await onBondMore(data, tokenPool);
|
||||
};
|
||||
|
||||
const handleAmountChanged = async (value: DecCoin) => {
|
||||
setAdditionalBond(value);
|
||||
const { amount } = value;
|
||||
|
||||
if (!amount) {
|
||||
setErrorAmount(true);
|
||||
} else {
|
||||
const validAmount = await validateAmount(amount, '1');
|
||||
if (!validAmount) {
|
||||
setErrorAmount(true);
|
||||
return;
|
||||
}
|
||||
setErrorAmount(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnOk = async () => {
|
||||
if (!proxy) {
|
||||
await getFee<TBondMoreArgs>(simulateBondMore, { additionalPledge: additionalBond });
|
||||
} else {
|
||||
await getFee<TBondMoreArgs>(simulateVestingBondMore, { additionalPledge: additionalBond });
|
||||
}
|
||||
};
|
||||
|
||||
if (fee)
|
||||
return (
|
||||
<ConfirmTx
|
||||
open
|
||||
header="Bond more details"
|
||||
fee={fee}
|
||||
onClose={onClose}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleConfirm}
|
||||
>
|
||||
<ModalListItem label="Current bond" value={`${currentBond.amount} ${currentBond.denom}`} divider />
|
||||
<ModalListItem label="Additional bond" value={`${additionalBond?.amount} ${additionalBond?.denom}`} divider />
|
||||
</ConfirmTx>
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open
|
||||
header="Bond more"
|
||||
subHeader="Bond more tokens on your node and receive more rewards"
|
||||
okLabel="Next"
|
||||
onOk={handleOnOk}
|
||||
okDisabled={errorAmount}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Stack gap={3}>
|
||||
<Box display="flex" gap={1}>
|
||||
<CurrencyFormField
|
||||
autoFocus
|
||||
label="Bond amount"
|
||||
denom={currentBond.denom}
|
||||
onChanged={(value) => {
|
||||
handleAmountChanged(value);
|
||||
}}
|
||||
fullWidth
|
||||
validationError={errorAmount ? 'Please enter a valid amount' : undefined}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<ModalListItem label="Account balance" value={userBalance?.toUpperCase() || '-'} divider />
|
||||
<ModalListItem label="Current bond" value={`${currentBond.amount} ${currentBond.denom}`} divider />
|
||||
<ModalListItem label="Est. fee for this operation will be calculated in the next page" value="" divider />
|
||||
</Box>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -12,8 +12,8 @@ export const BondOversaturatedModal: FCWithChildren<{
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOk={async () => onContinue?.()}
|
||||
header="Bond More"
|
||||
okLabel="Bond More"
|
||||
header="Change bond amount"
|
||||
okLabel="Change bond"
|
||||
buttonFullWidth
|
||||
>
|
||||
<Stack spacing={3} marginBottom={3}>
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
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, simulateVestingUpdateBond } from 'src/requests';
|
||||
import { TSimulateUpdateBondArgs, TUpdateBondArgs } from 'src/types';
|
||||
import { AppContext, TBondedMixnode } from 'src/context';
|
||||
import { TPoolOption } from '../../TokenPoolSelector';
|
||||
|
||||
export const UpdateBondAmountModal = ({
|
||||
node,
|
||||
onUpdateBond,
|
||||
onClose,
|
||||
onError,
|
||||
}: {
|
||||
node: TBondedMixnode;
|
||||
onUpdateBond: (data: TUpdateBondArgs, tokenPool: TPoolOption) => Promise<void>;
|
||||
onClose: () => void;
|
||||
onError: (e: string) => void;
|
||||
}) => {
|
||||
const { bond: currentBond, proxy, stakeSaturation, uncappedStakeSaturation } = node;
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
const [newBond, setNewBond] = useState<DecCoin | undefined>();
|
||||
const [errorAmount, setErrorAmount] = useState(false);
|
||||
|
||||
const { printBalance, printVestedBalance } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (feeError) {
|
||||
onError(feeError);
|
||||
}
|
||||
}, [feeError]);
|
||||
|
||||
const handleConfirm = async () => {
|
||||
if (!newBond) {
|
||||
return;
|
||||
}
|
||||
const tokenPool = proxy ? 'locked' : 'balance';
|
||||
await onUpdateBond(
|
||||
{
|
||||
currentPledge: currentBond,
|
||||
newPledge: newBond,
|
||||
fee: fee?.fee,
|
||||
},
|
||||
tokenPool,
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
if (!proxy) {
|
||||
await getFee<TSimulateUpdateBondArgs>(simulateUpdateBond, {
|
||||
currentPledge: currentBond,
|
||||
newPledge: newBond,
|
||||
});
|
||||
} else {
|
||||
await getFee<TSimulateUpdateBondArgs>(simulateVestingUpdateBond, {
|
||||
currentPledge: currentBond,
|
||||
newPledge: newBond,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const newBondToDisplay = () => {
|
||||
const coin = decCoinToDisplay(newBond as DecCoin);
|
||||
return `${coin.amount} ${coin.denom}`;
|
||||
};
|
||||
|
||||
if (fee)
|
||||
return (
|
||||
<ConfirmTx
|
||||
open
|
||||
header="Change bond details"
|
||||
fee={fee}
|
||||
onClose={onClose}
|
||||
onPrev={resetFeeState}
|
||||
onConfirm={handleConfirm}
|
||||
>
|
||||
<ModalListItem label="New bond details" value={newBondToDisplay()} divider />
|
||||
<ModalListItem label="Change bond details" value={`${currentBond.amount} ${currentBond.denom}`} divider />
|
||||
</ConfirmTx>
|
||||
);
|
||||
|
||||
return (
|
||||
<SimpleModal
|
||||
open
|
||||
header="Change bond amount"
|
||||
subHeader="Add or reduce amount of tokens on your node"
|
||||
okLabel="Next"
|
||||
onOk={handleOnOk}
|
||||
okDisabled={errorAmount || !newBond}
|
||||
onClose={onClose}
|
||||
>
|
||||
<Stack gap={3}>
|
||||
<Box display="flex" gap={1}>
|
||||
<CurrencyFormField
|
||||
autoFocus
|
||||
label="New bond amount"
|
||||
denom={currentBond.denom}
|
||||
onChanged={(value) => {
|
||||
handleAmountChanged(value);
|
||||
}}
|
||||
fullWidth
|
||||
validationError={errorAmount ? 'Please enter a valid amount' : undefined}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<ModalListItem
|
||||
fontWeight={600}
|
||||
label={proxy ? 'Locked account balance' : 'Account balance'}
|
||||
value={proxy ? printVestedBalance || '-' : printBalance}
|
||||
divider
|
||||
/>
|
||||
<ModalListItem label="Current bond amount" value={`${currentBond.amount} ${currentBond.denom}`} divider />
|
||||
{uncappedStakeSaturation ? (
|
||||
<ModalListItem
|
||||
label="Node saturation"
|
||||
value={`${uncappedStakeSaturation}%`}
|
||||
sxValue={{ color: 'error.main' }}
|
||||
divider
|
||||
/>
|
||||
) : (
|
||||
<ModalListItem label="Node saturation" value={`${stakeSaturation}%`} divider />
|
||||
)}
|
||||
<ModalListItem label="Est. fee for this operation will be calculated in the next page" value="" divider />
|
||||
</Box>
|
||||
</Stack>
|
||||
</SimpleModal>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
TransactionExecuteResult,
|
||||
decimalToPercentage,
|
||||
SelectionChance,
|
||||
decimalToFloatApproximation,
|
||||
} from '@nymproject/types';
|
||||
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import Big from 'big.js';
|
||||
@@ -17,7 +18,7 @@ import {
|
||||
TBondGatewaySignatureArgs,
|
||||
TBondMixNodeArgs,
|
||||
TBondMixnodeSignatureArgs,
|
||||
TBondMoreArgs,
|
||||
TUpdateBondArgs,
|
||||
} from 'src/types';
|
||||
import { Console } from 'src/utils/console';
|
||||
import {
|
||||
@@ -28,8 +29,6 @@ import {
|
||||
getMixnodeBondDetails,
|
||||
unbondGateway as unbondGatewayRequest,
|
||||
unbondMixNode as unbondMixnodeRequest,
|
||||
bondMore as bondMoreRequest,
|
||||
vestingBondMore,
|
||||
vestingBondGateway,
|
||||
vestingBondMixNode,
|
||||
vestingUnbondGateway,
|
||||
@@ -50,6 +49,8 @@ import {
|
||||
generateMixnodeMsgPayload as generateMixnodeMsgPayloadReq,
|
||||
vestingGenerateGatewayMsgPayload as vestingGenerateGatewayMsgPayloadReq,
|
||||
generateGatewayMsgPayload as generateGatewayMsgPayloadReq,
|
||||
updateBond as updateBondReq,
|
||||
vestingUpdateBond as vestingUpdateBondReq,
|
||||
} from '../requests';
|
||||
import { useCheckOwnership } from '../hooks/useCheckOwnership';
|
||||
import { AppContext } from './main';
|
||||
@@ -69,6 +70,7 @@ export type TBondedMixnode = {
|
||||
stake: DecCoin;
|
||||
bond: DecCoin;
|
||||
stakeSaturation: string;
|
||||
uncappedStakeSaturation?: number;
|
||||
profitMargin: string;
|
||||
operatorRewards?: DecCoin;
|
||||
delegators: number;
|
||||
@@ -117,7 +119,7 @@ export type TBondingContext = {
|
||||
bondMixnode: (data: TBondMixNodeArgs, tokenPool: TokenPool) => Promise<TransactionExecuteResult | undefined>;
|
||||
bondGateway: (data: TBondGatewayArgs, tokenPool: TokenPool) => Promise<TransactionExecuteResult | undefined>;
|
||||
unbond: (fee?: FeeDetails) => Promise<TransactionExecuteResult | undefined>;
|
||||
bondMore: (data: TBondMoreArgs, tokenPool: TokenPool) => Promise<TransactionExecuteResult | undefined>;
|
||||
updateBondAmount: (data: TUpdateBondArgs, tokenPool: TokenPool) => Promise<TransactionExecuteResult | undefined>;
|
||||
redeemRewards: (fee?: FeeDetails) => Promise<TransactionExecuteResult | undefined>;
|
||||
updateMixnode: (pm: string, fee?: FeeDetails) => Promise<TransactionExecuteResult | undefined>;
|
||||
checkOwnership: () => Promise<void>;
|
||||
@@ -138,7 +140,7 @@ export const BondingContext = createContext<TBondingContext>({
|
||||
unbond: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
bondMore: async () => {
|
||||
updateBondAmount: async () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
redeemRewards: async () => {
|
||||
@@ -189,6 +191,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
stakeSaturation: string;
|
||||
estimatedRewards?: DecCoin;
|
||||
uptime: number;
|
||||
uncappedSaturation?: number;
|
||||
} = {
|
||||
status: 'not_found',
|
||||
stakeSaturation: '0',
|
||||
@@ -207,6 +210,11 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
try {
|
||||
const stakeSaturationResponse = await getMixnodeStakeSaturation(mixId);
|
||||
additionalDetails.stakeSaturation = decimalToPercentage(stakeSaturationResponse.saturation);
|
||||
|
||||
const rawUncappedSaturation = decimalToFloatApproximation(stakeSaturationResponse.uncapped_saturation);
|
||||
if (rawUncappedSaturation && rawUncappedSaturation > 1) {
|
||||
additionalDetails.uncappedSaturation = Math.round(rawUncappedSaturation * 100);
|
||||
}
|
||||
} catch (e) {
|
||||
Console.log('getMixnodeStakeSaturation fails', e);
|
||||
}
|
||||
@@ -277,6 +285,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
|
||||
if (ownership.hasOwnership && ownership.nodeType === EnumNodeType.mixnode && clientDetails) {
|
||||
try {
|
||||
@@ -298,7 +307,13 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
bond_information: { mix_id },
|
||||
} = data;
|
||||
|
||||
const { status, stakeSaturation, estimatedRewards, uptime } = await getAdditionalMixnodeDetails(mix_id);
|
||||
const {
|
||||
status,
|
||||
stakeSaturation,
|
||||
uncappedSaturation: uncappedStakeSaturation,
|
||||
estimatedRewards,
|
||||
uptime,
|
||||
} = await getAdditionalMixnodeDetails(mix_id);
|
||||
const setProbabilities = await getSetProbabilities(mix_id);
|
||||
const nodeDescription = await getNodeDescription(
|
||||
bond_information.mix_node.host,
|
||||
@@ -322,6 +337,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
uptime,
|
||||
status,
|
||||
stakeSaturation,
|
||||
uncappedStakeSaturation,
|
||||
operatorCost: decCoinToDisplay(rewarding_details.cost_params.interval_operating_cost),
|
||||
host: bond_information.mix_node.host.replace(/\s/g, ''),
|
||||
routingScore,
|
||||
@@ -477,16 +493,16 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
return tx;
|
||||
};
|
||||
|
||||
const bondMore = async (data: TBondMoreArgs, tokenPool: TokenPool) => {
|
||||
const updateBondAmount = async (data: TUpdateBondArgs, tokenPool: TokenPool) => {
|
||||
let tx: TransactionExecuteResult | undefined;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (tokenPool === 'balance') {
|
||||
tx = await bondMoreRequest(data);
|
||||
tx = await updateBondReq(data);
|
||||
await userBalance.fetchBalance();
|
||||
}
|
||||
if (tokenPool === 'locked') {
|
||||
tx = await vestingBondMore(data);
|
||||
tx = await vestingUpdateBondReq(data);
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
|
||||
@@ -547,7 +563,7 @@ export const BondingContextProvider: FCWithChildren = ({ children }): JSX.Elemen
|
||||
updateMixnode,
|
||||
refresh,
|
||||
redeemRewards,
|
||||
bondMore,
|
||||
updateBondAmount,
|
||||
checkOwnership,
|
||||
generateMixnodeMsgPayload,
|
||||
generateGatewayMsgPayload,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '../requests';
|
||||
import { Console } from '../utils/console';
|
||||
import { createSignInWindow, getReactState, setReactState } from '../requests/app';
|
||||
import { toDisplay } from '../utils';
|
||||
|
||||
export const urls = (networkName?: Network) =>
|
||||
networkName === 'MAINNET'
|
||||
@@ -64,6 +65,8 @@ export type TAppContext = {
|
||||
signInWithPassword: (password: string) => void;
|
||||
logOut: () => void;
|
||||
keepState: () => Promise<void>;
|
||||
printBalance: string;
|
||||
printVestedBalance?: string; // spendable vested token
|
||||
};
|
||||
|
||||
interface RustState {
|
||||
@@ -89,6 +92,8 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
const [isAdminAddress, setIsAdminAddress] = useState<boolean>(false);
|
||||
const [showSendModal, setShowSendModal] = useState(false);
|
||||
const [showReceiveModal, setShowReceiveModal] = useState(false);
|
||||
const [printBalance, setPrintBalance] = useState<string>('-');
|
||||
const [printVestedBalance, setPrintVestedBalance] = useState<string | undefined>();
|
||||
|
||||
const userBalance = useGetBalance(clientDetails);
|
||||
const navigate = useNavigate();
|
||||
@@ -192,6 +197,18 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
}
|
||||
}, [network]);
|
||||
|
||||
useEffect(() => {
|
||||
const currency = clientDetails?.display_mix_denom.toUpperCase() || 'NYM';
|
||||
if (userBalance.originalVesting) {
|
||||
setPrintVestedBalance(`${toDisplay(userBalance.tokenAllocation?.spendableVestedCoins || 0)} ${currency}`);
|
||||
}
|
||||
if (userBalance?.balance?.amount) {
|
||||
setPrintBalance(`${toDisplay(userBalance.balance.amount.amount)} ${currency}`);
|
||||
} else {
|
||||
setPrintBalance(`${toDisplay(0)} ${currency}`);
|
||||
}
|
||||
}, [userBalance, clientDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
let newValue = false;
|
||||
if (network && appEnv?.ADMIN_ADDRESS && clientDetails?.client_address) {
|
||||
@@ -310,6 +327,8 @@ export const AppProvider: FCWithChildren = ({ children }) => {
|
||||
handleShowSendModal,
|
||||
handleShowReceiveModal,
|
||||
handleSwitchMode,
|
||||
printBalance,
|
||||
printVestedBalance,
|
||||
}),
|
||||
[
|
||||
appVersion,
|
||||
|
||||
@@ -154,7 +154,7 @@ export const MockBondingContextProvider = ({
|
||||
return TxResultMock;
|
||||
};
|
||||
|
||||
const bondMore = async (): Promise<TransactionExecuteResult> => {
|
||||
const updateBondAmount = async (): Promise<TransactionExecuteResult> => {
|
||||
setIsLoading(true);
|
||||
await mockSleep(SLEEP_MS);
|
||||
triggerStateUpdate();
|
||||
@@ -202,7 +202,7 @@ export const MockBondingContextProvider = ({
|
||||
getFee,
|
||||
resetFeeState,
|
||||
updateMixnode,
|
||||
bondMore,
|
||||
updateBondAmount,
|
||||
checkOwnership,
|
||||
generateMixnodeMsgPayload,
|
||||
generateGatewayMsgPayload,
|
||||
|
||||
@@ -57,6 +57,7 @@ export const MockMainContextProvider: FCWithChildren = ({ children }) => {
|
||||
handleShowSendModal: () => undefined,
|
||||
handleShowReceiveModal: () => undefined,
|
||||
keepState: async () => undefined,
|
||||
printBalance: '100.0000 NYMT',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { FeeDetails, decimalToFloatApproximation } from '@nymproject/types';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { Box } from '@mui/material';
|
||||
import { TPoolOption } from 'src/components';
|
||||
import { Bond } from 'src/components/Bonding/Bond';
|
||||
@@ -8,122 +8,122 @@ 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 { BondMoreModal } from 'src/components/Bonding/modals/BondMoreModal';
|
||||
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, TBondMoreArgs } from 'src/types';
|
||||
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs, TUpdateBondArgs } from 'src/types';
|
||||
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
|
||||
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { BondingContextProvider, useBondingContext } from '../../context';
|
||||
import { getMixnodeStakeSaturation } from '../../requests';
|
||||
|
||||
const Bonding = () => {
|
||||
const [showModal, setShowModal] = useState<
|
||||
'bond-mixnode' | 'bond-gateway' | 'bond-more' | 'bond-more-oversaturated' | 'unbond' | 'redeem'
|
||||
'bond-mixnode' | 'bond-gateway' | 'update-bond' | 'update-bond-oversaturated' | 'unbond' | 'redeem'
|
||||
>();
|
||||
const [confirmationDetails, setConfirmationDetails] = useState<ConfirmationDetailProps>();
|
||||
const [saturationPercentage, setSaturationPercentage] = useState<string | undefined>();
|
||||
const [uncappedSaturation, setUncappedSaturation] = useState<number | undefined>();
|
||||
|
||||
const {
|
||||
network,
|
||||
clientDetails,
|
||||
userBalance: { originalVesting, balance },
|
||||
userBalance: { originalVesting },
|
||||
} = useContext(AppContext);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { bondedNode, bondMixnode, bondGateway, redeemRewards, isLoading, checkOwnership, bondMore } =
|
||||
useBondingContext();
|
||||
const {
|
||||
bondedNode,
|
||||
bondMixnode,
|
||||
bondGateway,
|
||||
redeemRewards,
|
||||
isLoading,
|
||||
checkOwnership,
|
||||
updateBondAmount,
|
||||
error,
|
||||
refresh,
|
||||
} = useBondingContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (bondedNode && isMixnode(bondedNode) && bondedNode.uncappedStakeSaturation) {
|
||||
setUncappedSaturation(bondedNode.uncappedStakeSaturation);
|
||||
}
|
||||
}, [bondedNode]);
|
||||
|
||||
const handleCloseModal = async () => {
|
||||
setShowModal(undefined);
|
||||
await checkOwnership();
|
||||
};
|
||||
|
||||
const handleError = (error: string) => {
|
||||
const handleError = (err: string) => {
|
||||
setShowModal(undefined);
|
||||
setConfirmationDetails({
|
||||
status: 'error',
|
||||
title: 'An error occurred',
|
||||
subtitle: error,
|
||||
subtitle: err,
|
||||
});
|
||||
};
|
||||
|
||||
const handleBondMixnode = async (data: TBondMixNodeArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondMixnode(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
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);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
if (tx) {
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBondMore = async (data: TBondMoreArgs, tokenPool: TPoolOption) => {
|
||||
const handleUpdateBond = async (data: TUpdateBondArgs, tokenPool: TPoolOption) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await bondMore(data, tokenPool);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond More successful',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
|
||||
const tx = await updateBondAmount(data, tokenPool);
|
||||
if (tx) {
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Bond amount changed successfully',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRedeemReward = async (fee?: FeeDetails) => {
|
||||
setShowModal(undefined);
|
||||
const tx = await redeemRewards(fee);
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Rewards redeemed successfully',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
};
|
||||
|
||||
const handleCheckStakeSaturation = async (newMixId: number) => {
|
||||
try {
|
||||
const newSaturation = decimalToFloatApproximation(
|
||||
(await getMixnodeStakeSaturation(newMixId)).uncapped_saturation,
|
||||
);
|
||||
if (newSaturation && newSaturation > 1) {
|
||||
const newSaturationPercentage = Math.round(newSaturation * 100);
|
||||
return { isOverSaturated: true, saturationPercentage: newSaturationPercentage };
|
||||
}
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
} catch (e) {
|
||||
Console.error('Error fetching the saturation, error:', e);
|
||||
return { isOverSaturated: false, saturationPercentage: undefined };
|
||||
if (tx) {
|
||||
setConfirmationDetails({
|
||||
status: 'success',
|
||||
title: 'Rewards redeemed successfully',
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${tx?.transaction_hash}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleBondedMixnodeAction = async (action: TBondedMixnodeActions) => {
|
||||
switch (action) {
|
||||
case 'bondMore': {
|
||||
if (bondedNode && isMixnode(bondedNode)) {
|
||||
const { isOverSaturated, saturationPercentage: newSaturationPercentage } = await handleCheckStakeSaturation(
|
||||
bondedNode.mixId,
|
||||
);
|
||||
if (isOverSaturated && newSaturationPercentage) {
|
||||
setShowModal('bond-more-oversaturated');
|
||||
setSaturationPercentage(newSaturationPercentage.toString());
|
||||
break;
|
||||
}
|
||||
case 'updateBond': {
|
||||
if (uncappedSaturation) {
|
||||
setShowModal('update-bond-oversaturated');
|
||||
} else {
|
||||
setShowModal('update-bond');
|
||||
}
|
||||
setShowModal('bond-more');
|
||||
break;
|
||||
}
|
||||
case 'unbond': {
|
||||
@@ -141,6 +141,10 @@ const Bonding = () => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
if (error) {
|
||||
return <ErrorModal open message="An error occured, please check logs for details" onClose={() => refresh()} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 4 }}>
|
||||
{!bondedNode && <Bond disabled={isLoading} onBond={() => setShowModal('bond-mixnode')} />}
|
||||
@@ -179,20 +183,19 @@ const Bonding = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'bond-more-oversaturated' && saturationPercentage && (
|
||||
{showModal === 'update-bond-oversaturated' && uncappedSaturation && (
|
||||
<BondOversaturatedModal
|
||||
open
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onContinue={() => setShowModal('bond-more')}
|
||||
saturationPercentage={saturationPercentage}
|
||||
onContinue={() => setShowModal('update-bond')}
|
||||
saturationPercentage={uncappedSaturation.toString()}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showModal === 'bond-more' && bondedNode && isMixnode(bondedNode) && (
|
||||
<BondMoreModal
|
||||
{showModal === 'update-bond' && bondedNode && isMixnode(bondedNode) && (
|
||||
<UpdateBondAmountModal
|
||||
node={bondedNode}
|
||||
userBalance={balance?.printable_balance}
|
||||
onBondMore={handleBondMore}
|
||||
onUpdateBond={handleUpdateBond}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onError={handleError}
|
||||
/>
|
||||
|
||||
@@ -13,8 +13,7 @@ import {
|
||||
TBondGatewaySignatureArgs,
|
||||
TBondMixNodeArgs,
|
||||
TBondMixnodeSignatureArgs,
|
||||
TBondMoreArgs,
|
||||
TDecreaseBondArgs,
|
||||
TUpdateBondArgs,
|
||||
} from '../types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
@@ -51,7 +50,5 @@ export const unbond = async (type: EnumNodeType) => {
|
||||
return unbondGateway();
|
||||
};
|
||||
|
||||
export const bondMore = async (args: TBondMoreArgs) => invokeWrapper<TransactionExecuteResult>('pledge_more', args);
|
||||
|
||||
export const decreaseBond = async (args: TDecreaseBondArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('decrease_pledge', args);
|
||||
export const updateBond = async (args: TUpdateBondArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('update_pledge', args);
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
MixNodeConfigUpdate,
|
||||
GatewayConfigUpdate,
|
||||
} from '@nymproject/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
|
||||
import { TBondGatewayArgs, TBondMixNodeArgs, TSimulateUpdateBondArgs } from 'src/types';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
|
||||
export const simulateBondGateway = async (args: TBondGatewayArgs) =>
|
||||
@@ -80,7 +80,8 @@ export const simulateClaimOperatorReward = async () => invokeWrapper<FeeDetails>
|
||||
export const simulateVestingClaimOperatorReward = async () =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_claim_operator_reward');
|
||||
|
||||
export const simulateBondMore = async (args: any) => invokeWrapper<FeeDetails>('simulate_pledge_more', args);
|
||||
export const simulateUpdateBond = async (args: TSimulateUpdateBondArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_update_pledge', args);
|
||||
|
||||
export const simulateVestingBondMore = async (args: any) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_pledge_more', args);
|
||||
export const simulateVestingUpdateBond = async (args: TSimulateUpdateBondArgs) =>
|
||||
invokeWrapper<FeeDetails>('simulate_vesting_update_pledge', args);
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
} from '@nymproject/types';
|
||||
import { Fee } from '@nymproject/types/dist/types/rust/Fee';
|
||||
import { invokeWrapper } from './wrapper';
|
||||
import { TBondGatewaySignatureArgs, TBondMixnodeSignatureArgs } from '../types';
|
||||
import { TBondGatewaySignatureArgs, TBondMixnodeSignatureArgs, TUpdateBondArgs } from '../types';
|
||||
|
||||
export const getLockedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('locked_coins');
|
||||
|
||||
@@ -121,14 +121,5 @@ export const vestingClaimOperatorReward = async (fee?: Fee) =>
|
||||
export const vestingClaimDelegatorRewards = async (mixId: number) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_claim_delegator_reward', { mixId });
|
||||
|
||||
export const vestingBondMore = async ({ fee, additionalPledge }: { fee?: Fee; additionalPledge: DecCoin }) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_pledge_more', {
|
||||
fee,
|
||||
additionalPledge,
|
||||
});
|
||||
|
||||
export const vestingDecreaseBond = async ({ fee, decreaseBy }: { fee?: Fee; decreaseBy: DecCoin }) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_decrease_pledge', {
|
||||
fee,
|
||||
decreaseBy,
|
||||
});
|
||||
export const vestingUpdateBond = async (args: TUpdateBondArgs) =>
|
||||
invokeWrapper<TransactionExecuteResult>('vesting_update_pledge', args);
|
||||
|
||||
@@ -54,15 +54,13 @@ export type TBondGatewaySignatureArgs = {
|
||||
tokenPool: 'balance' | 'locked';
|
||||
};
|
||||
|
||||
export type TBondMoreArgs = {
|
||||
additionalPledge: DecCoin;
|
||||
export type TUpdateBondArgs = {
|
||||
currentPledge: DecCoin;
|
||||
newPledge: DecCoin;
|
||||
fee?: Fee;
|
||||
};
|
||||
|
||||
export type TDecreaseBondArgs = {
|
||||
decreaseBy: DecCoin;
|
||||
fee?: Fee;
|
||||
};
|
||||
export type TSimulateUpdateBondArgs = Omit<TUpdateBondArgs, 'fee'>;
|
||||
|
||||
export type TNodeDescription = {
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user