Feature/changing wallet currency types frontend work (#1455)
* Introduced concept of denom details No longer exposing plain 'DENOM' Denom registration + conversion Generating typescript type for DecCoin 'New' API on 'send' Further WIP work on transforming usages of MajorCurrencyAmount into DecCoin Further replacements of MajorCurrencyAmount into DecCoin Attempt at dec-coinifying get_all_mix_delegations Finished purge of MajorCurrencyAmount Display for Fee More unification for conversion methods Fixed up tests and made clippy happier Minor post-merge fixes Removed explicit Arc and RwLock from all tauri commands Fixed conversion to display coin More type-restrictive exported denom type Regenerated rust => ts types * post-rebase fixes * update frontend * fix lint errors * Adjusted Display implementation of DecCoin to include space between amount and denom * Adding separate base and display denoms for account * Fixed account constructor * Using CurrencyDenom for display_mix_denom * uppercase denom on frontend * Changed AutoFeeGrant constructor Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
This commit is contained in:
@@ -26,7 +26,7 @@ use cosmrs::rpc::{self, HttpClient, Order};
|
||||
use cosmrs::tendermint::abci::Code as AbciCode;
|
||||
use cosmrs::tendermint::abci::Transaction;
|
||||
use cosmrs::tendermint::{abci, block, chain};
|
||||
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Denom, Tx};
|
||||
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Tx};
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
@@ -121,7 +121,7 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
search_denom: Denom,
|
||||
search_denom: String,
|
||||
) -> Result<Option<Coin>, NymdError> {
|
||||
let path = Some("/cosmos.bank.v1beta1.Query/Balance".parse().unwrap());
|
||||
|
||||
|
||||
@@ -574,10 +574,10 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
let fee = match fee {
|
||||
Fee::Manual(fee) => fee,
|
||||
Fee::Auto(multiplier) => auto_fee(multiplier).await?,
|
||||
Fee::PayerGranterAuto(multiplier, payer, granter) => {
|
||||
let mut fee = auto_fee(multiplier).await?;
|
||||
fee.payer = payer;
|
||||
fee.granter = granter;
|
||||
Fee::PayerGranterAuto(auto_feegrant) => {
|
||||
let mut fee = auto_fee(auto_feegrant.gas_adjustment).await?;
|
||||
fee.payer = auto_feegrant.payer;
|
||||
fee.granter = auto_feegrant.granter;
|
||||
fee
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nymd::Coin;
|
||||
use crate::nymd::Gas;
|
||||
use cosmrs::{tx, AccountId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
pub mod gas_price;
|
||||
|
||||
@@ -11,11 +13,74 @@ pub type GasAdjustment = f32;
|
||||
|
||||
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.3;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AutoFeeGrant {
|
||||
pub gas_adjustment: Option<GasAdjustment>,
|
||||
pub payer: Option<AccountId>,
|
||||
pub granter: Option<AccountId>,
|
||||
}
|
||||
|
||||
impl Display for AutoFeeGrant {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(gas_adjustment) = self.gas_adjustment {
|
||||
write!(f, "Feegrant in auto mode with {gas_adjustment} simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
|
||||
} else {
|
||||
write!(f, "Feegrant in auto mode with no custom simulated multiplier with {:?} payer and {:?} granter", self.payer, self.granter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum Fee {
|
||||
Manual(#[serde(with = "sealed::TxFee")] tx::Fee),
|
||||
Auto(Option<GasAdjustment>),
|
||||
PayerGranterAuto(Option<GasAdjustment>, Option<AccountId>, Option<AccountId>),
|
||||
PayerGranterAuto(AutoFeeGrant),
|
||||
}
|
||||
|
||||
impl Display for Fee {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Fee::Manual(fee) => {
|
||||
write!(f, "Fee in manual mode with ")?;
|
||||
for fee in &fee.amount {
|
||||
write!(f, "{}{} paid in fees, ", fee.amount, fee.denom)?;
|
||||
}
|
||||
write!(f, "{} set as gas limit, ", fee.gas_limit)?;
|
||||
if let Some(payer) = &fee.payer {
|
||||
write!(f, "{payer} set as payer, ")?;
|
||||
}
|
||||
if let Some(granter) = &fee.granter {
|
||||
write!(f, "{granter} set as granter")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Fee::Auto(Some(multiplier)) => {
|
||||
write!(f, "Fee in auto mode with {multiplier} simulated multiplier")
|
||||
}
|
||||
Fee::Auto(None) => write!(f, "Fee in auto mode with no custom simulated multiplier"),
|
||||
Fee::PayerGranterAuto(auto_feegrant) => write!(f, "{}", auto_feegrant),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fee {
|
||||
pub fn new_payer_granter_auto(
|
||||
gas_adjustment: Option<GasAdjustment>,
|
||||
payer: Option<AccountId>,
|
||||
granter: Option<AccountId>,
|
||||
) -> Self {
|
||||
Fee::PayerGranterAuto(AutoFeeGrant {
|
||||
gas_adjustment,
|
||||
payer,
|
||||
granter,
|
||||
})
|
||||
}
|
||||
pub fn try_get_manual_amount(&self) -> Option<Vec<Coin>> {
|
||||
match self {
|
||||
Fee::Manual(tx_fee) => Some(tx_fee.amount.iter().cloned().map(Into::into).collect()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<tx::Fee> for Fee {
|
||||
|
||||
@@ -389,7 +389,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn get_balance(
|
||||
&self,
|
||||
address: &AccountId,
|
||||
denom: Denom,
|
||||
denom: String,
|
||||
) -> Result<Option<Coin>, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
|
||||
@@ -3,6 +3,7 @@ name = "mixnet-contract-common"
|
||||
version = "0.1.0"
|
||||
authors = ["Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.62"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ pub struct Delegation {
|
||||
pub node_identity: IdentityKey,
|
||||
pub amount: Coin,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
|
||||
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
|
||||
}
|
||||
|
||||
impl Eq for Delegation {}
|
||||
|
||||
@@ -37,6 +37,16 @@ pub enum DelegationEvent {
|
||||
Undelegate(PendingUndelegate),
|
||||
}
|
||||
|
||||
impl DelegationEvent {
|
||||
pub fn delegation_amount(&self) -> Option<Coin> {
|
||||
match self {
|
||||
DelegationEvent::Delegate(delegation) => Some(delegation.amount.clone()),
|
||||
// I think it would be nice to also expose an amount here to know how much we're undelegating
|
||||
DelegationEvent::Undelegate(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
|
||||
pub struct PendingUndelegate {
|
||||
mix_identity: IdentityKey,
|
||||
|
||||
@@ -28,8 +28,8 @@ pub enum Period {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct PledgeData {
|
||||
amount: Coin,
|
||||
block_time: Timestamp,
|
||||
pub amount: Coin,
|
||||
pub block_time: Timestamp,
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
@@ -48,9 +48,9 @@ impl PledgeData {
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: Coin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
pub amount: Coin,
|
||||
pub number_of_periods: usize,
|
||||
pub period_duration: u64,
|
||||
}
|
||||
|
||||
impl OriginalVestingResponse {
|
||||
|
||||
@@ -267,7 +267,7 @@ impl DenomDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, Hash, Clone, PartialEq, Eq)]
|
||||
pub struct DenomDetailsOwned {
|
||||
pub base: String,
|
||||
pub display: String,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::currency::{CurrencyDenom, MajorCurrencyAmount};
|
||||
use crate::currency::{CurrencyDenom, DecCoin};
|
||||
use config::defaults::DenomDetails;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -9,17 +10,20 @@ use serde::{Deserialize, Serialize};
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Account {
|
||||
pub contract_address: String,
|
||||
pub client_address: String,
|
||||
pub denom: CurrencyDenom,
|
||||
pub base_mix_denom: String,
|
||||
|
||||
// this should get refactored to just use a String, but for now it's fine as it reduces headache
|
||||
// for others
|
||||
pub display_mix_denom: CurrencyDenom,
|
||||
}
|
||||
|
||||
impl Account {
|
||||
pub fn new(contract_address: String, client_address: String, denom: CurrencyDenom) -> Self {
|
||||
pub fn new(client_address: String, mix_denom: DenomDetails) -> Self {
|
||||
Account {
|
||||
contract_address,
|
||||
client_address,
|
||||
denom,
|
||||
base_mix_denom: mix_denom.base.to_owned(),
|
||||
display_mix_denom: mix_denom.display.parse().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +57,15 @@ pub struct AccountEntry {
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Balance {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub printable_balance: String,
|
||||
}
|
||||
|
||||
impl Balance {
|
||||
pub fn new(amount: DecCoin) -> Self {
|
||||
Balance {
|
||||
printable_balance: amount.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+412
-384
@@ -1,24 +1,29 @@
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use config::defaults::all::Network;
|
||||
use config::defaults::{DenomDetails, DenomDetailsOwned};
|
||||
use cosmwasm_std::Fraction;
|
||||
use cosmwasm_std::{Decimal, Uint128};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Add, Mul};
|
||||
use std::str::FromStr;
|
||||
use strum::{Display, EnumString, EnumVariantNames};
|
||||
use validator_client::nymd::{Coin, CosmosCoin};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[cfg(feature = "generate-ts")]
|
||||
use ts_rs::{Dependency, TS};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyDenom.ts")
|
||||
)]
|
||||
#[cfg_attr(feature = "generate-ts", ts(rename_all = "UPPERCASE"))]
|
||||
#[cfg_attr(feature = "generate-ts", ts(rename_all = "lowercase"))]
|
||||
#[derive(
|
||||
Display,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Clone,
|
||||
@@ -29,10 +34,12 @@ use validator_client::nymd::{Coin, CosmosCoin};
|
||||
Eq,
|
||||
JsonSchema,
|
||||
)]
|
||||
#[serde(rename_all = "UPPERCASE")]
|
||||
#[strum(serialize_all = "UPPERCASE")]
|
||||
// TODO: this shouldn't be an enum...
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum CurrencyDenom {
|
||||
#[strum(ascii_case_insensitive)]
|
||||
#[default]
|
||||
Unknown,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
Nym,
|
||||
#[strum(ascii_case_insensitive)]
|
||||
@@ -43,448 +50,469 @@ pub enum CurrencyDenom {
|
||||
Nyxt,
|
||||
}
|
||||
|
||||
impl CurrencyDenom {
|
||||
pub fn parse(value: &str) -> Result<CurrencyDenom, TypesError> {
|
||||
let mut denom = value.to_string();
|
||||
if denom.starts_with('u') {
|
||||
denom = denom[1..].to_string();
|
||||
pub type Denom = String;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RegisteredCoins(HashMap<Denom, CoinMetadata>);
|
||||
|
||||
impl RegisteredCoins {
|
||||
pub fn default_denoms(network: Network) -> Self {
|
||||
let mut network_coins = HashMap::new();
|
||||
network_coins.insert(network.mix_denom().base, network.mix_denom().into());
|
||||
network_coins.insert(network.stake_denom().base, network.stake_denom().into());
|
||||
RegisteredCoins(network_coins)
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, denom: Denom, metadata: CoinMetadata) -> Option<CoinMetadata> {
|
||||
self.0.insert(denom, metadata)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, denom: &Denom) -> Option<CoinMetadata> {
|
||||
self.0.remove(denom)
|
||||
}
|
||||
|
||||
pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, TypesError> {
|
||||
// check if this is already in the base denom
|
||||
if self.0.contains_key(&coin.denom) {
|
||||
// if we're converting a base DecCoin it CANNOT fail, unless somebody is providing
|
||||
// bullshit data on purpose : )
|
||||
return coin.try_into();
|
||||
} else {
|
||||
// TODO: this kinda suggests we may need a better data structure
|
||||
for registered_coin in self.0.values() {
|
||||
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
|
||||
let amount = try_convert_decimal_to_u128(coin.try_scale_up_value(exponent)?)?;
|
||||
return Ok(Coin::new(amount, ®istered_coin.base));
|
||||
}
|
||||
}
|
||||
}
|
||||
match CurrencyDenom::from_str(&denom) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(_e) => Err(TypesError::InvalidDenom(value.to_string())),
|
||||
Err(TypesError::UnknownCoinDenom(coin.denom))
|
||||
}
|
||||
|
||||
pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result<DecCoin, TypesError> {
|
||||
for registered_coin in self.0.values() {
|
||||
if let Some(exponent) = registered_coin.get_exponent(&coin.denom) {
|
||||
// if this fails it means we haven't registered our display denom which honestly should never be the case
|
||||
// unless somebody is rocking their own custom network
|
||||
let display_exponent = registered_coin
|
||||
.get_exponent(®istered_coin.display)
|
||||
.ok_or_else(|| TypesError::UnknownCoinDenom(coin.denom.clone()))?;
|
||||
|
||||
return match exponent.cmp(&display_exponent) {
|
||||
Ordering::Greater => {
|
||||
// we need to scale up, unlikely to ever be needed but included for completion sake,
|
||||
// for example if we decided to created knym with exponent 9 and wanted to convert to nym with exponent 6
|
||||
Ok(DecCoin::new_scaled_up(
|
||||
coin.amount,
|
||||
®istered_coin.display,
|
||||
exponent - display_exponent,
|
||||
)?)
|
||||
}
|
||||
// we're already in the display denom
|
||||
Ordering::Equal => Ok(coin.into()),
|
||||
Ordering::Less => {
|
||||
// we need to scale down, the most common case, for example we're in base unym with exponent 0 and want to convert to nym with exponent 6
|
||||
Ok(DecCoin::new_scaled_down(
|
||||
coin.amount,
|
||||
®istered_coin.display,
|
||||
display_exponent - exponent,
|
||||
)?)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Err(TypesError::UnknownCoinDenom(coin.denom))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<CosmosDenom> for CurrencyDenom {
|
||||
type Error = TypesError;
|
||||
// TODO: should this live here?
|
||||
// attempts to replicate cosmos-sdk's coin metadata
|
||||
// https://docs.cosmos.network/master/architecture/adr-024-coin-metadata.html
|
||||
// this way we could more easily handle multiple coin types simultaneously (like nym/nyx/nymt/nyx + local currencies)
|
||||
#[derive(Debug)]
|
||||
pub struct DenomUnit {
|
||||
pub denom: Denom,
|
||||
pub exponent: u32,
|
||||
// pub aliases: Vec<String>,
|
||||
}
|
||||
|
||||
fn try_from(value: CosmosDenom) -> Result<Self, Self::Error> {
|
||||
CurrencyDenom::parse(value.as_ref())
|
||||
impl DenomUnit {
|
||||
pub fn new(denom: Denom, exponent: u32) -> Self {
|
||||
DenomUnit { denom, exponent }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/CurrencyStringMajorAmount.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MajorAmountString(String); // see https://github.com/Aleph-Alpha/ts-rs/issues/51 for exporting type aliases
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Currency.ts")
|
||||
)]
|
||||
// #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MajorCurrencyAmount {
|
||||
// temporarly going back to original impl to speed up merge
|
||||
pub amount: MajorAmountString,
|
||||
pub denom: CurrencyDenom,
|
||||
// // temporary...
|
||||
// #[cfg_attr(feature = "generate-ts", ts(skip))]
|
||||
// pub coin: Coin,
|
||||
#[derive(Debug)]
|
||||
pub struct CoinMetadata {
|
||||
pub denom_units: Vec<DenomUnit>,
|
||||
pub base: Denom,
|
||||
pub display: Denom,
|
||||
}
|
||||
|
||||
// impl JsonSchema for MajorCurrencyAmount {
|
||||
// fn schema_name() -> String {
|
||||
// todo!()
|
||||
// }
|
||||
//
|
||||
// fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
impl CoinMetadata {
|
||||
pub fn new(denom_units: Vec<DenomUnit>, base: Denom, display: Denom) -> Self {
|
||||
CoinMetadata {
|
||||
denom_units,
|
||||
base,
|
||||
display,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_exponent(&self, denom: &str) -> Option<u32> {
|
||||
self.denom_units
|
||||
.iter()
|
||||
.find(|denom_unit| denom_unit.denom == denom)
|
||||
.map(|denom_unit| denom_unit.exponent)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DenomDetails> for CoinMetadata {
|
||||
fn from(denom_details: DenomDetails) -> Self {
|
||||
CoinMetadata::new(
|
||||
vec![
|
||||
DenomUnit::new(denom_details.base.into(), 0),
|
||||
DenomUnit::new(denom_details.display.into(), denom_details.display_exponent),
|
||||
],
|
||||
denom_details.base.into(),
|
||||
denom_details.display.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DenomDetailsOwned> for CoinMetadata {
|
||||
fn from(denom_details: DenomDetailsOwned) -> Self {
|
||||
CoinMetadata::new(
|
||||
vec![
|
||||
DenomUnit::new(denom_details.base.clone(), 0),
|
||||
DenomUnit::new(
|
||||
denom_details.display.clone(),
|
||||
denom_details.display_exponent,
|
||||
),
|
||||
],
|
||||
denom_details.base,
|
||||
denom_details.display,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// tries to semi-replicate cosmos-sdk's DecCoin for being able to handle tokens with decimal amounts
|
||||
// https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/types/dec_coin.go
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
|
||||
pub struct DecCoin {
|
||||
//
|
||||
pub denom: Denom,
|
||||
// Decimal is already serialized to string and using string in its schema, so lets also go straight to string for ts_rs
|
||||
// todo: is `Decimal` the correct type to use? Do we want to depend on cosmwasm_std here?
|
||||
pub amount: Decimal,
|
||||
}
|
||||
|
||||
impl MajorCurrencyAmount {
|
||||
pub fn new(amount: &str, denom: CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom,
|
||||
// I had to implement it manually to correctly set dependencies
|
||||
#[cfg(feature = "generate-ts")]
|
||||
impl TS for DecCoin {
|
||||
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/DecCoin.ts");
|
||||
|
||||
fn decl() -> String {
|
||||
format!("type {} = {};", Self::name(), Self::inline())
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
"DecCoin".into()
|
||||
}
|
||||
|
||||
fn inline() -> String {
|
||||
"{ denom: CurrencyDenom, amount: string }".into()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<Dependency> {
|
||||
vec![Dependency::from_ty::<CurrencyDenom>()
|
||||
.expect("TS was incorrectly defined on `CurrencyDenom`")]
|
||||
}
|
||||
|
||||
fn transparent() -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl DecCoin {
|
||||
pub fn new_base<S: Into<String>>(amount: impl Into<Uint128>, denom: S) -> Self {
|
||||
DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: Decimal::from_atomics(amount, 0).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zero(denom: &CurrencyDenom) -> MajorCurrencyAmount {
|
||||
MajorCurrencyAmount::new("0", denom.clone())
|
||||
pub fn zero<S: Into<String>>(denom: S) -> Self {
|
||||
DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: Decimal::zero(),
|
||||
}
|
||||
}
|
||||
//
|
||||
// pub fn from_cosmrs_coin(coin: &CosmosCoin) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_cosmrs_decimal_and_denom(coin.amount, coin.denom.to_string())
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_uint128_and_denom(
|
||||
// amount_minor: Uint128,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// MajorCurrencyAmount::from_minor_decimal_and_denom(
|
||||
// Decimal::from_atomics(amount_minor, 0)?,
|
||||
// denom_minor,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// pub fn from_minor_decimal_and_denom(
|
||||
// amount_minor: Decimal,
|
||||
// denom_minor: &str,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if !(denom_minor.starts_with('u') || denom_minor.starts_with('U')) {
|
||||
// return Err(TypesError::InvalidDenom(denom_minor.to_string()));
|
||||
// }
|
||||
// let major = amount_minor / Uint128::from(1_000_000u64);
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(&denom_minor[1..].to_string()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(major.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom_minor.to_string()))
|
||||
// }
|
||||
// pub fn from_decimal_and_denom(
|
||||
// amount: Decimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom);
|
||||
// }
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
// pub fn from_cosmrs_decimal_and_denom(
|
||||
// amount: CosmosDecimal,
|
||||
// denom: String,
|
||||
// ) -> Result<MajorCurrencyAmount, TypesError> {
|
||||
// if denom.starts_with('u') || denom.starts_with('U') {
|
||||
// return match Decimal::from_str(&amount.to_string()) {
|
||||
// Ok(amount) => MajorCurrencyAmount::from_minor_decimal_and_denom(amount, &denom),
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(amount.to_string())),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// if let Ok(denom) = CurrencyDenom::from_str(denom.as_str()) {
|
||||
// return Ok(MajorCurrencyAmount {
|
||||
// amount: MajorAmountString(amount.to_string()),
|
||||
// denom,
|
||||
// });
|
||||
// }
|
||||
// Err(TypesError::InvalidDenom(denom))
|
||||
// }
|
||||
//
|
||||
// pub fn into_cosmos_coin(self) -> CosmosCoin {
|
||||
// self.coin.into()
|
||||
// }
|
||||
//
|
||||
// pub fn to_minor_uint128(&self) -> Result<Uint128, TypesError> {
|
||||
// if self.amount.0.contains('.') {
|
||||
// // has a decimal point (Cosmos assumes "." is the decimal separator)
|
||||
// let parts = self.amount.0.split('.');
|
||||
// let str = parts.collect_vec();
|
||||
// if str.is_empty() || str.len() > 2 {
|
||||
// return Err(TypesError::InvalidAmount("Amount is invalid".to_string()));
|
||||
// }
|
||||
// if str.len() == 2 {
|
||||
// // has a decimal, so check decimal places first
|
||||
// if str[1].len() > 6 {
|
||||
// return Err(TypesError::InvalidDenom(
|
||||
// "Amount is invalid, only 6 decimal places of precision are allowed"
|
||||
// .to_string(),
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// // so multiple whole part by 1e6 and add decimal part
|
||||
// let whole_part = Uint128::from_str(str[0])? * Uint128::from(1_000_000u64);
|
||||
//
|
||||
// // TODO: has Rust got anything that deals with fixed point values, or parsing from format strings? Leading zeroes are causing issues
|
||||
// return match format!("0.{}", str[1]).parse::<f64>() {
|
||||
// Ok(decimal_part_float) => {
|
||||
// // this makes an assumption that 6 decimal places of f64 can never lose precision
|
||||
// let truncated = (decimal_part_float * 1_000_000.).trunc() as u32;
|
||||
// let decimal_part = Uint128::from(truncated);
|
||||
// let sum = whole_part + decimal_part;
|
||||
// Ok(sum)
|
||||
// }
|
||||
// Err(_e) => Err(TypesError::InvalidAmount(
|
||||
// "Amount decimal part is invalid".to_string(),
|
||||
// )),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let major = Uint128::from_str(&self.amount.0)?;
|
||||
// let scaled = major * Uint128::new(1_000_000u128);
|
||||
// Ok(scaled)
|
||||
// }
|
||||
|
||||
// pub fn denom_to_string(&self) -> String {
|
||||
// self.denom.to_string()
|
||||
// }
|
||||
pub fn new_scaled_up<S: Into<String>>(
|
||||
base_amount: impl Into<Uint128>,
|
||||
denom: S,
|
||||
exponent: u32,
|
||||
) -> Result<Self, TypesError> {
|
||||
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
|
||||
Ok(DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: try_scale_up_decimal(base_amount, exponent)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_scaled_down<S: Into<String>>(
|
||||
base_amount: impl Into<Uint128>,
|
||||
denom: S,
|
||||
exponent: u32,
|
||||
) -> Result<Self, TypesError> {
|
||||
let base_amount = Decimal::from_atomics(base_amount, 0).unwrap();
|
||||
Ok(DecCoin {
|
||||
denom: denom.into(),
|
||||
amount: try_scale_down_decimal(base_amount, exponent)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn try_scale_down_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
try_scale_down_decimal(self.amount, exponent)
|
||||
}
|
||||
|
||||
pub fn try_scale_up_value(&self, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
try_scale_up_decimal(self.amount, exponent)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MajorCurrencyAmount {
|
||||
// TODO: should thoese live here?
|
||||
pub fn try_scale_down_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
let rhs = 10u128
|
||||
.checked_pow(exponent)
|
||||
.ok_or(TypesError::UnsupportedExponent(exponent))?;
|
||||
let denominator = dec
|
||||
.denominator()
|
||||
.checked_mul(rhs.into())
|
||||
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
|
||||
|
||||
Ok(Decimal::from_ratio(dec.numerator(), denominator))
|
||||
}
|
||||
|
||||
pub fn try_scale_up_decimal(dec: Decimal, exponent: u32) -> Result<Decimal, TypesError> {
|
||||
let rhs = 10u128
|
||||
.checked_pow(exponent)
|
||||
.ok_or(TypesError::UnsupportedExponent(exponent))?;
|
||||
let denominator = dec
|
||||
.denominator()
|
||||
.checked_div(rhs.into())
|
||||
.map_err(|_| TypesError::UnsupportedExponent(exponent))?;
|
||||
|
||||
Ok(Decimal::from_ratio(dec.numerator(), denominator))
|
||||
}
|
||||
|
||||
pub fn try_convert_decimal_to_u128(dec: Decimal) -> Result<u128, TypesError> {
|
||||
let whole = dec.numerator() / dec.denominator();
|
||||
|
||||
// unwrap is fine as we're not dividing by zero here
|
||||
let fractional = (dec.numerator()).checked_rem(dec.denominator()).unwrap();
|
||||
|
||||
// we cannot convert as we'd lose our decimal places
|
||||
// (for example if somebody attempted to represent our gas price (WHICH YOU SHOULDN'T DO) as DecCoin)
|
||||
if fractional != Uint128::zero() {
|
||||
return Err(TypesError::LossyCoinConversion);
|
||||
}
|
||||
Ok(whole.u128())
|
||||
}
|
||||
|
||||
impl Display for DecCoin {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {}", self.amount.0, self.denom)
|
||||
write!(f, "{} {}", self.amount, self.denom)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: cleanup after merge
|
||||
impl From<CosmosCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmosCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CosmWasmCoin> for MajorCurrencyAmount {
|
||||
fn from(c: CosmWasmCoin) -> Self {
|
||||
MajorCurrencyAmount::from(Coin::from(c))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coin> for MajorCurrencyAmount {
|
||||
impl From<Coin> for DecCoin {
|
||||
fn from(coin: Coin) -> Self {
|
||||
// current assumption: MajorCurrencyAmount is represented as decimal with 6 decimal points
|
||||
// unwrap is fine as we haven't exceeded decimal range since our coins are at max 1B in value
|
||||
// (this is a weak assumption, but for solving this merge conflict it's good enough temporary workaround)
|
||||
let amount = Decimal::from_atomics(coin.amount, 6).unwrap();
|
||||
MajorCurrencyAmount {
|
||||
amount: MajorAmountString(amount.to_string()),
|
||||
denom: CurrencyDenom::parse(&coin.denom).expect("this will go away after the merge..."),
|
||||
}
|
||||
DecCoin::new_base(coin.amount, coin.denom)
|
||||
}
|
||||
}
|
||||
|
||||
// temporary...
|
||||
impl From<MajorCurrencyAmount> for CosmosCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmosCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
// this conversion assumes same denomination
|
||||
impl TryFrom<DecCoin> for Coin {
|
||||
type Error = TypesError;
|
||||
|
||||
impl From<MajorCurrencyAmount> for CosmWasmCoin {
|
||||
fn from(c: MajorCurrencyAmount) -> CosmWasmCoin {
|
||||
let c: Coin = c.into();
|
||||
c.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MajorCurrencyAmount> for Coin {
|
||||
fn from(c: MajorCurrencyAmount) -> Coin {
|
||||
let decimal: Decimal = c
|
||||
.amount
|
||||
.0
|
||||
.parse()
|
||||
.expect("stringified amount should have been a valid decimal");
|
||||
|
||||
// again, temporary
|
||||
let exp = Uint128::new(1000000);
|
||||
let val = decimal.mul(exp);
|
||||
|
||||
// again, terrible assumption for denom, but it works temporarily...
|
||||
Coin {
|
||||
amount: val.u128(),
|
||||
denom: format!("u{}", c.denom).to_lowercase(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for MajorCurrencyAmount {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
// again, temporary workaround to help with merge
|
||||
(Coin::from(self).try_add(&Coin::from(rhs)))
|
||||
.expect("provided coins had different denoms")
|
||||
.into()
|
||||
fn try_from(value: DecCoin) -> Result<Self, Self::Error> {
|
||||
Ok(Coin {
|
||||
amount: try_convert_decimal_to_u128(value.try_scale_down_value(0)?)?,
|
||||
denom: value.denom,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::Decimal as CosmosDecimal;
|
||||
use cosmrs::Denom as CosmosDenom;
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use cosmwasm_std::Decimal as CosmWasmDecimal;
|
||||
use serde_json::json;
|
||||
use std::str::FromStr;
|
||||
use std::convert::TryFrom;
|
||||
use std::string::ToString;
|
||||
|
||||
#[test]
|
||||
fn json_to_major_currency_amount() {
|
||||
let nym = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYM"
|
||||
});
|
||||
let nymt = json!({
|
||||
"amount": "1",
|
||||
"denom": "NYMT"
|
||||
});
|
||||
fn dec_value_scale_down() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "1234007000".parse().unwrap(),
|
||||
};
|
||||
|
||||
let test_nym_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let test_nymt_amount = MajorCurrencyAmount::new("1", CurrencyDenom::Nymt);
|
||||
|
||||
let nym_amount = serde_json::from_value::<MajorCurrencyAmount>(nym).unwrap();
|
||||
let nymt_amount = serde_json::from_value::<MajorCurrencyAmount>(nymt).unwrap();
|
||||
|
||||
assert_eq!(nym_amount, test_nym_amount);
|
||||
assert_eq!(nymt_amount, test_nymt_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_amount_json_to_major_currency_amount() {
|
||||
let one_micro_nym = json!({
|
||||
"amount": "0.000001",
|
||||
"denom": "NYM"
|
||||
});
|
||||
|
||||
let expected_nym_amount = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let actual_nym_amount =
|
||||
serde_json::from_value::<MajorCurrencyAmount>(one_micro_nym).unwrap();
|
||||
|
||||
assert_eq!(expected_nym_amount, actual_nym_amount);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_from_str() {
|
||||
assert_eq!(CurrencyDenom::from_str("nym").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("nymt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NYM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYMT").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
assert_eq!(CurrencyDenom::from_str("NyM").unwrap(), CurrencyDenom::Nym);
|
||||
assert_eq!(
|
||||
CurrencyDenom::from_str("NYmt").unwrap(),
|
||||
CurrencyDenom::Nymt
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("foo").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
|
||||
// denominations must all be major
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unym").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
assert!(matches!(
|
||||
CurrencyDenom::from_str("unymt").unwrap_err(),
|
||||
strum::ParseError::VariantNotFound,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string() {
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nym).to_string(),
|
||||
"1 NYM"
|
||||
"1234007000".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(0).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1", CurrencyDenom::Nymt).to_string(),
|
||||
"1 NYMT"
|
||||
"123400700".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
MajorCurrencyAmount::new("1000000000000", CurrencyDenom::Nym).to_string(),
|
||||
"1000000000000 NYM"
|
||||
"12340070".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123400.7".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(4).unwrap()
|
||||
);
|
||||
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "10000000000".parse().unwrap(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
"100".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(8).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(10).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"0.01".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_down_value(12).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_coin_to_major_currency() {
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
fn dec_value_scale_up() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "1234.56".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount, 6).unwrap()
|
||||
assert_eq!(
|
||||
"1234.56".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(0).unwrap()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmwasm_coin_to_major_currency_2() {
|
||||
let coin = CosmWasmCoin {
|
||||
amount: Uint128::from(1_000_000u64),
|
||||
denom: "unym".to_string(),
|
||||
};
|
||||
println!(
|
||||
"from_atomics = {:?}",
|
||||
CosmWasmDecimal::from_atomics(coin.amount, 6)
|
||||
.unwrap()
|
||||
.to_string()
|
||||
assert_eq!(
|
||||
"12345.6".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123456".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1234560".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(3).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"12345600".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(4).unwrap()
|
||||
);
|
||||
let c: MajorCurrencyAmount = coin.into();
|
||||
assert_eq!(c, MajorCurrencyAmount::new("1", CurrencyDenom::Nym));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "0.00000123".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("0.000001", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
|
||||
assert_eq!(
|
||||
"0.0000123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"0.000123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(2).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"123".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(8).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"1230".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(9).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
"12300".parse::<Decimal>().unwrap(),
|
||||
dec.try_scale_up_value(10).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn major_currency_to_minor_cosmos_coin_2() {
|
||||
let expected_cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1000000u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
fn coin_to_dec_coin() {
|
||||
let coin = Coin::new(123, "foo");
|
||||
let dec = DecCoin::from(coin.clone());
|
||||
assert_eq!(coin.denom, dec.denom);
|
||||
assert_eq!(dec.amount, Decimal::from_atomics(coin.amount, 0).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dec_coin_to_coin() {
|
||||
let dec = DecCoin {
|
||||
denom: "foo".to_string(),
|
||||
amount: "123".parse().unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let minor_cosmos_coin = c.into();
|
||||
assert_eq!(expected_cosmos_coin, minor_cosmos_coin);
|
||||
assert_eq!("unym", minor_cosmos_coin.denom.to_string());
|
||||
let coin = Coin::try_from(dec.clone()).unwrap();
|
||||
assert_eq!(dec.denom, coin.denom);
|
||||
assert_eq!(coin.amount, 123u128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_cosmos_coin_to_major_currency_string() {
|
||||
// check minor cosmos coin is converted to major value
|
||||
let cosmos_coin = CosmosCoin {
|
||||
amount: CosmosDecimal::from(1u64),
|
||||
denom: CosmosDenom::from_str("unym").unwrap(),
|
||||
};
|
||||
let c = MajorCurrencyAmount::from(cosmos_coin);
|
||||
assert_eq!(c.to_string(), "0.000001 NYM");
|
||||
fn converting_to_display() {
|
||||
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
|
||||
let values = vec![
|
||||
(1u128, "0.000001"),
|
||||
(10u128, "0.00001"),
|
||||
(100u128, "0.0001"),
|
||||
(1000u128, "0.001"),
|
||||
(10000u128, "0.01"),
|
||||
(100000u128, "0.1"),
|
||||
(1000000u128, "1"),
|
||||
(1234567u128, "1.234567"),
|
||||
(123456700u128, "123.4567"),
|
||||
];
|
||||
|
||||
for (raw, expected) in values {
|
||||
let coin = Coin::new(raw, Network::MAINNET.mix_denom().base);
|
||||
let display = reg.attempt_convert_to_display_dec_coin(coin).unwrap();
|
||||
assert_eq!(Network::MAINNET.mix_denom().display, display.denom);
|
||||
assert_eq!(expected, display.amount.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn denom_to_string() {
|
||||
let c = MajorCurrencyAmount::new("1", CurrencyDenom::Nym);
|
||||
let denom = c.denom.to_string();
|
||||
assert_eq!(denom, "NYM".to_string());
|
||||
fn converting_to_base() {
|
||||
let reg = RegisteredCoins::default_denoms(Network::MAINNET);
|
||||
let values = vec![
|
||||
(1u128, "0.000001"),
|
||||
(10u128, "0.00001"),
|
||||
(100u128, "0.0001"),
|
||||
(1000u128, "0.001"),
|
||||
(10000u128, "0.01"),
|
||||
(100000u128, "0.1"),
|
||||
(1000000u128, "1"),
|
||||
(1234567u128, "1.234567"),
|
||||
(123456700u128, "123.4567"),
|
||||
];
|
||||
|
||||
for (expected, raw_display) in values {
|
||||
let coin = DecCoin {
|
||||
denom: Network::MAINNET.mix_denom().display.into(),
|
||||
amount: raw_display.parse().unwrap(),
|
||||
};
|
||||
let base = reg.attempt_convert_to_base_coin(coin).unwrap();
|
||||
assert_eq!(Network::MAINNET.mix_denom().base, base.denom);
|
||||
assert_eq!(expected, base.amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
-106
@@ -1,13 +1,10 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use log::error;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::mixnode::DelegationEvent as ContractDelegationEvent;
|
||||
use mixnet_contract_common::mixnode::PendingUndelegate as ContractPendingUndelegate;
|
||||
use mixnet_contract_common::Delegation as MixnetContractDelegation;
|
||||
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -18,31 +15,22 @@ use crate::error::TypesError;
|
||||
pub struct Delegation {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of another address
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for Delegation {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let MixnetContractDelegation {
|
||||
owner,
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let amount: MajorCurrencyAmount = amount.into();
|
||||
|
||||
impl Delegation {
|
||||
pub fn from_mixnet_contract(
|
||||
delegation: MixnetContractDelegation,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(Delegation {
|
||||
owner: owner.into_string(),
|
||||
node_identity,
|
||||
amount,
|
||||
block_height,
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
owner: delegation.owner.to_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
|
||||
block_height: delegation.block_height,
|
||||
proxy: delegation.proxy.map(|d| d.to_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -54,7 +42,7 @@ impl TryFrom<MixnetContractDelegation> for Delegation {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct DelegationRecord {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
pub uses_vesting_contract_tokens: bool,
|
||||
@@ -69,16 +57,16 @@ pub struct DelegationRecord {
|
||||
pub struct DelegationWithEverything {
|
||||
pub owner: String,
|
||||
pub node_identity: String,
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub total_delegation: Option<MajorCurrencyAmount>,
|
||||
pub pledge_amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: DecCoin,
|
||||
pub total_delegation: Option<DecCoin>,
|
||||
pub pledge_amount: Option<DecCoin>,
|
||||
pub block_height: u64,
|
||||
pub delegated_on_iso_datetime: String,
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
pub avg_uptime_percent: Option<u8>,
|
||||
pub stake_saturation: Option<f32>,
|
||||
pub uses_vesting_contract_tokens: bool,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
pub accumulated_rewards: Option<DecCoin>,
|
||||
pub pending_events: Vec<DelegationEvent>,
|
||||
pub history: Vec<DelegationRecord>,
|
||||
}
|
||||
@@ -92,34 +80,7 @@ pub struct DelegationWithEverything {
|
||||
pub struct DelegationResult {
|
||||
source_address: String,
|
||||
target_address: String,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
}
|
||||
|
||||
impl DelegationResult {
|
||||
pub fn new(
|
||||
source_address: &str,
|
||||
target_address: &str,
|
||||
amount: Option<MajorCurrencyAmount>,
|
||||
) -> DelegationResult {
|
||||
DelegationResult {
|
||||
source_address: source_address.to_string(),
|
||||
target_address: target_address.to_string(),
|
||||
amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractDelegation> for DelegationResult {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(delegation: MixnetContractDelegation) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.clone().into();
|
||||
Ok(DelegationResult {
|
||||
source_address: delegation.owner().to_string(),
|
||||
target_address: delegation.node_identity(),
|
||||
amount: Some(amount),
|
||||
})
|
||||
}
|
||||
amount: Option<DecCoin>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
@@ -143,36 +104,34 @@ pub struct DelegationEvent {
|
||||
pub kind: DelegationEventKind,
|
||||
pub node_identity: String,
|
||||
pub address: String,
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: Option<DecCoin>,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<ContractDelegationEvent> for DelegationEvent {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(event: ContractDelegationEvent) -> Result<Self, Self::Error> {
|
||||
match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => {
|
||||
let amount: MajorCurrencyAmount = delegation.amount.into();
|
||||
Ok(DelegationEvent {
|
||||
kind: DelegationEventKind::Delegate,
|
||||
block_height: delegation.block_height,
|
||||
address: delegation.owner.into_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: Some(amount),
|
||||
proxy: delegation.proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => Ok(DelegationEvent {
|
||||
impl DelegationEvent {
|
||||
pub fn from_mixnet_contract(
|
||||
event: ContractDelegationEvent,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(match event {
|
||||
ContractDelegationEvent::Delegate(delegation) => DelegationEvent {
|
||||
kind: DelegationEventKind::Delegate,
|
||||
block_height: delegation.block_height,
|
||||
address: delegation.owner.into_string(),
|
||||
node_identity: delegation.node_identity,
|
||||
amount: Some(reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?),
|
||||
proxy: delegation.proxy.map(|p| p.into_string()),
|
||||
},
|
||||
ContractDelegationEvent::Undelegate(pending_undelegate) => DelegationEvent {
|
||||
kind: DelegationEventKind::Undelegate,
|
||||
block_height: pending_undelegate.block_height(),
|
||||
address: pending_undelegate.delegate().into_string(),
|
||||
node_identity: pending_undelegate.mix_identity(),
|
||||
amount: None,
|
||||
proxy: pending_undelegate.proxy().map(|p| p.into_string()),
|
||||
}),
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,30 +159,6 @@ impl From<ContractPendingUndelegate> for PendingUndelegate {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_contract_delegation_events(
|
||||
events: Vec<ContractDelegationEvent>,
|
||||
) -> Result<Vec<DelegationEvent>, TypesError> {
|
||||
let (events, errors): (Vec<_>, Vec<_>) = events
|
||||
.into_iter()
|
||||
.map(|delegation_event| delegation_event.try_into())
|
||||
.partition(Result::is_ok);
|
||||
|
||||
if errors.is_empty() {
|
||||
let events = events
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.collect::<Vec<DelegationEvent>>();
|
||||
return Ok(events);
|
||||
}
|
||||
let errors = errors
|
||||
.into_iter()
|
||||
.filter_map(|e| e.err())
|
||||
.collect::<Vec<TypesError>>();
|
||||
|
||||
error!("Failed to convert delegations: {:?}", errors);
|
||||
Err(TypesError::DelegationsInvalid)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
@@ -232,6 +167,6 @@ pub fn from_contract_delegation_events(
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct DelegationsSummaryResponse {
|
||||
pub delegations: Vec<DelegationWithEverything>,
|
||||
pub total_delegations: MajorCurrencyAmount,
|
||||
pub total_rewards: MajorCurrencyAmount,
|
||||
pub total_delegations: DecCoin,
|
||||
pub total_rewards: DecCoin,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use thiserror::Error;
|
||||
use validator_client::validator_api::error::ValidatorAPIError;
|
||||
use validator_client::{nymd::error::NymdError, ValidatorClientError};
|
||||
|
||||
// TODO: ask @MS why this even exists
|
||||
#[derive(Error, Debug)]
|
||||
pub enum TypesError {
|
||||
#[error("{source}")]
|
||||
@@ -63,6 +64,12 @@ pub enum TypesError {
|
||||
InvalidGatewayBond(),
|
||||
#[error("Invalid delegations")]
|
||||
DelegationsInvalid,
|
||||
#[error("Attempted to use too huge currency exponent ({0})")]
|
||||
UnsupportedExponent(u32),
|
||||
#[error("Attempted to convert coin that would have resulted in loss of precision")]
|
||||
LossyCoinConversion,
|
||||
#[error("The provided coin has an unknown denomination - {0}")]
|
||||
UnknownCoinDenom(String),
|
||||
}
|
||||
|
||||
impl Serialize for TypesError {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::DecCoin;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
@@ -8,10 +8,16 @@ use ts_rs::{Dependency, TS};
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FeeDetails {
|
||||
// expected to be used by the wallet in order to display detailed fee information to the user
|
||||
pub amount: Option<MajorCurrencyAmount>,
|
||||
pub amount: Option<DecCoin>,
|
||||
pub fee: Fee,
|
||||
}
|
||||
|
||||
impl FeeDetails {
|
||||
pub fn new(amount: Option<DecCoin>, fee: Fee) -> Self {
|
||||
FeeDetails { amount, fee }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "generate-ts")]
|
||||
impl TS for FeeDetails {
|
||||
const EXPORT_TO: Option<&'static str> = Some("ts-packages/types/src/types/rust/FeeDetails.ts");
|
||||
@@ -25,13 +31,12 @@ impl TS for FeeDetails {
|
||||
}
|
||||
|
||||
fn inline() -> String {
|
||||
"{ amount: MajorCurrencyAmount | null, fee: Fee }".into()
|
||||
"{ amount: DecCoin | null, fee: Fee }".into()
|
||||
}
|
||||
|
||||
fn dependencies() -> Vec<Dependency> {
|
||||
vec![
|
||||
Dependency::from_ty::<MajorCurrencyAmount>()
|
||||
.expect("TS was incorrectly defined on `CurrencyDenom`"),
|
||||
Dependency::from_ty::<DecCoin>().expect("TS was incorrectly defined on `DecCoin`"),
|
||||
Dependency::from_ty::<ts_type_helpers::Fee>()
|
||||
.expect("TS was incorrectly defined on `ts_type_helpers::Fee`"),
|
||||
]
|
||||
|
||||
+26
-60
@@ -1,48 +1,29 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::error::TypesError;
|
||||
use cosmrs::tx::Gas as CosmrsGas;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::GasInfo as ValidatorClientGasInfo;
|
||||
use validator_client::nymd::GasPrice;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/Gas.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
|
||||
pub struct Gas {
|
||||
/// units of gas used
|
||||
pub gas_units: u64,
|
||||
//
|
||||
// /// gas units converted to fee as major coin amount
|
||||
// pub amount: MajorCurrencyAmount,
|
||||
}
|
||||
|
||||
impl Gas {
|
||||
pub fn from_cosmrs_gas(value: CosmrsGas, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas {
|
||||
gas_units: value.value(),
|
||||
})
|
||||
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value.value());
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value.value(),
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
pub fn from_u64(value: u64) -> Gas {
|
||||
Gas { gas_units: value }
|
||||
}
|
||||
pub fn from_u64(value: u64, _denom_minor: &str) -> Result<Gas, TypesError> {
|
||||
Ok(Gas { gas_units: value })
|
||||
// todo!()
|
||||
// // TODO: use simulator struct to do conversion to fee
|
||||
// let value_u128 = Uint128::from(value);
|
||||
// let amount = Decimal::new(value_u128) * Decimal::from_str("0.0025")?;
|
||||
// Ok(Gas {
|
||||
// gas_units: value,
|
||||
// amount: MajorCurrencyAmount::from_minor_decimal_and_denom(amount, denom_minor)?,
|
||||
// })
|
||||
}
|
||||
|
||||
impl From<CosmrsGas> for Gas {
|
||||
fn from(gas: CosmrsGas) -> Self {
|
||||
Gas {
|
||||
gas_units: gas.value(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,44 +32,29 @@ impl Gas {
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/GasInfo.ts")
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Copy, Clone, Debug)]
|
||||
pub struct GasInfo {
|
||||
/// GasWanted is the maximum units of work we allow this tx to perform.
|
||||
pub gas_wanted: u64,
|
||||
pub gas_wanted: Gas,
|
||||
|
||||
/// GasUsed is the amount of gas actually consumed.
|
||||
pub gas_used: u64,
|
||||
pub gas_used: Gas,
|
||||
}
|
||||
|
||||
/// gas units converted to fee as major coin amount
|
||||
pub fee: MajorCurrencyAmount,
|
||||
impl From<ValidatorClientGasInfo> for GasInfo {
|
||||
fn from(info: ValidatorClientGasInfo) -> Self {
|
||||
GasInfo {
|
||||
gas_wanted: info.gas_wanted.into(),
|
||||
gas_used: info.gas_used.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GasInfo {
|
||||
pub fn from_validator_client_gas_info(
|
||||
value: ValidatorClientGasInfo,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * value.gas_used;
|
||||
Ok(GasInfo {
|
||||
gas_wanted: value.gas_wanted.value(),
|
||||
gas_used: value.gas_used.value(),
|
||||
fee: fee.into(),
|
||||
})
|
||||
}
|
||||
pub fn from_u64(
|
||||
gas_wanted: u64,
|
||||
gas_used: u64,
|
||||
denom_minor: &str,
|
||||
) -> Result<GasInfo, TypesError> {
|
||||
// terrible workaround, but I don't want to break the current flow (just yet)
|
||||
let gas_price = GasPrice::new_with_default_price(denom_minor)?;
|
||||
let fee = (&gas_price) * CosmrsGas::from(gas_used);
|
||||
Ok(GasInfo {
|
||||
gas_wanted,
|
||||
gas_used,
|
||||
fee: fee.into(),
|
||||
})
|
||||
pub fn from_u64(gas_wanted: u64, gas_used: u64) -> GasInfo {
|
||||
GasInfo {
|
||||
gas_wanted: Gas::from_u64(gas_wanted),
|
||||
gas_used: Gas::from_u64(gas_used),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-33
@@ -1,4 +1,4 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Gateway as MixnetContractGateway, GatewayBond as MixnetContractGatewayBond,
|
||||
@@ -54,7 +54,7 @@ impl From<MixnetContractGateway> for Gateway {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub pledge_amount: DecCoin,
|
||||
pub owner: String,
|
||||
pub block_height: u64,
|
||||
pub gateway: Gateway,
|
||||
@@ -63,38 +63,15 @@ pub struct GatewayBond {
|
||||
|
||||
impl GatewayBond {
|
||||
pub fn from_mixnet_contract_gateway_bond(
|
||||
bond: Option<MixnetContractGatewayBond>,
|
||||
) -> Result<Option<GatewayBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: GatewayBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractGatewayBond> for GatewayBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractGatewayBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractGatewayBond {
|
||||
pledge_amount,
|
||||
owner,
|
||||
block_height,
|
||||
gateway,
|
||||
proxy,
|
||||
} = value;
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
|
||||
bond: MixnetContractGatewayBond,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<GatewayBond, TypesError> {
|
||||
Ok(GatewayBond {
|
||||
pledge_amount,
|
||||
owner: owner.into_string(),
|
||||
block_height,
|
||||
gateway: gateway.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
|
||||
owner: bond.owner.to_string(),
|
||||
block_height: bond.block_height,
|
||||
gateway: bond.gateway.into(),
|
||||
proxy: bond.proxy.map(|p| p.into_string()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+26
-54
@@ -1,11 +1,11 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use mixnet_contract_common::{
|
||||
Coin as CosmWasmCoin, MixNode as MixnetContractMixNode,
|
||||
MixNodeBond as MixnetContractMixNodeBond,
|
||||
MixNode as MixnetContractMixNode, MixNodeBond as MixnetContractMixNodeBond,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::Coin;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -58,67 +58,39 @@ impl From<MixnetContractMixNode> for MixNode {
|
||||
)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
pub pledge_amount: MajorCurrencyAmount,
|
||||
pub total_delegation: MajorCurrencyAmount,
|
||||
pub pledge_amount: DecCoin,
|
||||
pub total_delegation: DecCoin,
|
||||
pub owner: String,
|
||||
pub layer: String,
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub proxy: Option<String>,
|
||||
pub accumulated_rewards: Option<MajorCurrencyAmount>,
|
||||
pub accumulated_rewards: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
pub fn from_mixnet_contract_mixnode_bond(
|
||||
bond: Option<MixnetContractMixNodeBond>,
|
||||
) -> Result<Option<MixNodeBond>, TypesError> {
|
||||
match bond {
|
||||
Some(bond) => {
|
||||
let bond: MixNodeBond = bond.try_into()?;
|
||||
Ok(Some(bond))
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<MixnetContractMixNodeBond> for MixNodeBond {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(value: MixnetContractMixNodeBond) -> Result<Self, Self::Error> {
|
||||
let MixnetContractMixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner,
|
||||
layer,
|
||||
block_height,
|
||||
mix_node,
|
||||
proxy,
|
||||
accumulated_rewards,
|
||||
} = value;
|
||||
|
||||
if pledge_amount.denom != total_delegation.denom {
|
||||
return Err(TypesError::InvalidDenom(
|
||||
"The pledge and delegation denominations do not match".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let denom = total_delegation.denom.clone();
|
||||
|
||||
let pledge_amount: MajorCurrencyAmount = pledge_amount.into();
|
||||
let total_delegation: MajorCurrencyAmount = total_delegation.into();
|
||||
let accumulated_rewards: Option<MajorCurrencyAmount> =
|
||||
accumulated_rewards.map(|r| CosmWasmCoin::new(r.u128(), denom).into());
|
||||
|
||||
bond: MixnetContractMixNodeBond,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<MixNodeBond, TypesError> {
|
||||
let denom = bond.pledge_amount.denom.clone();
|
||||
Ok(MixNodeBond {
|
||||
pledge_amount,
|
||||
total_delegation,
|
||||
owner: owner.into_string(),
|
||||
layer: layer.into(),
|
||||
block_height,
|
||||
mix_node: mix_node.into(),
|
||||
proxy: proxy.map(|p| p.into_string()),
|
||||
accumulated_rewards,
|
||||
pledge_amount: reg.attempt_convert_to_display_dec_coin(bond.pledge_amount.into())?,
|
||||
total_delegation: reg
|
||||
.attempt_convert_to_display_dec_coin(bond.total_delegation.into())?,
|
||||
owner: bond.owner.into_string(),
|
||||
layer: bond.layer.into(),
|
||||
block_height: bond.block_height,
|
||||
mix_node: bond.mix_node.into(),
|
||||
proxy: bond.proxy.map(|p| p.to_string()),
|
||||
accumulated_rewards: bond
|
||||
.accumulated_rewards
|
||||
.map(|reward| {
|
||||
// here we're making an assumption that rewards always use the same denom as the pledge
|
||||
// (which I think is a reasonable assumption)
|
||||
reg.attempt_convert_to_display_dec_coin(Coin::new(reward.u128(), denom))
|
||||
})
|
||||
.transpose()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::DecCoin;
|
||||
use crate::error::TypesError;
|
||||
use crate::gas::GasInfo;
|
||||
use crate::gas::{Gas, GasInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator_client::nymd::cosmwasm_client::types::ExecuteResult;
|
||||
use validator_client::nymd::TxResponse;
|
||||
@@ -15,31 +15,23 @@ pub struct SendTxResult {
|
||||
pub block_height: u64,
|
||||
pub code: u32,
|
||||
pub details: TransactionDetails,
|
||||
pub gas_used: u64,
|
||||
pub gas_wanted: u64,
|
||||
pub gas_used: Gas,
|
||||
pub gas_wanted: Gas,
|
||||
pub tx_hash: String,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl SendTxResult {
|
||||
pub fn new(
|
||||
t: TxResponse,
|
||||
details: TransactionDetails,
|
||||
_denom_minor: &str,
|
||||
) -> Result<SendTxResult, TypesError> {
|
||||
Ok(SendTxResult {
|
||||
pub fn new(t: TxResponse, details: TransactionDetails, fee: Option<DecCoin>) -> SendTxResult {
|
||||
SendTxResult {
|
||||
block_height: t.height.value(),
|
||||
code: t.tx_result.code.value(),
|
||||
details,
|
||||
gas_used: t.tx_result.gas_used.value(),
|
||||
gas_wanted: t.tx_result.gas_wanted.value(),
|
||||
gas_used: t.tx_result.gas_used.into(),
|
||||
gas_wanted: t.tx_result.gas_wanted.into(),
|
||||
tx_hash: t.hash.to_string(),
|
||||
// that is completely wrong: fee is what you told the validator to use beforehand
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(t.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
})
|
||||
fee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +42,21 @@ impl SendTxResult {
|
||||
)]
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct TransactionDetails {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub from_address: String,
|
||||
pub to_address: String,
|
||||
}
|
||||
|
||||
impl TransactionDetails {
|
||||
pub fn new(amount: DecCoin, from_address: String, to_address: String) -> Self {
|
||||
TransactionDetails {
|
||||
amount,
|
||||
from_address,
|
||||
to_address,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
feature = "generate-ts",
|
||||
@@ -66,18 +68,16 @@ pub struct TransactionExecuteResult {
|
||||
pub data_json: String,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
pub fee: MajorCurrencyAmount,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl TransactionExecuteResult {
|
||||
pub fn from_execute_result(
|
||||
value: ExecuteResult,
|
||||
denom_minor: &str,
|
||||
fee: Option<DecCoin>,
|
||||
) -> Result<TransactionExecuteResult, TypesError> {
|
||||
let gas_info = GasInfo::from_validator_client_gas_info(value.gas_info, denom_minor)?;
|
||||
let fee = gas_info.fee.clone();
|
||||
Ok(TransactionExecuteResult {
|
||||
gas_info,
|
||||
gas_info: value.gas_info.into(),
|
||||
transaction_hash: value.transaction_hash.to_string(),
|
||||
data_json: ::serde_json::to_string_pretty(&value.data)?,
|
||||
logs_json: ::serde_json::to_string_pretty(&value.logs)?,
|
||||
@@ -97,30 +97,24 @@ pub struct RpcTransactionResponse {
|
||||
pub tx_result_json: String,
|
||||
pub block_height: u64,
|
||||
pub transaction_hash: String,
|
||||
pub gas_info: GasInfo,
|
||||
// pub fee: MajorCurrencyAmount,
|
||||
pub gas_used: Gas,
|
||||
pub gas_wanted: Gas,
|
||||
pub fee: Option<DecCoin>,
|
||||
}
|
||||
|
||||
impl RpcTransactionResponse {
|
||||
pub fn from_tx_response(
|
||||
value: &TxResponse,
|
||||
denom_minor: &str,
|
||||
t: &TxResponse,
|
||||
fee: Option<DecCoin>,
|
||||
) -> Result<RpcTransactionResponse, TypesError> {
|
||||
Ok(RpcTransactionResponse {
|
||||
index: value.index,
|
||||
gas_info: GasInfo::from_u64(
|
||||
value.tx_result.gas_wanted.value(),
|
||||
value.tx_result.gas_used.value(),
|
||||
denom_minor,
|
||||
)?,
|
||||
transaction_hash: value.hash.to_string(),
|
||||
tx_result_json: ::serde_json::to_string_pretty(&value.tx_result)?,
|
||||
block_height: value.height.value(),
|
||||
// wrong
|
||||
// fee: MajorCurrencyAmount::from_decimal_and_denom(
|
||||
// Decimal::new(Uint128::from(value.tx_result.gas_used.value())),
|
||||
// denom_minor.to_string(),
|
||||
// )?,
|
||||
index: t.index,
|
||||
gas_used: t.tx_result.gas_used.into(),
|
||||
gas_wanted: t.tx_result.gas_wanted.into(),
|
||||
transaction_hash: t.hash.to_string(),
|
||||
tx_result_json: ::serde_json::to_string_pretty(&t.tx_result)?,
|
||||
block_height: t.height.value(),
|
||||
fee,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+35
-45
@@ -1,10 +1,10 @@
|
||||
use crate::currency::MajorCurrencyAmount;
|
||||
use crate::currency::{DecCoin, RegisteredCoins};
|
||||
use crate::error::TypesError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use vesting_contract::vesting::Account as VestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as VestingVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as VestingOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
use vesting_contract::vesting::Account as ContractVestingAccount;
|
||||
use vesting_contract::vesting::VestingPeriod as ContractVestingPeriod;
|
||||
use vesting_contract_common::OriginalVestingResponse as ContractOriginalVestingResponse;
|
||||
use vesting_contract_common::PledgeData as ContractPledgeData;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -13,25 +13,19 @@ use vesting_contract_common::PledgeData as VestingPledgeData;
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PledgeData {
|
||||
pub amount: MajorCurrencyAmount,
|
||||
pub amount: DecCoin,
|
||||
pub block_time: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingPledgeData> for PledgeData {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingPledgeData) -> Result<Self, Self::Error> {
|
||||
let amount: MajorCurrencyAmount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
block_time: data.block_time().seconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PledgeData {
|
||||
pub fn and_then(data: VestingPledgeData) -> Option<Self> {
|
||||
data.try_into().ok()
|
||||
pub fn from_vesting_contract(
|
||||
pledge: ContractPledgeData,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(PledgeData {
|
||||
amount: reg.attempt_convert_to_display_dec_coin(pledge.amount.into())?,
|
||||
block_time: pledge.block_time.seconds(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,20 +36,20 @@ impl PledgeData {
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct OriginalVestingResponse {
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
number_of_periods: usize,
|
||||
period_duration: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingOriginalVestingResponse> for OriginalVestingResponse {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(data: VestingOriginalVestingResponse) -> Result<Self, Self::Error> {
|
||||
let amount = data.amount().into();
|
||||
Ok(Self {
|
||||
amount,
|
||||
number_of_periods: data.number_of_periods(),
|
||||
period_duration: data.period_duration(),
|
||||
impl OriginalVestingResponse {
|
||||
pub fn from_vesting_contract(
|
||||
res: ContractOriginalVestingResponse,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(OriginalVestingResponse {
|
||||
amount: reg.attempt_convert_to_display_dec_coin(res.amount.into())?,
|
||||
number_of_periods: res.number_of_periods,
|
||||
period_duration: res.period_duration,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -71,24 +65,20 @@ pub struct VestingAccountInfo {
|
||||
staking_address: Option<String>,
|
||||
start_time: u64,
|
||||
periods: Vec<VestingPeriod>,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
}
|
||||
|
||||
impl TryFrom<VestingAccount> for VestingAccountInfo {
|
||||
type Error = TypesError;
|
||||
|
||||
fn try_from(account: VestingAccount) -> Result<Self, Self::Error> {
|
||||
let mut periods = Vec::new();
|
||||
for period in account.periods() {
|
||||
periods.push(period.into());
|
||||
}
|
||||
let amount: MajorCurrencyAmount = account.coin().into();
|
||||
Ok(Self {
|
||||
impl VestingAccountInfo {
|
||||
pub fn from_vesting_contract(
|
||||
account: ContractVestingAccount,
|
||||
reg: &RegisteredCoins,
|
||||
) -> Result<Self, TypesError> {
|
||||
Ok(VestingAccountInfo {
|
||||
owner_address: account.owner_address().to_string(),
|
||||
staking_address: account.staking_address().map(|a| a.to_string()),
|
||||
start_time: account.start_time().seconds(),
|
||||
periods,
|
||||
amount,
|
||||
periods: account.periods().into_iter().map(Into::into).collect(),
|
||||
amount: reg.attempt_convert_to_display_dec_coin(account.coin.into())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -104,8 +94,8 @@ pub struct VestingPeriod {
|
||||
period_seconds: u64,
|
||||
}
|
||||
|
||||
impl From<VestingVestingPeriod> for VestingPeriod {
|
||||
fn from(period: VestingVestingPeriod) -> Self {
|
||||
impl From<ContractVestingPeriod> for VestingPeriod {
|
||||
fn from(period: ContractVestingPeriod) -> Self {
|
||||
Self {
|
||||
start_time: period.start_time,
|
||||
period_seconds: period.period_seconds,
|
||||
|
||||
@@ -1195,7 +1195,7 @@ mod tests {
|
||||
|
||||
// _try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// let delegation = query_mixnode_delegation(
|
||||
// let _delegation = query_mixnode_delegation(
|
||||
// &deps.storage,
|
||||
// &deps.api,
|
||||
// identity.clone(),
|
||||
|
||||
@@ -1225,7 +1225,6 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_1)
|
||||
@@ -1531,7 +1530,7 @@ pub mod tests {
|
||||
assert_eq!(mix_2_reward_result.lambda(), U128::from_num(0.0001f64));
|
||||
assert_eq!(mix_2_reward_result.reward().int(), 974456u128);
|
||||
|
||||
let mix_3_reward_result = mix_3.reward(¶ms3);
|
||||
let _mix_3_reward_result = mix_3.reward(¶ms3);
|
||||
|
||||
// assert_eq!(mix_3_reward_result.reward().int(), mix_1_reward_result.reward().int() + mix_2_reward_result.reward().int());
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ fn generate_storage_key(storage: &mut dyn Storage) -> Result<u32, ContractError>
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct Account {
|
||||
owner_address: Addr,
|
||||
staking_address: Option<Addr>,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
pub owner_address: Addr,
|
||||
pub staking_address: Option<Addr>,
|
||||
pub start_time: Timestamp,
|
||||
pub periods: Vec<VestingPeriod>,
|
||||
pub coin: Coin,
|
||||
storage_key: u32,
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ module.exports = {
|
||||
return {
|
||||
amount: {
|
||||
amount: '100',
|
||||
denom: 'NYMT',
|
||||
denom: 'nymt',
|
||||
},
|
||||
printable_balance: '100 NYMT',
|
||||
};
|
||||
@@ -25,7 +25,7 @@ module.exports = {
|
||||
return {
|
||||
amount: {
|
||||
amount: '0.01',
|
||||
denom: 'NYM',
|
||||
denom: 'nym',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Generated
+4
-6
@@ -844,9 +844,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f109fe191e73898d74b8020c50f86018364ad19bc30318aa074616c382b52856"
|
||||
version = "0.12.3"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -855,9 +854,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8413275b23cb5a0734d9d1e3e33f0b5b94547c1e94776dbc3149dbf46588a533"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmrs::Denom;
|
||||
use config::defaults::all::Network as ConfigNetwork;
|
||||
use config::defaults::{mainnet, qa, sandbox, DenomDetails};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use strum::EnumIter;
|
||||
|
||||
use config::defaults::all::Network as ConfigNetwork;
|
||||
use config::defaults::{mainnet, qa, sandbox};
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -28,13 +25,19 @@ impl Network {
|
||||
self.to_string().to_lowercase()
|
||||
}
|
||||
|
||||
// this should be returning just a `&str`, but don't want to cause too many conflicts just yet...
|
||||
pub fn base_mix_denom(&self) -> Denom {
|
||||
pub fn mix_denom(&self) -> DenomDetails {
|
||||
match self {
|
||||
// network defaults should be correctly formatted
|
||||
Network::QA => Denom::from_str(qa::MIX_DENOM.base).unwrap(),
|
||||
Network::SANDBOX => Denom::from_str(sandbox::MIX_DENOM.base).unwrap(),
|
||||
Network::MAINNET => Denom::from_str(mainnet::MIX_DENOM.base).unwrap(),
|
||||
Network::QA => qa::MIX_DENOM,
|
||||
Network::SANDBOX => sandbox::MIX_DENOM,
|
||||
Network::MAINNET => mainnet::MIX_DENOM,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_mix_denom(&self) -> &str {
|
||||
match self {
|
||||
Network::QA => qa::MIX_DENOM.base,
|
||||
Network::SANDBOX => sandbox::MIX_DENOM.base,
|
||||
Network::MAINNET => mainnet::MIX_DENOM.base,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use nym_types::error::TypesError;
|
||||
use nym_wallet_types::network::Network;
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::io;
|
||||
use std::num::ParseIntError;
|
||||
@@ -106,6 +107,10 @@ pub enum BackendError {
|
||||
FailedToDeriveAddress,
|
||||
#[error("{0}")]
|
||||
ValueParseError(#[from] ParseIntError),
|
||||
#[error("The provided coin has an unknown denomination - {0}")]
|
||||
UnknownCoinDenom(String),
|
||||
#[error("Network {network} doesn't have any associated registered coin denoms")]
|
||||
NoCoinsRegistered { network: Network },
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
)]
|
||||
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use std::sync::Arc;
|
||||
use tauri::Menu;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
mod config;
|
||||
mod error;
|
||||
@@ -24,7 +22,7 @@ use crate::operations::simulate;
|
||||
use crate::operations::validator_api;
|
||||
use crate::operations::vesting;
|
||||
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn main() {
|
||||
@@ -32,7 +30,7 @@ fn main() {
|
||||
setup_logging();
|
||||
|
||||
tauri::Builder::default()
|
||||
.manage(Arc::new(RwLock::new(State::default())))
|
||||
.manage(WalletState::default())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
mixnet::account::add_account_for_password,
|
||||
mixnet::account::archive_wallet_file,
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::state::WalletState;
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use nym_wallet_types::network_config::{Validator, ValidatorUrl, ValidatorUrls};
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_validator_nymd_urls(
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<ValidatorUrls, BackendError> {
|
||||
let state = state.read().await;
|
||||
let urls: Vec<ValidatorUrl> = state.get_nymd_urls(network).collect();
|
||||
@@ -24,7 +19,7 @@ pub async fn get_validator_nymd_urls(
|
||||
#[tauri::command]
|
||||
pub async fn get_validator_api_urls(
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<ValidatorUrls, BackendError> {
|
||||
let state = state.read().await;
|
||||
let urls: Vec<ValidatorUrl> = state.get_api_urls(network).collect();
|
||||
@@ -35,7 +30,7 @@ pub async fn get_validator_api_urls(
|
||||
pub async fn select_validator_nymd_url(
|
||||
url: &str,
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::debug!("Selecting new validator nymd_url for {network}: {url}");
|
||||
state
|
||||
@@ -49,7 +44,7 @@ pub async fn select_validator_nymd_url(
|
||||
pub async fn select_validator_api_url(
|
||||
url: &str,
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::debug!("Selecting new validator api_url for {network}: {url}");
|
||||
state.write().await.select_validator_api_url(url, network)?;
|
||||
@@ -60,7 +55,7 @@ pub async fn select_validator_api_url(
|
||||
pub async fn add_validator(
|
||||
validator: Validator,
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::debug!("Add validator for {network}: {validator}");
|
||||
let url = validator.try_into()?;
|
||||
@@ -72,7 +67,7 @@ pub async fn add_validator(
|
||||
pub async fn remove_validator(
|
||||
validator: Validator,
|
||||
network: WalletNetwork,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::debug!("Remove validator for {network}: {validator}");
|
||||
let url = validator.try_into()?;
|
||||
@@ -83,7 +78,7 @@ pub async fn remove_validator(
|
||||
// Update the list of validators by fecthing additional ones remotely. If it fails, just ignore.
|
||||
#[tauri::command]
|
||||
pub async fn update_validator_urls(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
let mut w_state = state.write().await;
|
||||
let _r = w_state.fetch_updated_validator_urls().await;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::error::BackendError;
|
||||
use crate::network_config;
|
||||
use crate::nymd_client;
|
||||
use crate::state::{State, WalletAccountIds};
|
||||
use crate::state::{WalletAccountIds, WalletState};
|
||||
use crate::wallet_storage::{self, DEFAULT_LOGIN_ID};
|
||||
use bip39::{Language, Mnemonic};
|
||||
use config::defaults::all::Network;
|
||||
@@ -10,15 +9,11 @@ use config::defaults::COSMOS_DERIVATION_PATH;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use itertools::Itertools;
|
||||
use nym_types::account::{Account, AccountEntry, Balance};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_wallet_types::network::Network as WalletNetwork;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet};
|
||||
use validator_client::{nymd::SigningNymdClient, Client};
|
||||
@@ -26,33 +21,30 @@ use validator_client::{nymd::SigningNymdClient, Client};
|
||||
#[tauri::command]
|
||||
pub async fn connect_with_mnemonic(
|
||||
mnemonic: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Account, BackendError> {
|
||||
let mnemonic = Mnemonic::from_str(&mnemonic)?;
|
||||
_connect_with_mnemonic(mnemonic, state).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_balance(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Balance, BackendError> {
|
||||
let denom = state.read().await.current_network().base_mix_denom();
|
||||
match nymd_client!(state)
|
||||
.get_balance(nymd_client!(state).address(), denom)
|
||||
.await
|
||||
pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result<Balance, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let address = client.nymd.address();
|
||||
let network = guard.current_network();
|
||||
let base_mix_denom = network.base_mix_denom();
|
||||
|
||||
match client
|
||||
.nymd
|
||||
.get_balance(address, base_mix_denom.to_string())
|
||||
.await?
|
||||
{
|
||||
Ok(Some(coin)) => {
|
||||
let amount = MajorCurrencyAmount::from(coin);
|
||||
let printable_balance = amount.to_string();
|
||||
Ok(Balance {
|
||||
amount,
|
||||
printable_balance,
|
||||
})
|
||||
Some(coin) => {
|
||||
let amount = guard.attempt_convert_to_display_dec_coin(coin)?;
|
||||
Ok(Balance::new(amount))
|
||||
}
|
||||
Ok(None) => Err(BackendError::NoBalance(
|
||||
nymd_client!(state).address().to_string(),
|
||||
)),
|
||||
Err(e) => Err(BackendError::from(e)),
|
||||
None => Err(BackendError::NoBalance(address.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,19 +60,15 @@ pub fn validate_mnemonic(mnemonic: &str) -> bool {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn switch_network(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
network: WalletNetwork,
|
||||
) -> Result<Account, BackendError> {
|
||||
let account = {
|
||||
let r_state = state.read().await;
|
||||
let client = r_state.client(network)?;
|
||||
let denom = network.base_mix_denom();
|
||||
let denom = network.mix_denom();
|
||||
|
||||
Account::new(
|
||||
client.nymd.mixnet_contract_address().to_string(),
|
||||
client.nymd.address().to_string(),
|
||||
denom.try_into()?,
|
||||
)
|
||||
Account::new(client.nymd.address().to_string(), denom)
|
||||
};
|
||||
|
||||
let mut w_state = state.write().await;
|
||||
@@ -90,7 +78,7 @@ pub async fn switch_network(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn logout(state: tauri::State<'_, Arc<RwLock<State>>>) -> Result<(), BackendError> {
|
||||
pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendError> {
|
||||
state.write().await.logout();
|
||||
Ok(())
|
||||
}
|
||||
@@ -102,7 +90,7 @@ fn random_mnemonic() -> Mnemonic {
|
||||
|
||||
async fn _connect_with_mnemonic(
|
||||
mnemonic: Mnemonic,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Account, BackendError> {
|
||||
{
|
||||
let mut w_state = state.write().await;
|
||||
@@ -160,9 +148,8 @@ async fn _connect_with_mnemonic(
|
||||
.find(|client| WalletNetwork::from(client.network.clone()) == default_network);
|
||||
let account_for_default_network = match client_for_default_network {
|
||||
Some(client) => Ok(Account::new(
|
||||
client.nymd.mixnet_contract_address().to_string(),
|
||||
client.nymd.address().to_string(),
|
||||
default_network.base_mix_denom().try_into()?,
|
||||
default_network.mix_denom(),
|
||||
)),
|
||||
None => Err(BackendError::NetworkNotSupported(
|
||||
config::defaults::DEFAULT_NETWORK,
|
||||
@@ -178,6 +165,7 @@ async fn _connect_with_mnemonic(
|
||||
let network: WalletNetwork = client.network.clone().into();
|
||||
let mut w_state = state.write().await;
|
||||
w_state.add_client(network, client);
|
||||
w_state.register_default_denoms(network);
|
||||
}
|
||||
|
||||
account_for_default_network
|
||||
@@ -328,7 +316,7 @@ pub fn create_password(mnemonic: &str, password: String) -> Result<(), BackendEr
|
||||
#[tauri::command]
|
||||
pub async fn sign_in_with_password(
|
||||
password: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Account, BackendError> {
|
||||
log::info!("Signing in with password");
|
||||
|
||||
@@ -368,7 +356,7 @@ fn extract_first_mnemonic(
|
||||
pub async fn sign_in_with_password_and_account_id(
|
||||
account_id: &str,
|
||||
password: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Account, BackendError> {
|
||||
log::info!("Signing in with password");
|
||||
|
||||
@@ -420,7 +408,7 @@ pub async fn add_account_for_password(
|
||||
mnemonic: &str,
|
||||
password: &str,
|
||||
account_id: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<AccountEntry, BackendError> {
|
||||
log::info!("Adding account for the current password: {account_id}");
|
||||
let mnemonic = Mnemonic::from_str(mnemonic)?;
|
||||
@@ -462,7 +450,7 @@ pub async fn add_account_for_password(
|
||||
async fn set_state_with_all_accounts(
|
||||
stored_login: wallet_storage::StoredLogin,
|
||||
first_id_when_converting: wallet_storage::AccountId,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::trace!("Set state with accounts:");
|
||||
let all_accounts: Vec<_> = stored_login
|
||||
@@ -503,7 +491,7 @@ async fn set_state_with_all_accounts(
|
||||
pub async fn remove_account_for_password(
|
||||
password: &str,
|
||||
account_id: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
log::info!("Removing account: {account_id}");
|
||||
// Currently we only support a single, default, id in the wallet
|
||||
@@ -534,7 +522,7 @@ fn derive_address(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_accounts(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<AccountEntry>, BackendError> {
|
||||
log::trace!("Listing accounts");
|
||||
let state = state.read().await;
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use mixnet_contract_common::ContractStateParams;
|
||||
use nym_wallet_types::admin::TauriContractStateParams;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use mixnet_contract_common::ContractStateParams;
|
||||
use nym_wallet_types::admin::TauriContractStateParams;
|
||||
use std::convert::TryInto;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_contract_settings(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TauriContractStateParams, BackendError> {
|
||||
log::info!(">>> Getting contract settings");
|
||||
let res = nymd_client!(state).get_contract_settings().await?.into();
|
||||
@@ -25,7 +20,7 @@ pub async fn get_contract_settings(
|
||||
pub async fn update_contract_settings(
|
||||
params: TauriContractStateParams,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TauriContractStateParams, BackendError> {
|
||||
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
|
||||
log::info!(
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use crate::{Gateway, MixNode};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::gateway::GatewayBond;
|
||||
use nym_types::mixnode::MixNodeBond;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{CosmWasmCoin, Fee};
|
||||
use validator_client::nymd::{Coin, Fee};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
let guard = state.read().await;
|
||||
let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Bond gateway: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
&gateway.identity_key,
|
||||
">>> Bond gateway: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}",
|
||||
gateway.identity_key,
|
||||
pledge,
|
||||
&pledge_minor,
|
||||
pledge_base,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.bond_gateway(gateway, owner_signature, pledge_minor, fee)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.bond_gateway(gateway, owner_signature, pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_gateway(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(">>> Unbond gateway, fee = {:?}", fee);
|
||||
let res = nymd_client!(state).unbond_gateway(fee).await?;
|
||||
let res = guard.current_client()?.nymd.unbond_gateway(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -58,43 +58,46 @@ pub async fn unbond_gateway(
|
||||
pub async fn bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
let guard = state.read().await;
|
||||
let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Bond mixnode: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
">>> Bond mixnode: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}",
|
||||
mixnode.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
pledge_base,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.bond_mixnode(mixnode, owner_signature, pledge_minor, fee)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.bond_mixnode(mixnode, owner_signature, pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(">>> Unbond mixnode, fee = {:?}", fee);
|
||||
let res = nymd_client!(state).unbond_mixnode(fee).await?;
|
||||
let res = guard.current_client()?.nymd.unbond_mixnode(fee).await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -102,34 +105,43 @@ pub async fn unbond_mixnode(
|
||||
pub async fn update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Update mixnode: profit_margin_percent = {}, fee {:?}",
|
||||
profit_margin_percent,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<MixNodeBond>, BackendError> {
|
||||
log::info!(">>> Get mixnode bond details");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_mixnode(client.nymd.address()).await?;
|
||||
let res = MixNodeBond::from_mixnet_contract_mixnode_bond(bond)?;
|
||||
let res = bond
|
||||
.map(|bond| {
|
||||
guard
|
||||
.registered_coins()
|
||||
.map(|reg| MixNodeBond::from_mixnet_contract_mixnode_bond(bond, reg))
|
||||
})
|
||||
.transpose()?
|
||||
.transpose()?;
|
||||
log::info!(
|
||||
"<<< identity_key = {:?}",
|
||||
res.as_ref().map(|r| r.mix_node.identity_key.to_string())
|
||||
@@ -140,13 +152,21 @@ pub async fn mixnode_bond_details(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn gateway_bond_details(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<GatewayBond>, BackendError> {
|
||||
log::info!(">>> Get gateway bond details");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let bond = client.nymd.owns_gateway(client.nymd.address()).await?;
|
||||
let res = GatewayBond::from_mixnet_contract_gateway_bond(bond)?;
|
||||
let res = bond
|
||||
.map(|bond| {
|
||||
guard
|
||||
.registered_coins()
|
||||
.map(|reg| GatewayBond::from_mixnet_contract_gateway_bond(bond, reg))
|
||||
})
|
||||
.transpose()?
|
||||
.transpose()?;
|
||||
|
||||
log::info!(
|
||||
"<<< identity_key = {:?}",
|
||||
res.as_ref().map(|r| r.gateway.identity_key.to_string())
|
||||
@@ -158,17 +178,23 @@ pub async fn gateway_bond_details(
|
||||
#[tauri::command]
|
||||
pub async fn get_operator_rewards(
|
||||
address: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Get operator rewards for {}", address);
|
||||
let denom = state.read().await.current_network().base_mix_denom();
|
||||
let rewards_as_minor = nymd_client!(state).get_operator_rewards(address).await?;
|
||||
let coin = CosmWasmCoin::new(rewards_as_minor.u128(), denom.as_ref());
|
||||
let amount: MajorCurrencyAmount = coin.into();
|
||||
let guard = state.read().await;
|
||||
let network = guard.current_network();
|
||||
let denom = network.base_mix_denom();
|
||||
let reward_amount = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.get_operator_rewards(address)
|
||||
.await?;
|
||||
let base_coin = Coin::new(reward_amount.u128(), denom);
|
||||
let display_coin: DecCoin = guard.attempt_convert_to_display_dec_coin(base_coin.clone())?;
|
||||
log::info!(
|
||||
"<<< rewards_as_minor = {}, amount = {}",
|
||||
rewards_as_minor,
|
||||
amount
|
||||
"<<< rewards_base = {}, rewards_display = {}",
|
||||
base_coin,
|
||||
display_coin
|
||||
);
|
||||
Ok(amount)
|
||||
Ok(display_coin)
|
||||
}
|
||||
|
||||
@@ -1,63 +1,66 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use crate::vesting::delegate::{
|
||||
get_pending_vesting_delegation_events, vesting_undelegate_from_mixnode,
|
||||
};
|
||||
use crate::{api_client, nymd_client};
|
||||
use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use nym_types::currency::{CurrencyDenom, MajorCurrencyAmount};
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::delegation::{
|
||||
from_contract_delegation_events, Delegation, DelegationEvent, DelegationRecord,
|
||||
DelegationWithEverything, DelegationsSummaryResponse,
|
||||
Delegation, DelegationEvent, DelegationRecord, DelegationWithEverything,
|
||||
DelegationsSummaryResponse,
|
||||
};
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
use validator_client::nymd::{Coin, Fee};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_pending_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
log::info!(">>> Get pending delegation events");
|
||||
let events = nymd_client!(state)
|
||||
.get_pending_delegation_events(nymd_client!(state).address().to_string(), None)
|
||||
let guard = state.read().await;
|
||||
let reg = guard.registered_coins()?;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let events = client
|
||||
.nymd
|
||||
.get_pending_delegation_events(client.nymd.address().to_string(), None)
|
||||
.await?;
|
||||
log::info!("<<< {} pending delegation events", events.len());
|
||||
log::trace!("<<< pending delegation events = {:?}", events);
|
||||
|
||||
match from_contract_delegation_events(events) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
Ok(events
|
||||
.into_iter()
|
||||
.map(|event| DelegationEvent::from_mixnet_contract(event, reg))
|
||||
.collect::<Result<_, _>>()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let delegation = amount.clone().into();
|
||||
let guard = state.read().await;
|
||||
let delegation_base = guard.attempt_convert_to_base_coin(amount.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Delegate to mixnode: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
|
||||
">>> Delegate to mixnode: identity_key = {}, display_amount = {}, base_amount = {}, fee = {:?}",
|
||||
identity,
|
||||
amount,
|
||||
delegation,
|
||||
delegation_base,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.delegate_to_mixnode(identity, delegation, fee)
|
||||
.delegate_to_mixnode(identity, delegation_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -65,22 +68,25 @@ pub async fn delegate_to_mixnode(
|
||||
pub async fn undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Undelegate from mixnode: identity_key = {}, fee = {:?}",
|
||||
identity,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.remove_mixnode_delegation(identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -90,7 +96,7 @@ pub async fn undelegate_all_from_mixnode(
|
||||
uses_vesting_contract_tokens: bool,
|
||||
fee: Option<Fee>,
|
||||
fee2: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<TransactionExecuteResult>, BackendError> {
|
||||
log::info!(
|
||||
">>> Undelegate all from mixnode: identity_key = {}, uses_vesting_contract_tokens = {}, fee = {:?}",
|
||||
@@ -110,30 +116,31 @@ pub async fn undelegate_all_from_mixnode(
|
||||
|
||||
struct DelegationWithHistory {
|
||||
pub delegation: Delegation,
|
||||
pub amount_sum: MajorCurrencyAmount,
|
||||
pub amount_sum: DecCoin,
|
||||
pub history: Vec<DelegationRecord>,
|
||||
pub uses_vesting_contract_tokens: bool,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_all_mix_delegations(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<DelegationWithEverything>, BackendError> {
|
||||
log::info!(">>> Get all mixnode delegations");
|
||||
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let reg = guard.registered_coins()?;
|
||||
|
||||
// TODO: add endpoint to validator API to get a single mix node bond
|
||||
let mixnodes = api_client!(state).get_mixnodes().await?;
|
||||
let mixnodes = client.validator_api.get_mixnodes().await?;
|
||||
|
||||
let address = nymd_client!(state).address().to_string();
|
||||
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
|
||||
let address = client.nymd.address();
|
||||
let network = guard.current_network();
|
||||
let display_mix_denom = network.display_mix_denom();
|
||||
let base_mix_denom = network.base_mix_denom();
|
||||
|
||||
log::info!(" >>> Get delegations");
|
||||
let delegations = nymd_client!(state)
|
||||
.get_delegator_delegations_paged(address.clone(), None, None) // get all delegations, ignoring paging
|
||||
.await?
|
||||
.delegations;
|
||||
let delegations = client.get_all_delegator_delegations(address).await?;
|
||||
log::info!(" <<< {} delegations", delegations.len());
|
||||
|
||||
// first get pending events from the mixnet contract (operations made with unlocked tokens)
|
||||
@@ -152,20 +159,21 @@ pub async fn get_all_mix_delegations(
|
||||
|
||||
let mut map: HashMap<String, DelegationWithHistory> = HashMap::new();
|
||||
|
||||
for pending_event in pending_events_for_account.clone() {
|
||||
for pending_event in &pending_events_for_account {
|
||||
if delegations
|
||||
.iter()
|
||||
.any(|d| d.node_identity == pending_event.node_identity)
|
||||
{
|
||||
let amount = pending_event
|
||||
.amount
|
||||
.unwrap_or_else(|| MajorCurrencyAmount::zero(&denom));
|
||||
.clone()
|
||||
.unwrap_or_else(|| DecCoin::zero(display_mix_denom));
|
||||
let delegation = DelegationWithHistory {
|
||||
delegation: Delegation {
|
||||
amount: amount.clone(),
|
||||
node_identity: pending_event.node_identity,
|
||||
proxy: pending_event.proxy,
|
||||
owner: pending_event.address,
|
||||
node_identity: pending_event.node_identity.clone(),
|
||||
proxy: pending_event.proxy.clone(), // TODO: ask @MS about delegations via vesting contract => surely we'd have proxy there?
|
||||
owner: pending_event.address.clone(),
|
||||
block_height: pending_event.block_height,
|
||||
},
|
||||
amount_sum: amount,
|
||||
@@ -178,11 +186,13 @@ pub async fn get_all_mix_delegations(
|
||||
|
||||
for d in delegations {
|
||||
// create record of delegation
|
||||
let delegated_on_iso_datetime = nymd_client!(state)
|
||||
let delegated_on_iso_datetime = client
|
||||
.nymd
|
||||
.get_block_timestamp(Some(d.block_height as u32))
|
||||
.await?
|
||||
.to_rfc3339();
|
||||
let amount: MajorCurrencyAmount = d.amount.clone().into();
|
||||
let amount = guard.attempt_convert_to_display_dec_coin(d.amount.clone().into())?;
|
||||
|
||||
let record = DelegationRecord {
|
||||
amount: amount.clone(),
|
||||
block_height: d.block_height,
|
||||
@@ -193,14 +203,16 @@ pub async fn get_all_mix_delegations(
|
||||
let entry = map
|
||||
.entry(d.node_identity.clone())
|
||||
.or_insert(DelegationWithHistory {
|
||||
delegation: d.try_into()?,
|
||||
delegation: Delegation::from_mixnet_contract(d, reg)?,
|
||||
history: vec![],
|
||||
amount_sum: MajorCurrencyAmount::zero(&amount.denom),
|
||||
amount_sum: DecCoin::zero(display_mix_denom),
|
||||
uses_vesting_contract_tokens: false,
|
||||
});
|
||||
|
||||
debug_assert_eq!(entry.amount_sum.denom, amount.denom);
|
||||
|
||||
entry.history.push(record);
|
||||
entry.amount_sum = entry.amount_sum.clone() + amount;
|
||||
entry.amount_sum.amount += amount.amount;
|
||||
entry.uses_vesting_contract_tokens =
|
||||
entry.uses_vesting_contract_tokens || entry.delegation.proxy.is_some();
|
||||
}
|
||||
@@ -229,25 +241,25 @@ pub async fn get_all_mix_delegations(
|
||||
.iter()
|
||||
.find(|m| m.mix_node.identity_key == node_identity);
|
||||
|
||||
let pledge_amount: Option<MajorCurrencyAmount> =
|
||||
mixnode.and_then(|m| m.pledge_amount.clone().try_into().ok());
|
||||
let pledge_amount = mixnode
|
||||
.map(|m| guard.attempt_convert_to_display_dec_coin(m.pledge_amount.clone().into()))
|
||||
.transpose()?;
|
||||
|
||||
let total_delegation: Option<MajorCurrencyAmount> =
|
||||
mixnode.and_then(|m| m.total_delegation.clone().try_into().ok());
|
||||
let total_delegation = mixnode
|
||||
.map(|m| guard.attempt_convert_to_display_dec_coin(m.total_delegation.clone().into()))
|
||||
.transpose()?;
|
||||
|
||||
let profit_margin_percent: Option<u8> = mixnode.map(|m| m.mix_node.profit_margin_percent);
|
||||
|
||||
log::trace!(" >>> Get accumulated rewards: address = {}", address);
|
||||
let accumulated_rewards = match nymd_client!(state)
|
||||
.get_delegator_rewards(address.clone(), node_identity.clone(), proxy.clone())
|
||||
let accumulated_rewards = match client
|
||||
.nymd
|
||||
.get_delegator_rewards(address.to_string(), node_identity.clone(), proxy.clone())
|
||||
.await
|
||||
{
|
||||
Ok(rewards) => {
|
||||
let reward = CosmWasmCoin {
|
||||
denom: denom_minor.to_string(),
|
||||
amount: rewards,
|
||||
};
|
||||
let amount = MajorCurrencyAmount::from(reward);
|
||||
let reward = Coin::new(rewards.u128(), base_mix_denom);
|
||||
let amount = guard.attempt_convert_to_display_dec_coin(reward)?;
|
||||
log::trace!(" <<< rewards = {}, amount = {}", rewards, amount);
|
||||
Some(amount)
|
||||
}
|
||||
@@ -338,40 +350,52 @@ pub async fn get_delegator_rewards(
|
||||
address: String,
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(
|
||||
">>> Get delegator rewards: mix_identity = {}, proxy = {:?}",
|
||||
mix_identity,
|
||||
proxy
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let network = guard.current_network();
|
||||
let denom = network.base_mix_denom();
|
||||
let reward_amount = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.get_delegator_rewards(address, mix_identity, proxy)
|
||||
.await?;
|
||||
let coin = CosmWasmCoin::new(res.u128(), denom_minor.as_ref());
|
||||
let amount = coin.into();
|
||||
log::info!(">>> res = {}, amount = {}", res, amount);
|
||||
Ok(amount)
|
||||
let base_coin = Coin::new(reward_amount.u128(), denom);
|
||||
let display_coin: DecCoin = guard.attempt_convert_to_display_dec_coin(base_coin.clone())?;
|
||||
|
||||
log::info!(
|
||||
"<<< rewards_base = {}, rewards_display = {}",
|
||||
base_coin,
|
||||
display_coin
|
||||
);
|
||||
Ok(display_coin)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_delegation_summary(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DelegationsSummaryResponse, BackendError> {
|
||||
log::info!(">>> Get delegation summary");
|
||||
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let denom: CurrencyDenom = denom_minor.clone().try_into()?;
|
||||
let guard = state.read().await;
|
||||
let network = guard.current_network();
|
||||
let display_mix_denom = network.display_mix_denom();
|
||||
|
||||
let delegations = get_all_mix_delegations(state.clone()).await?;
|
||||
let mut total_delegations = MajorCurrencyAmount::zero(&denom);
|
||||
let mut total_rewards = MajorCurrencyAmount::zero(&denom);
|
||||
let mut total_delegations = DecCoin::zero(display_mix_denom);
|
||||
let mut total_rewards = DecCoin::zero(display_mix_denom);
|
||||
|
||||
for d in delegations.clone() {
|
||||
total_delegations = total_delegations + d.amount;
|
||||
if let Some(rewards) = d.accumulated_rewards {
|
||||
total_rewards = total_rewards + rewards;
|
||||
for d in &delegations {
|
||||
debug_assert_eq!(d.amount.denom, display_mix_denom);
|
||||
total_delegations.amount += d.amount.amount;
|
||||
if let Some(rewards) = &d.accumulated_rewards {
|
||||
debug_assert_eq!(rewards.denom, display_mix_denom);
|
||||
total_rewards.amount += rewards.amount;
|
||||
}
|
||||
}
|
||||
log::info!(
|
||||
@@ -391,7 +415,7 @@ pub async fn get_delegation_summary(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_all_pending_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
log::info!(">>> Get all pending delegation events");
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use nym_wallet_types::epoch::Epoch;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_current_epoch(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Epoch, BackendError> {
|
||||
log::info!(">>> Get curren epoch");
|
||||
let interval = nymd_client!(state).get_current_epoch().await?;
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use crate::vesting::rewards::{vesting_claim_delegator_reward, vesting_compound_delegator_reward};
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn claim_operator_reward(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
// TODO: handle operator bonding with vesting contract
|
||||
log::info!(">>> Claim operator reward");
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_claim_operator_reward(fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn compound_operator_reward(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
// TODO: handle operator bonding with vesting contract
|
||||
log::info!(">>> Compound operator reward");
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_compound_operator_reward(fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -50,21 +52,23 @@ pub async fn compound_operator_reward(
|
||||
pub async fn claim_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(
|
||||
">>> Claim delegator reward: identity_key = {}",
|
||||
mix_identity
|
||||
);
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_claim_delegator_reward(mix_identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -72,21 +76,23 @@ pub async fn claim_delegator_reward(
|
||||
pub async fn compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(
|
||||
">>> Compound delegator reward: identity_key = {}",
|
||||
mix_identity
|
||||
);
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_compound_delegator_reward(mix_identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -94,7 +100,7 @@ pub async fn compound_delegator_reward(
|
||||
pub async fn claim_locked_and_unlocked_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<TransactionExecuteResult>, BackendError> {
|
||||
log::info!(
|
||||
">>> Claim delegator reward (locked and unlocked): identity_key = {}",
|
||||
@@ -140,7 +146,7 @@ pub async fn claim_locked_and_unlocked_delegator_reward(
|
||||
pub async fn compound_locked_and_unlocked_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<TransactionExecuteResult>, BackendError> {
|
||||
log::info!(
|
||||
">>> Compound delegator reward (locked and unlocked): identity_key = {}",
|
||||
|
||||
@@ -1,46 +1,43 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use crate::state::WalletState;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::transaction::{SendTxResult, TransactionDetails};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{AccountId, Fee};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn send(
|
||||
address: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
memo: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<SendTxResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let address = AccountId::from_str(address)?;
|
||||
let from_address = nymd_client!(state).address().to_string();
|
||||
let amount2 = amount.clone().into();
|
||||
let guard = state.read().await;
|
||||
let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?;
|
||||
|
||||
let to_address = AccountId::from_str(address)?;
|
||||
let from_address = guard.current_client()?.nymd.address().to_string();
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Send: amount = {}, minor_amount = {:?}, from = {}, to = {}, fee = {:?}",
|
||||
">>> Send: display_amount = {}, base_amount = {}, from = {}, to = {}, fee = {:?}",
|
||||
amount,
|
||||
amount2,
|
||||
amount_base,
|
||||
from_address,
|
||||
address.as_ref(),
|
||||
to_address,
|
||||
fee,
|
||||
);
|
||||
let raw_res = nymd_client!(state)
|
||||
.send(&address, vec![amount2], memo, fee)
|
||||
let raw_res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.send(&to_address, vec![amount_base], memo, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", raw_res.hash.to_string());
|
||||
let res = SendTxResult::new(
|
||||
raw_res,
|
||||
TransactionDetails {
|
||||
from_address,
|
||||
to_address: address.to_string(),
|
||||
amount,
|
||||
},
|
||||
denom_minor.as_ref(),
|
||||
)?;
|
||||
TransactionDetails::new(amount, from_address, to_address.to_string()),
|
||||
fee_amount,
|
||||
);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -3,17 +3,14 @@
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::simulate::detailed_fee;
|
||||
use crate::State;
|
||||
use crate::WalletState;
|
||||
use mixnet_contract_common::{ContractStateParams, ExecuteMsg};
|
||||
use nym_wallet_types::admin::TauriContractStateParams;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_update_contract_settings(
|
||||
params: TauriContractStateParams,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
|
||||
@@ -28,5 +25,5 @@ pub async fn simulate_update_contract_settings(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
@@ -3,24 +3,22 @@
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::simulate::detailed_fee;
|
||||
use crate::state::State;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use crate::state::WalletState;
|
||||
use nym_types::currency::DecCoin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{AccountId, MsgSend};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_send(
|
||||
address: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?;
|
||||
|
||||
let to_address = AccountId::from_str(address)?;
|
||||
let amount = vec![amount.into()];
|
||||
let amount = vec![amount_base.into()];
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let from_address = client.nymd.address().clone();
|
||||
@@ -33,5 +31,5 @@ pub async fn simulate_send(
|
||||
};
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
@@ -3,23 +3,20 @@
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::simulate::detailed_fee;
|
||||
use crate::State;
|
||||
use crate::WalletState;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use mixnet_contract_common::{ExecuteMsg, Gateway, MixNode};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use nym_types::currency::DecCoin;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into();
|
||||
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -35,12 +32,12 @@ pub async fn simulate_bond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_gateway(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
@@ -53,18 +50,18 @@ pub async fn simulate_unbond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into();
|
||||
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -79,12 +76,12 @@ pub async fn simulate_bond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_unbond_mixnode(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
@@ -97,13 +94,13 @@ pub async fn simulate_unbond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
@@ -118,17 +115,17 @@ pub async fn simulate_update_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let delegation = amount.into();
|
||||
let delegation = guard.attempt_convert_to_base_coin(amount)?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -142,15 +139,14 @@ pub async fn simulate_delegate_to_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
println!("Called");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
let mixnet_contract = client.nymd.mixnet_contract_address();
|
||||
@@ -164,53 +160,57 @@ pub async fn simulate_undelegate_from_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_claim_operator_reward(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client.nymd.simulate_claim_operator_reward(None).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_compound_operator_reward(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client.nymd.simulate_compound_operator_reward(None).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_claim_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_claim_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_compound_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
@@ -3,29 +3,15 @@
|
||||
|
||||
use cosmrs::tx;
|
||||
use cosmrs::tx::Gas;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::fees::FeeDetails;
|
||||
use validator_client::nymd::cosmwasm_client::types::{GasInfo, SimulateResponse};
|
||||
use validator_client::nymd::{
|
||||
CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice, SigningNymdClient,
|
||||
};
|
||||
use validator_client::Client;
|
||||
use validator_client::nymd::cosmwasm_client::types::GasInfo;
|
||||
use validator_client::nymd::{CosmosCoin, Fee, GasAdjustable, GasAdjustment, GasPrice};
|
||||
|
||||
pub mod admin;
|
||||
pub mod cosmos;
|
||||
pub mod mixnet;
|
||||
pub mod vesting;
|
||||
|
||||
pub(crate) fn detailed_fee(
|
||||
client: &Client<SigningNymdClient>,
|
||||
simulate_response: SimulateResponse,
|
||||
) -> FeeDetails {
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let gas_adjustment = client.nymd.gas_adjustment();
|
||||
|
||||
SimulateResult::new(simulate_response.gas_info, gas_price, gas_adjustment).detailed_fee()
|
||||
}
|
||||
|
||||
// technically we could have also exposed a result: Option<AbciResult> field from the SimulateResponse,
|
||||
// but in the context of the wallet it's really irrelevant and useless for the time being
|
||||
pub(crate) struct SimulateResult {
|
||||
@@ -50,24 +36,16 @@ impl SimulateResult {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detailed_fee(&self) -> FeeDetails {
|
||||
let amount = self.to_fee_amount().map(MajorCurrencyAmount::from);
|
||||
FeeDetails {
|
||||
amount,
|
||||
fee: self.to_fee(),
|
||||
}
|
||||
}
|
||||
|
||||
fn adjusted_gas(&self) -> Option<Gas> {
|
||||
pub(crate) fn adjusted_gas(&self) -> Option<Gas> {
|
||||
self.gas_info
|
||||
.map(|gas_info| gas_info.gas_used.adjust_gas(self.gas_adjustment))
|
||||
}
|
||||
|
||||
fn to_fee_amount(&self) -> Option<CosmosCoin> {
|
||||
pub(crate) fn to_fee_amount(&self) -> Option<CosmosCoin> {
|
||||
self.adjusted_gas().map(|gas| &self.gas_price * gas)
|
||||
}
|
||||
|
||||
fn to_fee(&self) -> Fee {
|
||||
pub(crate) fn to_fee(&self) -> Fee {
|
||||
self.adjusted_gas()
|
||||
.map(|gas| {
|
||||
let fee_amount = &self.gas_price * gas;
|
||||
|
||||
@@ -3,24 +3,21 @@
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::simulate::FeeDetails;
|
||||
use crate::simulate::detailed_fee;
|
||||
use crate::State;
|
||||
use crate::WalletState;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use mixnet_contract_common::{Gateway, MixNode};
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use nym_types::currency::DecCoin;
|
||||
use vesting_contract_common::ExecuteMsg;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
owner_signature: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into();
|
||||
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -30,18 +27,18 @@ pub async fn simulate_vesting_bond_gateway(
|
||||
&ExecuteMsg::BondGateway {
|
||||
gateway,
|
||||
owner_signature,
|
||||
amount: pledge,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_unbond_gateway(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
@@ -55,18 +52,18 @@ pub async fn simulate_vesting_unbond_gateway(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
pledge: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let pledge = pledge.into();
|
||||
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -76,18 +73,18 @@ pub async fn simulate_vesting_bond_mixnode(
|
||||
&ExecuteMsg::BondMixnode {
|
||||
mix_node: mixnode,
|
||||
owner_signature,
|
||||
amount: pledge,
|
||||
amount: pledge.into(),
|
||||
},
|
||||
vec![],
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_unbond_mixnode(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
@@ -101,13 +98,13 @@ pub async fn simulate_vesting_unbond_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
@@ -123,17 +120,17 @@ pub async fn simulate_vesting_update_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into();
|
||||
let amount = guard.attempt_convert_to_base_coin(amount)?.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -148,13 +145,13 @@ pub async fn simulate_vesting_delegate_to_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
|
||||
@@ -170,16 +167,16 @@ pub async fn simulate_vesting_undelegate_from_mixnode(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_withdraw_vested_coins(
|
||||
amount: MajorCurrencyAmount,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
amount: DecCoin,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let amount = amount.into();
|
||||
let amount = guard.attempt_convert_to_base_coin(amount)?.into();
|
||||
|
||||
let client = guard.current_client()?;
|
||||
let vesting_contract = client.nymd.vesting_contract_address();
|
||||
@@ -191,59 +188,63 @@ pub async fn simulate_withdraw_vested_coins(
|
||||
)?;
|
||||
|
||||
let result = client.nymd.simulate(vec![msg]).await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_claim_operator_reward(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_vesting_claim_operator_reward(None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_compound_operator_reward(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_vesting_compound_operator_reward(None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_claim_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_vesting_claim_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn simulate_vesting_compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let result = client
|
||||
.nymd
|
||||
.simulate_vesting_compound_delegator_reward(mix_identity, None)
|
||||
.await?;
|
||||
Ok(detailed_fee(client, result))
|
||||
guard.create_detailed_fee(result)
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
use crate::api_client;
|
||||
use crate::error::BackendError;
|
||||
use crate::state::State;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use crate::state::WalletState;
|
||||
use validator_client::models::{
|
||||
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
@@ -15,7 +13,7 @@ use validator_client::models::{
|
||||
pub async fn mixnode_core_node_status(
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<CoreNodeStatusResponse, BackendError> {
|
||||
Ok(api_client!(state)
|
||||
.get_mixnode_core_status_count(identity, since)
|
||||
@@ -26,7 +24,7 @@ pub async fn mixnode_core_node_status(
|
||||
pub async fn gateway_core_node_status(
|
||||
identity: &str,
|
||||
since: Option<i64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<CoreNodeStatusResponse, BackendError> {
|
||||
Ok(api_client!(state)
|
||||
.get_gateway_core_status_count(identity, since)
|
||||
@@ -36,7 +34,7 @@ pub async fn gateway_core_node_status(
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_status(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<MixnodeStatusResponse, BackendError> {
|
||||
Ok(api_client!(state).get_mixnode_status(identity).await?)
|
||||
}
|
||||
@@ -44,7 +42,7 @@ pub async fn mixnode_status(
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_reward_estimation(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<RewardEstimationResponse, BackendError> {
|
||||
Ok(api_client!(state)
|
||||
.get_mixnode_reward_estimation(identity)
|
||||
@@ -54,7 +52,7 @@ pub async fn mixnode_reward_estimation(
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_stake_saturation(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<StakeSaturationResponse, BackendError> {
|
||||
Ok(api_client!(state)
|
||||
.get_mixnode_stake_saturation(identity)
|
||||
@@ -64,7 +62,7 @@ pub async fn mixnode_stake_saturation(
|
||||
#[tauri::command]
|
||||
pub async fn mixnode_inclusion_probability(
|
||||
identity: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<InclusionProbabilityResponse, BackendError> {
|
||||
Ok(api_client!(state)
|
||||
.get_mixnode_inclusion_probability(identity)
|
||||
|
||||
@@ -1,48 +1,50 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use crate::{Gateway, MixNode};
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_bond_gateway(
|
||||
gateway: Gateway,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
owner_signature: String,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
let guard = state.read().await;
|
||||
let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Bond gateway with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
gateway.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge_minor, fee)
|
||||
">>> Bond gateway with locked tokens: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}",
|
||||
gateway.identity_key,
|
||||
pledge,
|
||||
pledge_base,
|
||||
fee,
|
||||
);
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_bond_gateway(gateway, &owner_signature, pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_gateway(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Unbond gateway bonded with locked tokens, fee = {:?}",
|
||||
fee
|
||||
@@ -51,8 +53,7 @@ pub async fn vesting_unbond_gateway(
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -60,71 +61,81 @@ pub async fn vesting_unbond_gateway(
|
||||
pub async fn vesting_bond_mixnode(
|
||||
mixnode: MixNode,
|
||||
owner_signature: String,
|
||||
pledge: MajorCurrencyAmount,
|
||||
pledge: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let pledge_minor = pledge.clone().into();
|
||||
let guard = state.read().await;
|
||||
let pledge_base = guard.attempt_convert_to_base_coin(pledge.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Bond mixnode with locked tokens: identity_key = {}, pledge = {}, pledge_minor = {}, fee = {:?}",
|
||||
">>> Bond mixnode with locked tokens: identity_key = {}, pledge_display = {}, pledge_base = {}, fee = {:?}",
|
||||
mixnode.identity_key,
|
||||
pledge,
|
||||
pledge_minor,
|
||||
pledge_base,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge_minor, fee)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_bond_mixnode(mixnode, &owner_signature, pledge_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_unbond_mixnode(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Unbond mixnode bonded with locked tokens, fee = {:?}",
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state).vesting_unbond_mixnode(fee).await?;
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_unbond_mixnode(fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn withdraw_vested_coins(
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let amount_minor = amount.clone().into();
|
||||
let guard = state.read().await;
|
||||
let amount_base = guard.attempt_convert_to_base_coin(amount.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Withdraw vested liquid coins: amount = {}, amount_minor = {}, fee = {:?}",
|
||||
">>> Withdraw vested liquid coins: amount_base = {}, amount_base = {}, fee = {:?}",
|
||||
amount,
|
||||
amount_minor,
|
||||
amount_base,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
.withdraw_vested_coins(amount_minor, fee)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.withdraw_vested_coins(amount_base, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -132,21 +143,23 @@ pub async fn withdraw_vested_coins(
|
||||
pub async fn vesting_update_mixnode(
|
||||
profit_margin_percent: u8,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Update mixnode bonded with locked tokens: profit_margin_percent = {}, fee = {:?}",
|
||||
profit_margin_percent,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_update_mixnode_config(profit_margin_percent, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::delegation::{from_contract_delegation_events, DelegationEvent};
|
||||
use crate::error::BackendError;
|
||||
use crate::state::WalletState;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::delegation::DelegationEvent;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use validator_client::nymd::{Fee, VestingSigningClient};
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_pending_vesting_delegation_events(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Vec<DelegationEvent>, BackendError> {
|
||||
log::info!(">>> Get pending delegations from vesting contract");
|
||||
|
||||
let guard = state.read().await;
|
||||
let reg = guard.registered_coins()?;
|
||||
let client = &guard.current_client()?.nymd;
|
||||
let vesting_contract = client.vesting_contract_address();
|
||||
|
||||
@@ -31,36 +26,39 @@ pub async fn get_pending_vesting_delegation_events(
|
||||
log::info!("<<< {} events", events.len());
|
||||
log::trace!("<<< {:?}", events);
|
||||
|
||||
match from_contract_delegation_events(events) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
Ok(events
|
||||
.into_iter()
|
||||
.map(|event| DelegationEvent::from_mixnet_contract(event, reg))
|
||||
.collect::<Result<_, _>>()?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_delegate_to_mixnode(
|
||||
identity: &str,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let delegation = amount.clone().into();
|
||||
let guard = state.read().await;
|
||||
let delegation = guard.attempt_convert_to_base_coin(amount.clone())?;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
|
||||
log::info!(
|
||||
">>> Delegate to mixnode with locked tokens: identity_key = {}, amount = {}, minor_amount = {}, fee = {:?}",
|
||||
">>> Delegate to mixnode with locked tokens: identity_key = {}, amount_display = {}, amount_base = {}, fee = {:?}",
|
||||
identity,
|
||||
amount,
|
||||
delegation,
|
||||
fee
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_delegate_to_mixnode(identity, delegation, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -68,21 +66,23 @@ pub async fn vesting_delegate_to_mixnode(
|
||||
pub async fn vesting_undelegate_from_mixnode(
|
||||
identity: &str,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
log::info!(
|
||||
">>> Undelegate from mixnode delegated with locked tokens: identity_key = {}, fee = {:?}",
|
||||
identity,
|
||||
fee,
|
||||
);
|
||||
let res = nymd_client!(state)
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_undelegate_from_mixnode(identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,92 +1,106 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::WalletState;
|
||||
use cosmwasm_std::Timestamp;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_types::vesting::VestingAccountInfo;
|
||||
use nym_types::vesting::{OriginalVestingResponse, PledgeData};
|
||||
use validator_client::nymd::VestingQueryClient;
|
||||
use vesting_contract_common::Period;
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn locked_coins(
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query locked coins");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let res = client
|
||||
.nymd
|
||||
.locked_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
client.nymd.address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< locked coins = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< locked coins = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn spendable_coins(
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query spendable coins");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let res = client
|
||||
.nymd
|
||||
.spendable_coins(
|
||||
nymd_client!(state).address().as_ref(),
|
||||
client.nymd.address().as_ref(),
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< spendable coins = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable coins = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vested_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query vested coins");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vested_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< vested coins = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< vested coins = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_coins(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query vesting coins");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< vesting coins = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< vesting coins = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_start_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<u64, BackendError> {
|
||||
log::info!(">>> Query vesting start time");
|
||||
let res = nymd_client!(state)
|
||||
@@ -100,7 +114,7 @@ pub async fn vesting_start_time(
|
||||
#[tauri::command]
|
||||
pub async fn vesting_end_time(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<u64, BackendError> {
|
||||
log::info!(">>> Query vesting end time");
|
||||
let res = nymd_client!(state)
|
||||
@@ -114,13 +128,19 @@ pub async fn vesting_end_time(
|
||||
#[tauri::command]
|
||||
pub async fn original_vesting(
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<OriginalVestingResponse, BackendError> {
|
||||
log::info!(">>> Query original vesting");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let reg = guard.registered_coins()?;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.original_vesting(vesting_account_address)
|
||||
.await?
|
||||
.try_into()?;
|
||||
.await?;
|
||||
|
||||
let res = OriginalVestingResponse::from_vesting_contract(res, reg)?;
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
@@ -129,18 +149,23 @@ pub async fn original_vesting(
|
||||
pub async fn delegated_free(
|
||||
vesting_account_address: &str,
|
||||
block_time: Option<u64>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query delegated free");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.delegated_free(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< delegated free = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< delegated free = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
/// Returns the total amount of delegated tokens that have vested
|
||||
@@ -148,30 +173,42 @@ pub async fn delegated_free(
|
||||
pub async fn delegated_vesting(
|
||||
block_time: Option<u64>,
|
||||
vesting_account_address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query delegated vesting");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.delegated_vesting(
|
||||
vesting_account_address,
|
||||
block_time.map(Timestamp::from_seconds),
|
||||
)
|
||||
.await?
|
||||
.into();
|
||||
log::info!("<<< delegated_vesting = {}", res);
|
||||
Ok(res)
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< delegated_vesting = {}", display);
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_get_mixnode_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
log::info!(">>> Query vesting get mixnode pledge");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let reg = guard.registered_coins()?;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.get_mixnode_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then);
|
||||
.map(|pledge| PledgeData::from_vesting_contract(pledge, reg))
|
||||
.transpose()?;
|
||||
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
@@ -179,13 +216,20 @@ pub async fn vesting_get_mixnode_pledge(
|
||||
#[tauri::command]
|
||||
pub async fn vesting_get_gateway_pledge(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<PledgeData>, BackendError> {
|
||||
log::info!(">>> Query vesting get gateway pledge");
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let reg = guard.registered_coins()?;
|
||||
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.get_gateway_pledge(address)
|
||||
.await?
|
||||
.and_then(PledgeData::and_then);
|
||||
.map(|pledge| PledgeData::from_vesting_contract(pledge, reg))
|
||||
.transpose()?;
|
||||
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
@@ -193,7 +237,7 @@ pub async fn vesting_get_gateway_pledge(
|
||||
#[tauri::command]
|
||||
pub async fn get_current_vesting_period(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Period, BackendError> {
|
||||
log::info!(">>> Query current vesting period");
|
||||
let res = nymd_client!(state)
|
||||
@@ -206,10 +250,15 @@ pub async fn get_current_vesting_period(
|
||||
#[tauri::command]
|
||||
pub async fn get_account_info(
|
||||
address: &str,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<VestingAccountInfo, BackendError> {
|
||||
log::info!(">>> Query account info");
|
||||
let res = nymd_client!(state).get_account(address).await?.try_into()?;
|
||||
let guard = state.read().await;
|
||||
let res = guard.registered_coins()?;
|
||||
|
||||
let vesting_account = guard.current_client()?.nymd.get_account(address).await?;
|
||||
let res = VestingAccountInfo::from_vesting_contract(vesting_account, res)?;
|
||||
|
||||
log::info!("<<< {:?}", res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::state::WalletState;
|
||||
use mixnet_contract_common::IdentityKey;
|
||||
use nym_types::transaction::TransactionExecuteResult;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::Fee;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_claim_operator_reward(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(">>> Vesting account: claim operator reward");
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_vesting_claim_operator_reward(None)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn vesting_compound_operator_reward(
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(">>> Vesting account: compound operator reward");
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_vesting_compound_operator_reward(fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -46,21 +48,23 @@ pub async fn vesting_compound_operator_reward(
|
||||
pub async fn vesting_claim_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(
|
||||
">>> Vesting account: claim delegator reward: identity_key = {}",
|
||||
mix_identity
|
||||
);
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_vesting_claim_delegator_reward(mix_identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -68,20 +72,22 @@ pub async fn vesting_claim_delegator_reward(
|
||||
pub async fn vesting_compound_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
fee: Option<Fee>,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<TransactionExecuteResult, BackendError> {
|
||||
log::info!(
|
||||
">>> Vesting account: compound delegator reward: identity_key = {}",
|
||||
mix_identity
|
||||
);
|
||||
let denom_minor = state.read().await.current_network().base_mix_denom();
|
||||
let res = nymd_client!(state)
|
||||
let guard = state.read().await;
|
||||
let fee_amount = guard.convert_tx_fee(fee.as_ref());
|
||||
let res = guard
|
||||
.current_client()?
|
||||
.nymd
|
||||
.execute_vesting_compound_delegator_reward(mix_identity, fee)
|
||||
.await?;
|
||||
log::info!("<<< tx hash = {}", res.transaction_hash);
|
||||
log::trace!("<<< {:?}", res);
|
||||
Ok(TransactionExecuteResult::from_execute_result(
|
||||
res,
|
||||
denom_minor.as_ref(),
|
||||
res, fee_amount,
|
||||
)?)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
use crate::config;
|
||||
use crate::error::BackendError;
|
||||
use crate::simulate::SimulateResult;
|
||||
use itertools::Itertools;
|
||||
use log::warn;
|
||||
use nym_types::currency::{DecCoin, RegisteredCoins};
|
||||
use nym_types::fees::FeeDetails;
|
||||
use nym_wallet_types::network::Network;
|
||||
use nym_wallet_types::network_config;
|
||||
|
||||
use strum::IntoEnumIterator;
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, SigningNymdClient};
|
||||
use validator_client::Client;
|
||||
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::sync::RwLock;
|
||||
use url::Url;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
use url::Url;
|
||||
use validator_client::nymd::cosmwasm_client::types::SimulateResponse;
|
||||
use validator_client::nymd::{AccountId as CosmosAccountId, Coin, Fee, SigningNymdClient};
|
||||
use validator_client::Client;
|
||||
|
||||
// Some hardcoded metadata overrides
|
||||
static METADATA_OVERRIDES: Lazy<Vec<(Url, ValidatorMetadata)>> = Lazy::new(|| {
|
||||
@@ -28,7 +30,7 @@ static METADATA_OVERRIDES: Lazy<Vec<(Url, ValidatorMetadata)>> = Lazy::new(|| {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn load_config_from_files(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
state.write().await.load_config_files();
|
||||
Ok(())
|
||||
@@ -36,13 +38,30 @@ pub async fn load_config_from_files(
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn save_config_to_files(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<(), BackendError> {
|
||||
state.read().await.save_config_files()
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct WalletState {
|
||||
inner: Arc<RwLock<WalletStateInner>>,
|
||||
}
|
||||
|
||||
impl WalletState {
|
||||
// not the best API, but those are exposed here for backwards compatibility with the existing
|
||||
// state type assumptions so that we wouldn't need to fix it up everywhere at once
|
||||
pub(crate) async fn read(&self) -> RwLockReadGuard<'_, WalletStateInner> {
|
||||
self.inner.read().await
|
||||
}
|
||||
|
||||
pub(crate) async fn write(&self) -> RwLockWriteGuard<'_, WalletStateInner> {
|
||||
self.inner.write().await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
pub struct WalletStateInner {
|
||||
config: config::Config,
|
||||
signing_clients: HashMap<Network, Client<SigningNymdClient>>,
|
||||
current_network: Network,
|
||||
@@ -56,6 +75,7 @@ pub struct State {
|
||||
|
||||
/// We fetch (and cache) some metadata, such as names, when available
|
||||
validator_metadata: HashMap<Url, ValidatorMetadata>,
|
||||
registered_coins: HashMap<Network, RegisteredCoins>,
|
||||
}
|
||||
|
||||
pub(crate) struct WalletAccountIds {
|
||||
@@ -65,7 +85,70 @@ pub(crate) struct WalletAccountIds {
|
||||
pub addresses: HashMap<Network, CosmosAccountId>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
impl WalletStateInner {
|
||||
// note that `Coin` is ALWAYS the base coin
|
||||
pub fn attempt_convert_to_base_coin(&self, coin: DecCoin) -> Result<Coin, BackendError> {
|
||||
let registered_coins = self
|
||||
.registered_coins
|
||||
.get(&self.current_network)
|
||||
.ok_or_else(|| BackendError::UnknownCoinDenom(coin.denom.clone()))?;
|
||||
|
||||
Ok(registered_coins.attempt_convert_to_base_coin(coin)?)
|
||||
}
|
||||
|
||||
pub fn attempt_convert_to_display_dec_coin(&self, coin: Coin) -> Result<DecCoin, BackendError> {
|
||||
let registered_coins = self
|
||||
.registered_coins
|
||||
.get(&self.current_network)
|
||||
.ok_or_else(|| BackendError::UnknownCoinDenom(coin.denom.clone()))?;
|
||||
|
||||
Ok(registered_coins.attempt_convert_to_display_dec_coin(coin)?)
|
||||
}
|
||||
|
||||
pub(crate) fn registered_coins(&self) -> Result<&RegisteredCoins, BackendError> {
|
||||
self.registered_coins
|
||||
.get(&self.current_network)
|
||||
.ok_or(BackendError::NoCoinsRegistered {
|
||||
network: self.current_network,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn convert_tx_fee(&self, fee: Option<&Fee>) -> Option<DecCoin> {
|
||||
let mut fee_amount = fee?.try_get_manual_amount()?;
|
||||
if fee_amount.len() > 1 {
|
||||
warn!(
|
||||
"our tx fee contained more than a single denomination. using the first one for display"
|
||||
)
|
||||
}
|
||||
if fee_amount.is_empty() {
|
||||
warn!("our tx has had an unknown fee set");
|
||||
None
|
||||
} else {
|
||||
self.attempt_convert_to_display_dec_coin(fee_amount.pop().unwrap())
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
// this one is rather gnarly and I'm not 100% sure how to feel about existence of it
|
||||
pub(crate) fn create_detailed_fee(
|
||||
&self,
|
||||
simulate_response: SimulateResponse,
|
||||
) -> Result<FeeDetails, BackendError> {
|
||||
// this MUST succeed as we just used it before
|
||||
let client = self.current_client()?;
|
||||
let gas_price = client.nymd.gas_price().clone();
|
||||
let gas_adjustment = client.nymd.gas_adjustment();
|
||||
|
||||
let res = SimulateResult::new(simulate_response.gas_info, gas_price, gas_adjustment);
|
||||
|
||||
let amount = res
|
||||
.to_fee_amount()
|
||||
.map(|amount| self.attempt_convert_to_display_dec_coin(amount.into()))
|
||||
.transpose()?;
|
||||
|
||||
Ok(FeeDetails::new(amount, res.to_fee()))
|
||||
}
|
||||
|
||||
pub fn client(&self, network: Network) -> Result<&Client<SigningNymdClient>, BackendError> {
|
||||
self.signing_clients
|
||||
.get(&network)
|
||||
@@ -112,6 +195,11 @@ impl State {
|
||||
self.signing_clients.insert(network, client);
|
||||
}
|
||||
|
||||
pub fn register_default_denoms(&mut self, network: Network) {
|
||||
self.registered_coins
|
||||
.insert(network, RegisteredCoins::default_denoms(network.into()));
|
||||
}
|
||||
|
||||
pub fn set_network(&mut self, network: Network) {
|
||||
self.current_network = network;
|
||||
}
|
||||
@@ -390,7 +478,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn adding_validators_urls_prepends() {
|
||||
let mut state = State::default();
|
||||
let mut state = WalletStateInner::default();
|
||||
let _api_urls = state.get_api_urls(Network::MAINNET).collect::<Vec<_>>();
|
||||
|
||||
state.add_validator_url(
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use nym_types::currency::MajorCurrencyAmount;
|
||||
use crate::state::WalletState;
|
||||
use nym_types::currency::DecCoin;
|
||||
use nym_wallet_types::app::AppEnv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use validator_client::nymd::{tx, Coin, CosmosCoin, Gas, GasPrice};
|
||||
|
||||
fn get_env_as_option(key: &str) -> Option<String> {
|
||||
@@ -25,9 +23,7 @@ pub fn get_env() -> AppEnv {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn owns_mixnode(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<bool, BackendError> {
|
||||
pub async fn owns_mixnode(state: tauri::State<'_, WalletState>) -> Result<bool, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
.owns_mixnode(nymd_client!(state).address())
|
||||
.await?
|
||||
@@ -35,9 +31,7 @@ pub async fn owns_mixnode(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn owns_gateway(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<bool, BackendError> {
|
||||
pub async fn owns_gateway(state: tauri::State<'_, WalletState>) -> Result<bool, BackendError> {
|
||||
Ok(nymd_client!(state)
|
||||
.owns_gateway(nymd_client!(state).address())
|
||||
.await?
|
||||
@@ -145,13 +139,14 @@ impl Operation {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_old_and_incorrect_hardcoded_fee(
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
state: tauri::State<'_, WalletState>,
|
||||
operation: Operation,
|
||||
) -> Result<MajorCurrencyAmount, BackendError> {
|
||||
let mut approximate_fee = operation.default_fee(nymd_client!(state).gas_price());
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
let guard = state.read().await;
|
||||
let mut approximate_fee = operation.default_fee(guard.current_client()?.nymd.gas_price());
|
||||
// on all our chains it should only ever contain a single type of currency
|
||||
assert_eq!(approximate_fee.amount.len(), 1);
|
||||
let coin: Coin = approximate_fee.amount.pop().unwrap().into();
|
||||
log::info!("hardcoded fee for {:?} is {:?}", operation, coin);
|
||||
Ok(coin.into())
|
||||
guard.attempt_convert_to_display_dec_coin(coin)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar, FormGroup, FormControlLabel, Switch } from '@mui/material';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Logout } from '@mui/icons-material';
|
||||
import TerminalIcon from '@mui/icons-material/Terminal';
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Logout } from '@mui/icons-material';
|
||||
import TerminalIcon from '@mui/icons-material/Terminal';
|
||||
import ModeNightOutlinedIcon from '@mui/icons-material/ModeNightOutlined';
|
||||
import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar, FormGroup, FormControlLabel, Switch } from '@mui/material';
|
||||
import { AppBar as MuiAppBar, Grid, IconButton, Toolbar } from '@mui/material';
|
||||
import { Node } from 'src/svg-icons/node';
|
||||
import { config } from '../../config';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
@@ -21,7 +21,7 @@ Default.args = {
|
||||
open: true,
|
||||
header: 'Confirm transaction',
|
||||
subheader: 'Confirm and proceed or cancel transaction',
|
||||
fee: { amount: { amount: '0.001', denom: 'NYM' }, fee: { Auto: null } },
|
||||
fee: { amount: { amount: '0.001', denom: 'nym' }, fee: { Auto: null } },
|
||||
onClose: () => {},
|
||||
onConfirm: async () => {},
|
||||
onPrev: () => {},
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ModalDivider } from './Modals/ModalDivider';
|
||||
import { backDropStyles, modalStyles } from '../../.storybook/storiesStyles';
|
||||
|
||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||
!!isStorybook
|
||||
isStorybook
|
||||
? {
|
||||
backdropProps: { ...backDropStyles(theme), ...backdropProps },
|
||||
sx: modalStyles(theme),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { SxProps } from '@mui/material';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
|
||||
export const OverSaturatedBlockerModal: React.FC<{
|
||||
open: boolean;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Box, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { IdentityKeyFormField } from '@nymproject/react/mixnodes/IdentityKeyFormField';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { CurrencyDenom, FeeDetails, DecCoin } from '@nymproject/types';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { simulateDelegateToMixnode, simulateVestingDelegateToMixnode } from 'src/requests';
|
||||
@@ -20,7 +20,7 @@ const MIN_AMOUNT_TO_DELEGATE = 10;
|
||||
export const DelegateModal: React.FC<{
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
onOk?: (identityKey: string, amount: MajorCurrencyAmount, tokenPool: TPoolOption, fee?: FeeDetails) => Promise<void>;
|
||||
onOk?: (identityKey: string, amount: DecCoin, tokenPool: TPoolOption, fee?: FeeDetails) => Promise<void>;
|
||||
identityKey?: string;
|
||||
onIdentityKeyChanged?: (identityKey: string) => void;
|
||||
onAmountChanged?: (amount: string) => void;
|
||||
@@ -31,7 +31,7 @@ export const DelegateModal: React.FC<{
|
||||
estimatedReward?: number;
|
||||
profitMarginPercentage?: number | null;
|
||||
nodeUptimePercentage?: number | null;
|
||||
currency: CurrencyDenom;
|
||||
currency: string;
|
||||
initialAmount?: string;
|
||||
hasVestingContract: boolean;
|
||||
sx?: SxProps;
|
||||
@@ -118,11 +118,11 @@ export const DelegateModal: React.FC<{
|
||||
|
||||
const handleOk = async () => {
|
||||
if (onOk && amount && identityKey) {
|
||||
onOk(identityKey, { amount, denom: currency }, tokenPool, fee);
|
||||
onOk(identityKey, { amount, denom: currency as CurrencyDenom }, tokenPool, fee);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = async ({ identity, value }: { identity: string; value: MajorCurrencyAmount }) => {
|
||||
const handleConfirm = async ({ identity, value }: { identity: string; value: DecCoin }) => {
|
||||
const hasEnoughTokens = await checkTokenBalance(tokenPool, value.amount);
|
||||
|
||||
if (!hasEnoughTokens) {
|
||||
@@ -147,7 +147,7 @@ export const DelegateModal: React.FC<{
|
||||
}
|
||||
};
|
||||
|
||||
const handleAmountChanged = (newAmount: MajorCurrencyAmount) => {
|
||||
const handleAmountChanged = (newAmount: DecCoin) => {
|
||||
setAmount(newAmount.amount);
|
||||
|
||||
if (onAmountChanged) {
|
||||
@@ -181,7 +181,7 @@ export const DelegateModal: React.FC<{
|
||||
onClose={onClose}
|
||||
onOk={async () => {
|
||||
if (identityKey && amount) {
|
||||
handleConfirm({ identity: identityKey, value: { amount, denom: currency } });
|
||||
handleConfirm({ identity: identityKey, value: { amount, denom: currency as CurrencyDenom } });
|
||||
}
|
||||
}}
|
||||
header={header || 'Delegate'}
|
||||
|
||||
@@ -15,31 +15,31 @@ export const items: DelegationWithEverything[] = [
|
||||
{
|
||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
||||
accumulated_rewards: { amount: '0.05', denom: 'NYM' },
|
||||
amount: { amount: '10', denom: 'NYM' },
|
||||
accumulated_rewards: { amount: '0.05', denom: 'nym' },
|
||||
amount: { amount: '10', denom: 'nym' },
|
||||
profit_margin_percent: 0.1122323949234,
|
||||
owner: '',
|
||||
block_height: BigInt(100),
|
||||
stake_saturation: 0.5,
|
||||
avg_uptime_percent: 0.5,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
total_delegation: { amount: '0', denom: 'nym' },
|
||||
pledge_amount: { amount: '0', denom: 'nym' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
uses_vesting_contract_tokens: false,
|
||||
},
|
||||
{
|
||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
||||
accumulated_rewards: { amount: '0.1', denom: 'NYM' },
|
||||
amount: { amount: '100', denom: 'NYM' },
|
||||
accumulated_rewards: { amount: '0.1', denom: 'nym' },
|
||||
amount: { amount: '100', denom: 'nym' },
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
||||
profit_margin_percent: 0.89,
|
||||
owner: '',
|
||||
block_height: BigInt(4000),
|
||||
stake_saturation: 0.5,
|
||||
avg_uptime_percent: 0.1,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
total_delegation: { amount: '0', denom: 'nym' },
|
||||
pledge_amount: { amount: '0', denom: 'nym' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
uses_vesting_contract_tokens: true,
|
||||
|
||||
@@ -193,10 +193,12 @@ export const DelegationList: React.FC<{
|
||||
}
|
||||
arrow
|
||||
>
|
||||
<span style={{ cursor: 'pointer' }}>{`${item.amount.amount} ${item.amount.denom}`}</span>
|
||||
<span
|
||||
style={{ cursor: 'pointer', textTransform: 'uppercase' }}
|
||||
>{`${item.amount.amount} ${item.amount.denom}`}</span>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCell sx={{ textTransform: 'uppercase' }}>
|
||||
{!item.accumulated_rewards
|
||||
? '-'
|
||||
: `${item.accumulated_rewards.amount} ${item.accumulated_rewards.denom}`}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Box, Button, Modal, Typography, SxProps } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { modalStyle } from '../Modals/styles';
|
||||
import { LoadingModal } from '../Modals/LoadingModal';
|
||||
|
||||
@@ -67,7 +68,7 @@ export const DelegationModal: React.FC<
|
||||
<Typography color={(theme) => theme.palette.error.main} mb={1}>
|
||||
Oh no! Something went wrong...
|
||||
</Typography>
|
||||
<Typography my={5} color={'text.primary'}>
|
||||
<Typography my={5} color="text.primary">
|
||||
{message}
|
||||
</Typography>
|
||||
{children}
|
||||
@@ -78,15 +79,15 @@ export const DelegationModal: React.FC<
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
transactions &&
|
||||
transactions.map((transaction) => console.log('action', action, 'status', status, 'key', transaction.hash));
|
||||
|
||||
transactions?.map((transaction) => Console.log('action', action, 'status', status, 'key', transaction.hash));
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} BackdropProps={backdropProps}>
|
||||
<Box sx={{ ...modalStyle, ...sx }} textAlign="center">
|
||||
<Typography color={(theme) => theme.palette.success.main} mb={1}>
|
||||
{actionToHeader(action)}
|
||||
</Typography>
|
||||
<Typography mb={3} color={'text.primary'}>
|
||||
<Typography mb={3} color="text.primary">
|
||||
{message}
|
||||
</Typography>
|
||||
|
||||
@@ -98,15 +99,15 @@ export const DelegationModal: React.FC<
|
||||
{balanceVested ? (
|
||||
<>
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
Your current balance: {balance}
|
||||
Your current balance: {balance?.toUpperCase()}
|
||||
</Typography>
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
({balanceVested} is unlocked in your vesting account)
|
||||
({balanceVested.toUpperCase()} is unlocked in your vesting account)
|
||||
</Typography>
|
||||
</>
|
||||
) : (
|
||||
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
|
||||
Your current balance: {balance}
|
||||
Your current balance: {balance?.toUpperCase()}
|
||||
</Typography>
|
||||
)}
|
||||
{transactions && (
|
||||
|
||||
@@ -61,7 +61,7 @@ export const Delegate = () => {
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
@@ -84,7 +84,7 @@ export const DelegateBelowMinimum = () => {
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
estimatedReward={425.2345053}
|
||||
nodeUptimePercentage={99.28394}
|
||||
profitMarginPercentage={11.12334234}
|
||||
@@ -109,7 +109,7 @@ export const DelegateMore = () => {
|
||||
onOk={async () => setOpen(false)}
|
||||
header="Delegate more"
|
||||
buttonText="Delegate more"
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
estimatedReward={50.423}
|
||||
accountBalance="425.2345053"
|
||||
nodeUptimePercentage={99.28394}
|
||||
@@ -132,7 +132,7 @@ export const Undelegate = () => {
|
||||
open={open}
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
amount={150}
|
||||
identityKey="AA6RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujyxx"
|
||||
usesVestingContractTokens={false}
|
||||
|
||||
@@ -142,7 +142,7 @@ export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl:
|
||||
/>
|
||||
</Tooltip>
|
||||
</TableCell>
|
||||
<TableCell>{!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom}`}</TableCell>
|
||||
<TableCell>{!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom.toUpperCase()}`}</TableCell>
|
||||
<TableCell>
|
||||
{item.kind === 'Delegate' ? 'Delegation' : 'Undelegation'}
|
||||
{item.proxy && (
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import React, { useState, useEffect, useContext } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { Operation } from '@nymproject/types';
|
||||
import { getGasFee } from '../requests';
|
||||
import { AppContext } from '../context/main';
|
||||
|
||||
export const Fee = ({ feeType }: { feeType: Operation }) => {
|
||||
const [fee, setFee] = useState<string>();
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
const res = await getGasFee(feeType);
|
||||
setFee(res.amount);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getFee();
|
||||
}, []);
|
||||
|
||||
if (fee) {
|
||||
return (
|
||||
<Typography sx={{ color: 'nym.fee', fontWeight: 600 }}>
|
||||
Est.fee for this transaction: {`${fee} ${clientDetails?.denom}`}{' '}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -14,7 +14,11 @@ export const ModalListItem: React.FC<{
|
||||
<Typography fontSize="smaller" fontWeight={strong ? 600 : undefined} sx={{ color: 'text.primary' }}>
|
||||
{label}:
|
||||
</Typography>
|
||||
<Typography fontSize="smaller" fontWeight={strong ? 600 : undefined} sx={{ color: 'text.primary' }}>
|
||||
<Typography
|
||||
fontSize="smaller"
|
||||
fontWeight={strong ? 600 : undefined}
|
||||
sx={{ color: 'text.primary', textTransform: 'uppercase' }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -61,7 +61,7 @@ export const RedeemAllRewards = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={425.65843}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -82,7 +82,7 @@ export const RedeemRewardForMixnode = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={async () => setOpen(false)}
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={425.65843}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -103,7 +103,7 @@ export const FeeIsMoreThanAllRewards = () => {
|
||||
onClose={() => setOpen(false)}
|
||||
onOk={() => setOpen(false)}
|
||||
message="Redeem all rewards"
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
amount={0.001}
|
||||
{...storybookStyles(theme)}
|
||||
@@ -125,7 +125,7 @@ export const FeeIsMoreThanMixnodeReward = () => {
|
||||
onOk={async () => setOpen(false)}
|
||||
identityKey="D88RfeY8DttMD3CQKoayV6mss5a5FC3RoH75Kmcujaaa"
|
||||
message="Redeem rewards"
|
||||
currency="NYM"
|
||||
currency="nym"
|
||||
amount={0.001}
|
||||
{...storybookStyles(theme)}
|
||||
usesVestingTokens={false}
|
||||
|
||||
@@ -13,13 +13,13 @@ export const RewardsSummary: React.FC<{
|
||||
<Stack direction="row" spacing={4}>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography>Total delegations:</Typography>
|
||||
<Typography fontWeight={600} fontSize={16}>
|
||||
<Typography fontWeight={600} fontSize={16} textTransform="uppercase">
|
||||
{isLoading ? <CircularProgress size={theme.typography.fontSize} /> : totalDelegation || '-'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={2}>
|
||||
<Typography>New rewards:</Typography>
|
||||
<Typography fontWeight={600} fontSize={16}>
|
||||
<Typography fontWeight={600} fontSize={16} textTransform="uppercase">
|
||||
{isLoading ? <CircularProgress size={theme.typography.fontSize} /> : totalRewards || '-'}
|
||||
</Typography>
|
||||
</Stack>
|
||||
|
||||
@@ -45,8 +45,8 @@ export const SendDetails = () => {
|
||||
<SendDetailsModal
|
||||
fromAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
||||
toAddress="nymt1w8qp7zsxggvtxhpqpt6e329j42wtv07dm5ts8u"
|
||||
fee={{ amount: { amount: '0.01', denom: 'NYM' }, fee: { Auto: null } }}
|
||||
amount={{ amount: '100', denom: 'NYM' }}
|
||||
fee={{ amount: { amount: '0.01', denom: 'nym' }, fee: { Auto: null } }}
|
||||
amount={{ amount: '100', denom: 'nym' }}
|
||||
onPrev={() => {}}
|
||||
onSend={() => {}}
|
||||
onClose={() => {}}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Stack } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { FeeDetails, DecCoin } from '@nymproject/types';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
|
||||
@@ -19,10 +19,10 @@ export const SendDetailsModal = ({
|
||||
fromAddress?: string;
|
||||
toAddress: string;
|
||||
fee?: FeeDetails;
|
||||
amount?: MajorCurrencyAmount;
|
||||
amount?: DecCoin;
|
||||
onClose: () => void;
|
||||
onPrev: () => void;
|
||||
onSend: (data: { val: MajorCurrencyAmount; to: string }) => void;
|
||||
onSend: (data: { val: DecCoin; to: string }) => void;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
}) => (
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import { Stack, TextField, Typography } from '@mui/material';
|
||||
import { SxProps } from '@mui/system';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { DecCoin } from '@nymproject/types';
|
||||
import { validateAmount } from 'src/utils';
|
||||
import { SimpleModal } from '../Modals/SimpleModal';
|
||||
import { ModalListItem } from '../Modals/ModalListItem';
|
||||
@@ -22,19 +22,19 @@ export const SendInputModal = ({
|
||||
}: {
|
||||
fromAddress?: string;
|
||||
toAddress: string;
|
||||
amount?: MajorCurrencyAmount;
|
||||
amount?: DecCoin;
|
||||
balance?: string;
|
||||
error?: string;
|
||||
onNext: () => void;
|
||||
onClose: () => void;
|
||||
onAmountChange: (value: MajorCurrencyAmount) => void;
|
||||
onAmountChange: (value: DecCoin) => void;
|
||||
onAddressChange: (value: string) => void;
|
||||
sx?: SxProps;
|
||||
backdropProps?: object;
|
||||
}) => {
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const validate = async (value: MajorCurrencyAmount) => {
|
||||
const validate = async (value: DecCoin) => {
|
||||
const isValidAmount = await validateAmount(value.amount, '0');
|
||||
setIsValid(isValidAmount);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { DecCoin } from '@nymproject/types';
|
||||
import { AppContext, urls } from 'src/context';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { send } from 'src/requests';
|
||||
@@ -14,14 +14,14 @@ import { TTransactionDetails } from './types';
|
||||
|
||||
export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void; hasStorybookStyles?: {} }) => {
|
||||
const [toAddress, setToAddress] = useState<string>('');
|
||||
const [amount, setAmount] = useState<MajorCurrencyAmount>();
|
||||
const [amount, setAmount] = useState<DecCoin>();
|
||||
const [modal, setModal] = useState<'send' | 'send details'>('send');
|
||||
const [error, setError] = useState<string>();
|
||||
const [sendError, setSendError] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [txDetails, setTxDetails] = useState<TTransactionDetails>();
|
||||
|
||||
const { clientDetails, userBalance, network } = useContext(AppContext);
|
||||
const { clientDetails, userBalance, network, denom } = useContext(AppContext);
|
||||
const { fee, getFee } = useGetFee();
|
||||
|
||||
const handleOnNext = async () => {
|
||||
@@ -41,13 +41,13 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
|
||||
}
|
||||
};
|
||||
|
||||
const handleSend = async ({ val, to }: { val: MajorCurrencyAmount; to: string }) => {
|
||||
const handleSend = async ({ val, to }: { val: DecCoin; to: string }) => {
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
try {
|
||||
const txResponse = await send({ amount: val, address: to, memo: '', fee: fee?.fee });
|
||||
setTxDetails({
|
||||
amount: `${amount?.amount} ${clientDetails?.denom}`,
|
||||
amount: `${amount?.amount} ${denom}`,
|
||||
txUrl: `${urls(network).blockExplorer}/transaction/${txResponse.tx_hash}`,
|
||||
});
|
||||
} catch (e) {
|
||||
|
||||
@@ -7,7 +7,13 @@ export const SuccessReponse: React.FC<{
|
||||
caption: string | React.ReactNode;
|
||||
}> = ({ title, subtitle, caption }) => (
|
||||
<Stack spacing={3} alignItems="center" sx={{ mb: 5 }}>
|
||||
<Typography variant="h5" fontWeight="600" data-testid="transaction-complete" color="success.main">
|
||||
<Typography
|
||||
variant="h5"
|
||||
fontWeight="600"
|
||||
data-testid="transaction-complete"
|
||||
color="success.main"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
<Typography fontWeight="600">{subtitle}</Typography>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T
|
||||
const [value, setValue] = useState<TPoolOption>('balance');
|
||||
const {
|
||||
userBalance: { tokenAllocation, balance, fetchBalance, fetchTokenAllocation },
|
||||
clientDetails,
|
||||
denom,
|
||||
} = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -48,7 +48,8 @@ export const TokenPoolSelector: React.FC<{ disabled: boolean; onSelect: (pool: T
|
||||
{tokenAllocation && (
|
||||
<ListItemText
|
||||
primary="Locked"
|
||||
secondary={`${+tokenAllocation.locked + +tokenAllocation.spendable} ${clientDetails?.denom}`}
|
||||
secondary={`${+tokenAllocation.locked + +tokenAllocation.spendable} ${denom}`}
|
||||
sx={{ textTransform: 'uppercase' }}
|
||||
/>
|
||||
)}
|
||||
</MenuItem>
|
||||
|
||||
@@ -3,7 +3,6 @@ export * from './ClientAddress';
|
||||
export * from './ConfirmPassword';
|
||||
export * from './CopyToClipboard';
|
||||
export * from './ErrorFallback';
|
||||
export * from './Fee';
|
||||
export * from './InfoToolTip';
|
||||
export * from './LoadingPage';
|
||||
export * from './Mnemonic';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
DelegationEvent,
|
||||
DelegationWithEverything,
|
||||
FeeDetails,
|
||||
MajorCurrencyAmount,
|
||||
DecCoin,
|
||||
TransactionExecuteResult,
|
||||
} from '@nymproject/types';
|
||||
import type { Network } from 'src/types';
|
||||
@@ -20,7 +20,7 @@ export type TDelegationContext = {
|
||||
totalRewards?: string;
|
||||
refresh: () => Promise<void>;
|
||||
addDelegation: (
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
data: { identity: string; amount: DecCoin },
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => Promise<TransactionExecuteResult>;
|
||||
@@ -57,7 +57,7 @@ export const DelegationContextProvider: FC<{
|
||||
const [pendingDelegations, setPendingDelegations] = useState<DelegationEvent[]>();
|
||||
|
||||
const addDelegation = async (
|
||||
data: { identity: string; amount: MajorCurrencyAmount },
|
||||
data: { identity: string; amount: DecCoin },
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { forage } from '@tauri-apps/tauri-forage';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Account, AccountEntry, MixNodeBond } from '@nymproject/types';
|
||||
import { Account, AccountEntry, CurrencyDenom, MixNodeBond } from '@nymproject/types';
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
import { AppEnv, Network } from '../types';
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||
@@ -49,6 +49,7 @@ export type TAppContext = {
|
||||
loginType?: TLoginType;
|
||||
showSettings: boolean;
|
||||
showSendModal: boolean;
|
||||
denom: Uppercase<CurrencyDenom>;
|
||||
handleShowSettings: () => void;
|
||||
handleShowSendModal: () => void;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
@@ -67,6 +68,7 @@ export const AppContext = createContext({} as TAppContext);
|
||||
|
||||
export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [clientDetails, setClientDetails] = useState<Account>();
|
||||
const [denom, setDenom] = useState<Uppercase<CurrencyDenom>>('NYM');
|
||||
const [storedAccounts, setStoredAccounts] = useState<AccountEntry[]>();
|
||||
const [mixnodeDetails, setMixnodeDetails] = useState<MixNodeBond | null>(null);
|
||||
const [network, setNetwork] = useState<Network | undefined>();
|
||||
@@ -99,6 +101,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
try {
|
||||
const client = await selectNetwork(n);
|
||||
setClientDetails(client);
|
||||
setDenom(client.display_mix_denom.toUpperCase() as Uppercase<CurrencyDenom>);
|
||||
} catch (e) {
|
||||
enqueueSnackbar('Error loading account', { variant: 'error' });
|
||||
Console.error(e as string);
|
||||
@@ -136,10 +139,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const setModeInStorage = async (mode: 'light' | 'dark') => {
|
||||
const setModeInStorage = async (newMode: 'light' | 'dark') => {
|
||||
await forage.setItem({
|
||||
key: 'nym-wallet-mode',
|
||||
value: mode,
|
||||
value: newMode,
|
||||
})();
|
||||
};
|
||||
|
||||
@@ -253,6 +256,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
storedAccounts,
|
||||
mixnodeDetails,
|
||||
userBalance,
|
||||
denom,
|
||||
showAdmin,
|
||||
showTerminal,
|
||||
showSettings,
|
||||
@@ -290,6 +294,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
showTerminal,
|
||||
showSettings,
|
||||
showSendModal,
|
||||
denom,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { DelegationWithEverything, MajorCurrencyAmount, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { DelegationWithEverything, DecCoin, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { DelegationContext, TDelegationTransaction } from '../delegations';
|
||||
|
||||
import { mockSleep } from './utils';
|
||||
@@ -10,31 +10,31 @@ let mockDelegations: DelegationWithEverything[] = [
|
||||
{
|
||||
node_identity: 'FiojKW7oY9WQmLCiYAsCA21tpowZHS6zcUoyYm319p6Z',
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 1).toDateString(),
|
||||
accumulated_rewards: { amount: '0.05', denom: 'NYM' },
|
||||
amount: { amount: '10', denom: 'NYM' },
|
||||
accumulated_rewards: { amount: '0.05', denom: 'nym' },
|
||||
amount: { amount: '10', denom: 'nym' },
|
||||
profit_margin_percent: 0.1122323949234,
|
||||
owner: '',
|
||||
block_height: BigInt(100),
|
||||
stake_saturation: 0.5,
|
||||
avg_uptime_percent: 0.5,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
total_delegation: { amount: '0', denom: 'nym' },
|
||||
pledge_amount: { amount: '0', denom: 'nym' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
uses_vesting_contract_tokens: false,
|
||||
},
|
||||
{
|
||||
node_identity: 'DT8S942S8AQs2zKHS9SVo1GyHmuca3pfL2uLhLksJ3D8',
|
||||
accumulated_rewards: { amount: '0.1', denom: 'NYM' },
|
||||
amount: { amount: '100', denom: 'NYM' },
|
||||
accumulated_rewards: { amount: '0.1', denom: 'nym' },
|
||||
amount: { amount: '100', denom: 'nym' },
|
||||
delegated_on_iso_datetime: new Date(2021, 1, 2).toDateString(),
|
||||
profit_margin_percent: 0.89,
|
||||
owner: '',
|
||||
block_height: BigInt(4000),
|
||||
stake_saturation: 0.5,
|
||||
avg_uptime_percent: 0.1,
|
||||
total_delegation: { amount: '0', denom: 'NYM' },
|
||||
pledge_amount: { amount: '0', denom: 'NYM' },
|
||||
total_delegation: { amount: '0', denom: 'nym' },
|
||||
pledge_amount: { amount: '0', denom: 'nym' },
|
||||
pending_events: [],
|
||||
history: [],
|
||||
uses_vesting_contract_tokens: true,
|
||||
@@ -64,7 +64,7 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
|
||||
identity,
|
||||
}: {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
amount: DecCoin;
|
||||
}): Promise<TransactionExecuteResult> => {
|
||||
await mockSleep(SLEEP_MS);
|
||||
// mockDelegations.push({ ...newDelegation });
|
||||
@@ -86,12 +86,11 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
|
||||
logs_json: '',
|
||||
data_json: '',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
gas_used: BigInt(1),
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
fee: { amount: '1', denom: 'nym' },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -164,11 +163,10 @@ export const MockDelegationContextProvider: FC<{}> = ({ children }) => {
|
||||
data_json: '',
|
||||
transaction_hash: '',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
gas_used: BigInt(1),
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
fee: { amount: '1', denom: 'NYM' },
|
||||
fee: { amount: '1', denom: 'nym' },
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -16,15 +16,15 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
|
||||
isAdminAddress: false,
|
||||
isLoading: false,
|
||||
clientDetails: {
|
||||
denom: 'NYMT',
|
||||
display_mix_denom: 'nymt',
|
||||
base_mix_denom: 'unymt',
|
||||
client_address: '',
|
||||
contract_address: '',
|
||||
},
|
||||
userBalance: {
|
||||
balance: {
|
||||
amount: {
|
||||
amount: '100',
|
||||
denom: 'NYMT',
|
||||
denom: 'nymt',
|
||||
},
|
||||
printable_balance: '100 NYMT',
|
||||
},
|
||||
@@ -35,6 +35,7 @@ export const MockMainContextProvider: FC<{}> = ({ children }) => {
|
||||
fetchTokenAllocation: async () => undefined,
|
||||
refreshBalances: async () => {},
|
||||
},
|
||||
denom: 'NYM',
|
||||
showAdmin: false,
|
||||
showTerminal: false,
|
||||
showSettings: false,
|
||||
|
||||
@@ -61,34 +61,26 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
denom: 'nym',
|
||||
},
|
||||
data_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
},
|
||||
gas_used: BigInt(1),
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
denom: 'nym',
|
||||
},
|
||||
data_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
},
|
||||
gas_used: BigInt(1),
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -112,34 +104,26 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
denom: 'nym',
|
||||
},
|
||||
data_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
},
|
||||
gas_used: BigInt(1),
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
{
|
||||
transaction_hash: '55303CD4B91FAC4C2715E40EBB52BB3B92829D9431B3A279D37B5CC58432E354',
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
denom: 'nym',
|
||||
},
|
||||
data_json: '[]',
|
||||
logs_json: '[]',
|
||||
gas_info: {
|
||||
gas_wanted: BigInt(1),
|
||||
fee: {
|
||||
amount: '1',
|
||||
denom: 'NYM',
|
||||
},
|
||||
gas_used: BigInt(1),
|
||||
gas_wanted: { gas_units: BigInt(1) },
|
||||
gas_used: { gas_units: BigInt(1) },
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import {
|
||||
Account,
|
||||
Balance,
|
||||
MajorCurrencyAmount,
|
||||
OriginalVestingResponse,
|
||||
Period,
|
||||
VestingAccountInfo,
|
||||
} from '@nymproject/types';
|
||||
import { Account, Balance, DecCoin, OriginalVestingResponse, Period, VestingAccountInfo } from '@nymproject/types';
|
||||
import {
|
||||
getVestingCoins,
|
||||
getVestedCoins,
|
||||
@@ -20,7 +13,7 @@ import {
|
||||
import { Console } from '../utils/console';
|
||||
|
||||
type TTokenAllocation = {
|
||||
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: MajorCurrencyAmount['amount'];
|
||||
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: DecCoin['amount'];
|
||||
};
|
||||
|
||||
export type TUseuserBalance = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, CircularProgress } from '@mui/material';
|
||||
import { FeeDetails } from '@nymproject/types';
|
||||
import { CurrencyDenom, FeeDetails } from '@nymproject/types';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { ModalListItem } from 'src/components/Modals/ModalListItem';
|
||||
import { AppContext, urls } from 'src/context';
|
||||
@@ -16,17 +16,20 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const [fee, setFee] = useState<FeeDetails>();
|
||||
const [tx, setTx] = useState<TTransactionDetails>();
|
||||
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
const { userBalance, denom, network } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) {
|
||||
if (userBalance.tokenAllocation?.spendable && denom) {
|
||||
try {
|
||||
const simulatedFee = await simulateWithdrawVestedCoins({
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.denom },
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom },
|
||||
});
|
||||
setFee(simulatedFee);
|
||||
} catch (e) {
|
||||
setFee({ amount: { amount: 'n/a', denom: clientDetails.denom }, fee: { Auto: null } });
|
||||
setFee({
|
||||
amount: { amount: 'n/a', denom: denom as CurrencyDenom },
|
||||
fee: { Auto: null },
|
||||
});
|
||||
Console.error(e);
|
||||
}
|
||||
}
|
||||
@@ -37,16 +40,16 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
}, []);
|
||||
|
||||
const handleTransfer = async () => {
|
||||
if (userBalance.tokenAllocation?.spendable && clientDetails?.denom) {
|
||||
if (userBalance.tokenAllocation?.spendable && denom) {
|
||||
setState('loading');
|
||||
try {
|
||||
const txResponse = await withdrawVestedCoins({
|
||||
amount: userBalance.tokenAllocation?.spendable,
|
||||
denom: clientDetails.denom,
|
||||
denom: denom as CurrencyDenom,
|
||||
});
|
||||
setState('success');
|
||||
setTx({
|
||||
amount: `${userBalance.tokenAllocation?.spendable} ${clientDetails?.denom}`,
|
||||
amount: `${userBalance.tokenAllocation?.spendable} ${denom}`,
|
||||
url: `${urls(network).blockExplorer}/transaction/${txResponse.transaction_hash}`,
|
||||
});
|
||||
await userBalance.refreshBalances();
|
||||
@@ -81,7 +84,7 @@ export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
<>
|
||||
<ModalListItem
|
||||
label="Unlocked transferrable tokens"
|
||||
value={`${userBalance.tokenAllocation?.spendable} ${clientDetails?.denom}`}
|
||||
value={`${userBalance.tokenAllocation?.spendable} ${denom}`}
|
||||
divider
|
||||
/>
|
||||
<ModalListItem
|
||||
|
||||
@@ -17,7 +17,9 @@ export const SuccessModal = ({ tx, onClose }: { tx?: TTransactionDetails; onClos
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
{tx && (
|
||||
<>
|
||||
<Typography variant="h5">{tx.amount}</Typography>
|
||||
<Typography variant="h5" textTransform="uppercase">
|
||||
{tx.amount}
|
||||
</Typography>
|
||||
<Link href={tx.url} target="_blank" sx={{ ml: 1 }} text="View on blockchain" />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -35,7 +35,7 @@ const vestingPeriod = (current?: Period, original?: number) => {
|
||||
};
|
||||
|
||||
const VestingSchedule = () => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const [vestedPercentage, setVestedPercentage] = useState(0);
|
||||
|
||||
const calculatePercentage = () => {
|
||||
@@ -65,9 +65,8 @@ const VestingSchedule = () => {
|
||||
))}
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell sx={{ borderBottom: 'none' }}>
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.denom}
|
||||
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }}>
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom}
|
||||
</TableCell>
|
||||
<TableCell align="left" sx={{ borderBottom: 'none' }}>
|
||||
{vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)}
|
||||
@@ -78,9 +77,8 @@ const VestingSchedule = () => {
|
||||
<VestingTimeline percentageComplete={vestedPercentage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell sx={{ borderBottom: 'none' }} align="right">
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.denom}
|
||||
<TableCell sx={{ borderBottom: 'none', textTransform: 'uppercase' }} align="right">
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount} {denom}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
@@ -90,7 +88,7 @@ const VestingSchedule = () => {
|
||||
};
|
||||
|
||||
const TokenTransfer = () => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const icon = useCallback(
|
||||
() => (
|
||||
<Box sx={{ display: 'flex', mr: 1 }}>
|
||||
@@ -109,8 +107,14 @@ const TokenTransfer = () => {
|
||||
Transferable tokens
|
||||
</Typography>
|
||||
|
||||
<Typography data-testid="refresh-success" sx={{ color: 'text.primary' }} variant="h5" fontWeight="700">
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.denom}
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
sx={{ color: 'text.primary' }}
|
||||
variant="h5"
|
||||
fontWeight="700"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {denom}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { FeeDetails, DecCoin } from '@nymproject/types';
|
||||
import { SimpleModal } from 'src/components/Modals/SimpleModal';
|
||||
import { ModalFee } from 'src/components/Modals/ModalFee';
|
||||
import { ModalListItem } from 'src/components/Modals/ModalListItem';
|
||||
@@ -13,7 +13,7 @@ export const ConfirmationModal = ({
|
||||
onConfirm,
|
||||
}: {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
amount: DecCoin;
|
||||
fee: FeeDetails;
|
||||
onPrev: () => void;
|
||||
onConfirm: () => Promise<void>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { CurrencyDenom, DecCoin } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
@@ -19,7 +19,7 @@ type TBondFormFields = {
|
||||
ownerSignature: string;
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
amount: DecCoin;
|
||||
host: string;
|
||||
version: string;
|
||||
location: string;
|
||||
@@ -33,7 +33,7 @@ const defaultValues = {
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
amount: { amount: '', denom: 'nym' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
location: '',
|
||||
@@ -63,7 +63,7 @@ export const GatewayForm = ({
|
||||
resolver: yupResolver(gatewayValidationSchema),
|
||||
defaultValues,
|
||||
});
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, clientDetails, denom } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
@@ -209,7 +209,7 @@ export const GatewayForm = ({
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={clientDetails?.denom}
|
||||
denom={denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useContext, useEffect } from 'react';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Box, Button, Checkbox, CircularProgress, FormControl, FormControlLabel, Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { CurrencyDenom, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { CurrencyDenom, DecCoin } from '@nymproject/types';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { LoadingModal } from 'src/components/Modals/LoadingModal';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
@@ -20,7 +20,7 @@ type TBondFormFields = {
|
||||
identityKey: string;
|
||||
sphinxKey: string;
|
||||
profitMarginPercent: number;
|
||||
amount: MajorCurrencyAmount;
|
||||
amount: DecCoin;
|
||||
host: string;
|
||||
version: string;
|
||||
mixPort: number;
|
||||
@@ -34,7 +34,7 @@ const defaultValues = {
|
||||
identityKey: '',
|
||||
sphinxKey: '',
|
||||
ownerSignature: '',
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
amount: { amount: '', denom: 'nym' as CurrencyDenom },
|
||||
host: '',
|
||||
version: '',
|
||||
profitMarginPercent: 10,
|
||||
@@ -66,7 +66,7 @@ export const MixnodeForm = ({
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, clientDetails, denom } = useContext(AppContext);
|
||||
|
||||
const { fee, getFee, resetFeeState, feeError } = useGetFee();
|
||||
|
||||
@@ -216,7 +216,7 @@ export const MixnodeForm = ({
|
||||
fullWidth
|
||||
label="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
denom={clientDetails?.denom}
|
||||
denom={denom}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { AppContext } from '../../../context/main';
|
||||
import { useCheckOwnership } from '../../../hooks/useCheckOwnership';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; address: string } }> = ({ details }) => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const { userBalance, denom } = useContext(AppContext);
|
||||
const { ownership } = useCheckOwnership();
|
||||
|
||||
return (
|
||||
@@ -15,8 +15,8 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
subtitle="Successfully bonded to node with following details"
|
||||
caption={
|
||||
ownership.vestingPledge
|
||||
? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${clientDetails?.denom}`
|
||||
: `Your current balance is: ${userBalance.balance?.printable_balance}`
|
||||
? `Your current locked balance is: ${userBalance.tokenAllocation?.locked}${denom}`
|
||||
: `Your current balance is: ${userBalance.balance?.printable_balance.toUpperCase()}`
|
||||
}
|
||||
/>
|
||||
{details && (
|
||||
@@ -24,7 +24,10 @@ export const SuccessView: React.FC<{ details?: { amount: string; address: string
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Node', secondary: details.address },
|
||||
{ primary: 'Amount', secondary: `${details.amount} ${clientDetails?.denom}` },
|
||||
{
|
||||
primary: 'Amount',
|
||||
secondary: `${details.amount} ${denom}`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Box, Button, CircularProgress, FormControl, Grid, InputAdornment, TextField } from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { MajorCurrencyAmount, TNodeType, TransactionExecuteResult } from '@nymproject/types';
|
||||
import { TDelegateArgs } from '../../types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { delegateToMixnode, vestingDelegateToMixnode } from '../../requests';
|
||||
import { Fee, TokenPoolSelector } from '../../components';
|
||||
import { Console } from '../../utils/console';
|
||||
|
||||
type TDelegateForm = {
|
||||
identity: string;
|
||||
amount: MajorCurrencyAmount;
|
||||
tokenPool: string;
|
||||
type: TNodeType;
|
||||
};
|
||||
|
||||
const defaultValues: TDelegateForm = {
|
||||
identity: '',
|
||||
amount: { amount: '', denom: 'NYM' },
|
||||
tokenPool: 'balance',
|
||||
type: 'mixnode',
|
||||
};
|
||||
|
||||
export const DelegateForm = ({
|
||||
onError,
|
||||
onSuccess,
|
||||
}: {
|
||||
onError: (message?: string) => void;
|
||||
onSuccess: (details: { amount: string; result: TransactionExecuteResult }) => void;
|
||||
}) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
reset,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TDelegateForm>({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
reset();
|
||||
}, [clientDetails]);
|
||||
|
||||
const onSubmit = async (data: TDelegateForm, cb: (data: TDelegateArgs) => Promise<TransactionExecuteResult>) => {
|
||||
await cb({
|
||||
identity: data.identity,
|
||||
amount: { ...data.amount, denom: clientDetails!.denom },
|
||||
})
|
||||
.then(async (result) => {
|
||||
if (data.tokenPool === 'balance') {
|
||||
await userBalance.fetchBalance();
|
||||
} else {
|
||||
await userBalance.fetchTokenAllocation();
|
||||
}
|
||||
onSuccess({ amount: data.amount.amount, result });
|
||||
})
|
||||
.catch((e) => {
|
||||
Console.error(e as string);
|
||||
onError(e);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('identity')}
|
||||
required
|
||||
variant="outlined"
|
||||
id="identity"
|
||||
name="identity"
|
||||
label="Mixnode identity"
|
||||
fullWidth
|
||||
error={!!errors.identity}
|
||||
helperText={errors?.identity?.message}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{userBalance.originalVesting && (
|
||||
<Grid item xs={6}>
|
||||
<TokenPoolSelector onSelect={(pool) => setValue('tokenPool', pool)} disabled={false} />
|
||||
</Grid>
|
||||
)}
|
||||
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
{...register('amount.amount')}
|
||||
required
|
||||
variant="outlined"
|
||||
id="amount"
|
||||
name="amount.amount"
|
||||
label="Amount to delegate"
|
||||
fullWidth
|
||||
error={!!errors.amount?.amount}
|
||||
helperText={errors?.amount?.amount?.message}
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">{clientDetails?.denom}</InputAdornment>,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Fee feeType="DelegateToMixnode" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={handleSubmit((data) =>
|
||||
onSubmit(data, data.tokenPool === 'balance' ? delegateToMixnode : vestingDelegateToMixnode),
|
||||
)}
|
||||
disabled={isSubmitting}
|
||||
data-testid="delegate-button"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
disableElevation
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
Delegate stake
|
||||
</Button>
|
||||
</Box>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, Stack, Typography } from '@mui/material';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { SuccessReponse, TransactionDetails } from '../../components';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
export const SuccessView: React.FC<{ details?: { amount: string; result: TransactionExecuteResult } }> = ({
|
||||
details,
|
||||
}) => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
return (
|
||||
<>
|
||||
<SuccessReponse
|
||||
title="Delegation Request Complete"
|
||||
subtitle={
|
||||
<Stack alignItems="center" spacing={1}>
|
||||
<Typography>Successfully requested delegation to node </Typography>
|
||||
<Typography sx={{ textDecoration: 'underline', fontWeight: 600 }}>
|
||||
Note it may take up to one hour to take effect
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
caption={`Your current balance is: ${userBalance.balance?.printable_balance}`}
|
||||
/>
|
||||
{details && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Node', secondary: details.result.transaction_hash },
|
||||
{ primary: 'Amount', secondary: `${details.amount} ${clientDetails?.denom}` },
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Alert, AlertTitle, Box, Button, Typography } from '@mui/material';
|
||||
import { TransactionExecuteResult } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { DelegateForm } from './DelegateForm';
|
||||
import { NymCard } from '../../components';
|
||||
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
|
||||
import { SuccessView } from './SuccessView';
|
||||
import { AppContext, urls } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Delegate = () => {
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
|
||||
const [error, setError] = useState<string>();
|
||||
const [successDetails, setSuccessDetails] = useState<{ amount: string; result: TransactionExecuteResult }>();
|
||||
|
||||
const { network } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title="Delegate" subheader="Delegate to mixnode" noPadding data-testid="delegateCard">
|
||||
<>
|
||||
{status === EnumRequestStatus.initial && (
|
||||
<Box sx={{ px: 3, mb: 1 }}>
|
||||
<Alert severity="warning">Always ensure you leave yourself enough funds to UNDELEGATE</Alert>
|
||||
</Box>
|
||||
)}
|
||||
{status === EnumRequestStatus.initial && (
|
||||
<DelegateForm
|
||||
onError={(message?: string) => {
|
||||
setStatus(EnumRequestStatus.error);
|
||||
setError(message);
|
||||
}}
|
||||
onSuccess={(details) => {
|
||||
setStatus(EnumRequestStatus.success);
|
||||
setSuccessDetails(details);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{status !== EnumRequestStatus.initial && (
|
||||
<>
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Error={
|
||||
<Alert severity="error" data-testid="delegate-error">
|
||||
<AlertTitle>Delegation failed</AlertTitle>
|
||||
An error occurred with the request:
|
||||
<Box sx={{ wordBreak: 'break-word' }}>{error}</Box>
|
||||
</Alert>
|
||||
}
|
||||
Success={<SuccessView details={successDetails} />}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="finish-button"
|
||||
size="large"
|
||||
disableElevation
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
}}
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</NymCard>
|
||||
<Typography sx={{ p: 3 }}>
|
||||
Checkout the{' '}
|
||||
<Link
|
||||
href={`${urls(network).networkExplorer}/network-components/mixnodes`}
|
||||
target="_blank"
|
||||
text="list of mixnodes"
|
||||
/>{' '}
|
||||
for uptime and performances to help make delegation decisions
|
||||
</Typography>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import * as Yup from 'yup';
|
||||
import { checkHasEnoughFunds, checkHasEnoughLockedTokens, validateAmount, validateKey } from '../../utils';
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
identity: Yup.string()
|
||||
.required()
|
||||
.test(
|
||||
'valid-id-key',
|
||||
'A valid identity key is required e.g. 824WyExLUWvLE2mpSHBatN4AoByuLzfnHFeHWiBYzg4z',
|
||||
(value) => (value ? validateKey(value, 32) : false),
|
||||
),
|
||||
|
||||
amount: Yup.object().shape({
|
||||
amount: Yup.string()
|
||||
.required()
|
||||
.test('valid-amount', 'A valid amount is required', async function isValidAmount(value) {
|
||||
const isValid = await validateAmount(value || '', '0');
|
||||
|
||||
if (!isValid) {
|
||||
return this.createError({ message: 'A valid amount is required' });
|
||||
}
|
||||
|
||||
const hasEnoughBalance = await checkHasEnoughFunds(value || '');
|
||||
const hasEnoughLocked = await checkHasEnoughLockedTokens(value || '');
|
||||
|
||||
if (this.parent.tokenPool === 'balance' && !hasEnoughBalance) {
|
||||
return this.createError({ message: 'Not enough funds in wallet' });
|
||||
}
|
||||
|
||||
if (this.parent.tokenPool === 'locked' && !hasEnoughLocked) {
|
||||
return this.createError({ message: 'Not enough locked tokens' });
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
denom: Yup.string().required(),
|
||||
}),
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { FC, useContext, useEffect, useState } from 'react';
|
||||
import { Box, Button, Paper, Stack, Typography } from '@mui/material';
|
||||
import { useTheme, Theme } from '@mui/material/styles';
|
||||
import { DelegationWithEverything, FeeDetails, MajorCurrencyAmount } from '@nymproject/types';
|
||||
import { DelegationWithEverything, FeeDetails, DecCoin } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { AppContext, urls } from 'src/context/main';
|
||||
import { DelegationList } from 'src/components/Delegation/DelegationList';
|
||||
@@ -22,7 +22,7 @@ import { DelegationModal, DelegationModalProps } from '../../components/Delegati
|
||||
import { backDropStyles, modalStyles } from '../../../.storybook/storiesStyles';
|
||||
|
||||
const storybookStyles = (theme: Theme, isStorybook?: boolean, backdropProps?: object) =>
|
||||
!!isStorybook
|
||||
isStorybook
|
||||
? {
|
||||
backdropProps: { ...backDropStyles(theme), ...backdropProps },
|
||||
sx: modalStyles(theme),
|
||||
@@ -44,6 +44,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
const {
|
||||
clientDetails,
|
||||
network,
|
||||
denom,
|
||||
userBalance: { balance, originalVesting, fetchBalance },
|
||||
} = useContext(AppContext);
|
||||
|
||||
@@ -64,7 +65,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
|
||||
const getAllBalances = async () => {
|
||||
const resBalance = (await userBalance()).printable_balance;
|
||||
let resVesting: MajorCurrencyAmount | undefined;
|
||||
let resVesting: DecCoin | undefined;
|
||||
try {
|
||||
resVesting = await getSpendableCoins();
|
||||
} catch (e) {
|
||||
@@ -113,7 +114,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
|
||||
const handleNewDelegation = async (
|
||||
identityKey: string,
|
||||
amount: MajorCurrencyAmount,
|
||||
amount: DecCoin,
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
@@ -154,12 +155,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelegateMore = async (
|
||||
identityKey: string,
|
||||
amount: MajorCurrencyAmount,
|
||||
tokenPool: TPoolOption,
|
||||
fee?: FeeDetails,
|
||||
) => {
|
||||
const handleDelegateMore = async (identityKey: string, amount: DecCoin, tokenPool: TPoolOption, fee?: FeeDetails) => {
|
||||
if (currentDelegationListActionItem?.node_identity !== identityKey || !clientDetails) {
|
||||
setConfirmationModalProps({
|
||||
status: 'error',
|
||||
@@ -347,7 +343,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onOk={handleNewDelegation}
|
||||
header="Delegate"
|
||||
buttonText="Delegate stake"
|
||||
currency={clientDetails!.denom}
|
||||
currency={denom}
|
||||
accountBalance={balance?.printable_balance}
|
||||
rewardInterval="weekly"
|
||||
hasVestingContract={Boolean(originalVesting)}
|
||||
@@ -363,7 +359,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
header="Delegate more"
|
||||
buttonText="Delegate more"
|
||||
identityKey={currentDelegationListActionItem.node_identity}
|
||||
currency={clientDetails!.denom}
|
||||
currency={denom}
|
||||
accountBalance={balance?.printable_balance}
|
||||
nodeUptimePercentage={currentDelegationListActionItem.avg_uptime_percent}
|
||||
profitMarginPercentage={currentDelegationListActionItem.profit_margin_percent}
|
||||
@@ -390,7 +386,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onClose={() => setShowRedeemRewardsModal(false)}
|
||||
onOk={(identity, fee) => handleRedeem(identity, fee)}
|
||||
message="Redeem rewards"
|
||||
currency={clientDetails!.denom}
|
||||
currency={denom}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
@@ -403,7 +399,7 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
|
||||
onClose={() => setShowCompoundRewardsModal(false)}
|
||||
onOk={(identity, fee) => handleCompound(identity, fee)}
|
||||
message="Compound rewards"
|
||||
currency={clientDetails!.denom}
|
||||
currency={denom}
|
||||
identityKey={currentDelegationListActionItem?.node_identity}
|
||||
amount={+currentDelegationListActionItem.accumulated_rewards.amount}
|
||||
usesVestingTokens={currentDelegationListActionItem.uses_vesting_contract_tokens}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
export * from './Admin';
|
||||
export * from './balance';
|
||||
export * from './bond';
|
||||
export * from './delegate';
|
||||
export * from './internal-docs';
|
||||
export * from './receive';
|
||||
export * from './send';
|
||||
export * from './auth';
|
||||
export * from './settings';
|
||||
export * from './unbond';
|
||||
export * from './undelegate';
|
||||
export * from './delegation';
|
||||
|
||||
@@ -6,11 +6,11 @@ import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Receive = () => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
const { clientDetails, denom } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title={`Receive ${clientDetails?.denom}`}>
|
||||
<NymCard title={`Receive ${denom}`}>
|
||||
<Stack spacing={3} alignItems="center">
|
||||
<Alert severity="info" data-testid="receive-nym" sx={{ width: '100%' }}>
|
||||
You can receive tokens by providing this address to the sender
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Box, CircularProgress, Typography } from '@mui/material';
|
||||
import { TransactionDetails as TTransactionDetails } from '@nymproject/types';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { SendError } from './SendError';
|
||||
import { AppContext, urls } from '../../context/main';
|
||||
import { SuccessReponse } from '../../components';
|
||||
import { TransactionDetails } from '../../components/TransactionDetails';
|
||||
|
||||
export const SendConfirmation = ({
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
}: {
|
||||
data?: TTransactionDetails & { tx_hash: string };
|
||||
error?: string;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
if (!data && !error && !isLoading) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
{isLoading && <CircularProgress size={48} />}
|
||||
{!isLoading && !!error && <SendError message={error} />}
|
||||
{!isLoading && data && (
|
||||
<>
|
||||
<SuccessReponse
|
||||
title="Transaction Complete"
|
||||
subtitle={
|
||||
<>
|
||||
Check the transaction hash{' '}
|
||||
<Link
|
||||
href={`${urls(network).blockExplorer}/transactions/${data.tx_hash}`}
|
||||
target="_blank"
|
||||
text="here"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
caption={
|
||||
userBalance.balance && (
|
||||
<Typography>Your current balance is: {userBalance.balance.printable_balance}</Typography>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<TransactionDetails
|
||||
details={[
|
||||
{ primary: 'Recipient', secondary: data.to_address },
|
||||
{ primary: 'Amount', secondary: `${data.amount.amount} ${clientDetails?.denom}` },
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Alert, Box, Card, Typography } from '@mui/material';
|
||||
import { ErrorOutline } from '@mui/icons-material';
|
||||
|
||||
export const SendError = ({ message }: { message?: string }) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
mb: 4,
|
||||
}}
|
||||
>
|
||||
<ErrorOutline sx={{ fontSize: 50, color: 'error.main' }} />
|
||||
<Typography>Transaction failed</Typography>
|
||||
</Box>
|
||||
|
||||
<Card variant="outlined" sx={{ width: '100%', p: 2 }}>
|
||||
<Alert severity="error" data-testid="transaction-error">
|
||||
An error occured during the request {message}
|
||||
</Alert>
|
||||
</Card>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
@@ -1,47 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { Grid, TextField } from '@mui/material';
|
||||
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { Fee } from '../../components';
|
||||
|
||||
export const SendForm = () => {
|
||||
const {
|
||||
register,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
{...register('to')}
|
||||
required
|
||||
variant="outlined"
|
||||
id="to"
|
||||
name="to"
|
||||
label="To"
|
||||
fullWidth
|
||||
autoFocus
|
||||
error={!!errors.to}
|
||||
helperText={errors.to?.message}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CurrencyFormField
|
||||
required
|
||||
fullWidth
|
||||
placeholder="Amount"
|
||||
onChanged={(val) => setValue('amount', val, { shouldValidate: true })}
|
||||
validationError={errors.amount?.amount?.message}
|
||||
denom={clientDetails?.denom}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Fee feeType="Send" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
@@ -1,57 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Card, Divider, Grid, Typography } from '@mui/material';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
const SendReviewField = ({ title, subtitle, info }: { title: string; subtitle?: string; info?: boolean }) => (
|
||||
<>
|
||||
<Typography sx={{ color: info ? 'nym.fee' : '' }}>{title}</Typography>
|
||||
<Typography data-testid={title} sx={{ color: info ? 'nym.fee' : '', wordBreak: 'break-all' }}>
|
||||
{subtitle}
|
||||
</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
export const SendReview = ({ transferFee }: { transferFee?: string }) => {
|
||||
const { getValues } = useFormContext();
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const values = getValues();
|
||||
|
||||
return (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
width: '100%',
|
||||
py: 3,
|
||||
px: 2,
|
||||
my: 3,
|
||||
mx: 0,
|
||||
}}
|
||||
>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="From" subtitle={clientDetails?.client_address} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="To" subtitle={values.to} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="Amount" subtitle={`${values.amount.amount} ${clientDetails?.denom}`} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider light />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<SendReviewField title="Transfer fee" subtitle={`${transferFee} ${clientDetails?.denom}`} info />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,177 +0,0 @@
|
||||
/* eslint-disable no-nested-ternary */
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { Box, Button, Step, StepLabel, Stepper } from '@mui/material';
|
||||
import { CurrencyDenom, MajorCurrencyAmount, SendTxResult, TransactionDetails } from '@nymproject/types';
|
||||
import { SendForm } from './SendForm';
|
||||
import { SendReview } from './SendReview';
|
||||
import { SendConfirmation } from './SendConfirmation';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { getGasFee, send } from '../../requests';
|
||||
import { checkHasEnoughFunds } from '../../utils';
|
||||
import { Console } from '../../utils/console';
|
||||
import { validationSchema } from './validationSchema';
|
||||
|
||||
const defaultValues = {
|
||||
amount: { amount: '', denom: 'NYM' as CurrencyDenom },
|
||||
memo: '',
|
||||
to: '',
|
||||
};
|
||||
|
||||
export type TFormData = {
|
||||
amount: MajorCurrencyAmount;
|
||||
memo: string;
|
||||
to: string;
|
||||
};
|
||||
|
||||
export const SendWizard = () => {
|
||||
const [activeStep, setActiveStep] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [requestError, setRequestError] = useState<string>();
|
||||
const [transferFee, setTransferFee] = useState<string>();
|
||||
const [confirmedData, setConfirmedData] = useState<TransactionDetails & { tx_hash: string }>();
|
||||
|
||||
const { userBalance } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
const getFee = async () => {
|
||||
const fee = await getGasFee('Send');
|
||||
setTransferFee(fee.amount);
|
||||
};
|
||||
getFee();
|
||||
}, []);
|
||||
|
||||
const steps = ['Enter address', 'Review and send', 'Await confirmation'];
|
||||
|
||||
const methods = useForm<TFormData>({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const handleNextStep = methods.handleSubmit(() => setActiveStep((s) => s + 1));
|
||||
|
||||
const handlePreviousStep = () => setActiveStep((s) => s - 1);
|
||||
|
||||
const handleFinish = () => {
|
||||
methods.reset();
|
||||
setIsLoading(false);
|
||||
setRequestError(undefined);
|
||||
setConfirmedData(undefined);
|
||||
setActiveStep(0);
|
||||
};
|
||||
|
||||
const handleSend = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
const formState = methods.getValues();
|
||||
|
||||
const hasEnoughFunds = await checkHasEnoughFunds(formState.amount.amount);
|
||||
if (!hasEnoughFunds) {
|
||||
methods.setError('amount.amount', {
|
||||
message: 'Not enough funds in wallet',
|
||||
});
|
||||
handlePreviousStep();
|
||||
return;
|
||||
}
|
||||
setActiveStep((s) => s + 1);
|
||||
|
||||
send({
|
||||
amount: formState.amount,
|
||||
address: formState.to,
|
||||
memo: formState.memo,
|
||||
})
|
||||
.then((res: SendTxResult) => {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { details, tx_hash } = res;
|
||||
|
||||
setActiveStep((s) => s + 1);
|
||||
setConfirmedData({
|
||||
...details,
|
||||
tx_hash,
|
||||
});
|
||||
userBalance.fetchBalance();
|
||||
})
|
||||
.catch((e) => {
|
||||
setRequestError(e);
|
||||
Console.error(e);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider {...methods}>
|
||||
<Box>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
sx={{
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
{steps.map((s) => (
|
||||
<Step
|
||||
key={s}
|
||||
sx={{
|
||||
'& .MuiStepLabel-root .Mui-active': {
|
||||
color: 'text.primary',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<StepLabel>{s}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<Box
|
||||
sx={{
|
||||
minHeight: 300,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
py: 0,
|
||||
px: 3,
|
||||
}}
|
||||
>
|
||||
{activeStep === 0 && <SendForm />}
|
||||
{activeStep === 1 && <SendReview transferFee={transferFee} />}
|
||||
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
{activeStep === 1 && (
|
||||
<Button disableElevation sx={{ mr: 1 }} onClick={handlePreviousStep} data-testid="back-button">
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disableElevation
|
||||
data-testid="button"
|
||||
onClick={() => {
|
||||
switch (activeStep) {
|
||||
case 0:
|
||||
return handleNextStep();
|
||||
case 1:
|
||||
return handleSend();
|
||||
default:
|
||||
return handleFinish();
|
||||
}
|
||||
}}
|
||||
disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)}
|
||||
size="large"
|
||||
>
|
||||
{activeStep === 0 ? 'Next' : activeStep === 1 ? 'Send' : 'Finish'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { NymCard } from '../../components';
|
||||
import { SendWizard } from './SendWizard';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Send = () => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
return (
|
||||
<PageLayout>
|
||||
<NymCard title={`Send ${clientDetails?.denom}`} noPadding>
|
||||
<SendWizard />
|
||||
</NymCard>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
@@ -1,12 +0,0 @@
|
||||
import * as Yup from 'yup';
|
||||
import { validateAmount } from '../../utils';
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
to: Yup.string().strict().trim('Cannot have leading space').required(),
|
||||
amount: Yup.object().shape({
|
||||
amount: Yup.string()
|
||||
.required('Amount is required')
|
||||
.test('valid-amount', 'A valid amount is required', (amount) => validateAmount(amount || '0', '0')),
|
||||
denom: Yup.string(),
|
||||
}),
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { PercentOutlined } from '@mui/icons-material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { InclusionProbabilityResponse, SelectionChance } from '@nymproject/types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { Fee, InfoTooltip } from '../../components';
|
||||
import { InfoTooltip } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { updateMixnode, vestingUpdateMixnode } from '../../requests';
|
||||
import { AppContext } from '../../context/main';
|
||||
@@ -162,7 +162,6 @@ export const SystemVariables = ({
|
||||
{nodeUpdateResponse === 'failed' && (
|
||||
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
|
||||
)}
|
||||
{!nodeUpdateResponse && <Fee feeType="UpdateMixnodeConfig" />}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { Table, TableCell, TableHead, TableRow } from '@mui/material';
|
||||
import { DelegationResult } from '@nymproject/types';
|
||||
import { AppContext } from 'src/context';
|
||||
|
||||
export const PendingEvents = ({
|
||||
pendingDelegations,
|
||||
show,
|
||||
}: {
|
||||
pendingDelegations: DelegationResult[];
|
||||
show: boolean;
|
||||
}) => {
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
return show ? (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ pl: 3 }}>Address</TableCell>
|
||||
<TableCell>Amount</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
{pendingDelegations.map((delegation) => (
|
||||
<TableRow>
|
||||
<TableCell sx={{ maxWidth: 200, pl: 3 }}>{delegation.target_address}</TableCell>
|
||||
<TableCell align="left">{`${delegation.amount?.amount} ${clientDetails?.denom}`}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</Table>
|
||||
) : null;
|
||||
};
|
||||
@@ -1,165 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import {
|
||||
ListItem,
|
||||
ListItemText,
|
||||
Box,
|
||||
Autocomplete,
|
||||
Button,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
Grid,
|
||||
TextField,
|
||||
} from '@mui/material';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { format } from 'date-fns';
|
||||
import { TNodeType, PendingUndelegate, TDelegation } from '@nymproject/types';
|
||||
import { validationSchema } from './validationSchema';
|
||||
import { undelegateFromMixnode, vestingUndelegateFromMixnode } from '../../requests';
|
||||
import { Fee } from '../../components';
|
||||
|
||||
type TFormData = {
|
||||
nodeType: TNodeType;
|
||||
identity: string;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
nodeType: 'Mixnode' as TNodeType,
|
||||
identity: '',
|
||||
};
|
||||
|
||||
export const UndelegateForm = ({
|
||||
delegations,
|
||||
pendingUndelegations,
|
||||
currentEndEpoch,
|
||||
onError,
|
||||
onSuccess,
|
||||
}: {
|
||||
delegations?: TDelegation[];
|
||||
pendingUndelegations?: PendingUndelegate[];
|
||||
currentEndEpoch?: BigInt;
|
||||
onError: (message?: string) => void;
|
||||
onSuccess: (message?: string) => void;
|
||||
}) => {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
setValue,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<TFormData>({
|
||||
defaultValues,
|
||||
resolver: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: TFormData) => {
|
||||
const delegation = (delegations || []).find((d) => d.node_identity === data.identity);
|
||||
|
||||
if (!delegation) {
|
||||
onError(`Could not undelegate from ${data.identity} as not found in list of delegations for this account`);
|
||||
return;
|
||||
}
|
||||
|
||||
let res;
|
||||
try {
|
||||
if ((delegation.proxy || '').trim().length === 0) {
|
||||
// the owner of the delegation is main account (the owner of the vesting account), so it is delegation with unlocked tokens
|
||||
res = await undelegateFromMixnode(data.identity);
|
||||
} else {
|
||||
// the delegation is with locked tokens, so use the vesting contract
|
||||
res = await vestingUndelegateFromMixnode(data.identity);
|
||||
}
|
||||
|
||||
if (!res) {
|
||||
onError('An error occurred when undelegating');
|
||||
return;
|
||||
}
|
||||
|
||||
onSuccess(`Successfully requested undelegation from ${data.identity}. Tx hash = ${res.transaction_hash}`);
|
||||
} catch (e) {
|
||||
onError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormControl fullWidth>
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Grid container spacing={3} direction="column">
|
||||
<Grid item xs={12}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="identity"
|
||||
render={() => (
|
||||
<Autocomplete
|
||||
disabled={isSubmitting}
|
||||
getOptionDisabled={(opt) => pendingUndelegations?.some((item) => item.mix_identity === opt) || false}
|
||||
options={delegations?.map((d) => d.node_identity) || []}
|
||||
renderOption={(props, opt) => (
|
||||
<ListItem
|
||||
{...props}
|
||||
onClick={(e: React.MouseEvent<HTMLLIElement>) => {
|
||||
setValue('identity', opt);
|
||||
props.onClick!(e);
|
||||
}}
|
||||
disablePadding
|
||||
disableGutters
|
||||
>
|
||||
<ListItemText
|
||||
primary={opt}
|
||||
secondary={
|
||||
pendingUndelegations?.some((item) => item.mix_identity === opt)
|
||||
? `Pending - Expected time of completion: ${
|
||||
currentEndEpoch ? format(new Date(Number(currentEndEpoch) * 1000), 'HH:mm') : 'N/A'
|
||||
}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
required
|
||||
variant="outlined"
|
||||
id="identity"
|
||||
name="identity"
|
||||
label="Mixnode identity"
|
||||
error={!!errors.identity}
|
||||
helperText={errors.identity?.message}
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Fee feeType="UndelegateFromMixnode" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
type="submit"
|
||||
data-testid="submit-button"
|
||||
disableElevation
|
||||
disabled={isSubmitting}
|
||||
endIcon={isSubmitting && <CircularProgress size={20} />}
|
||||
size="large"
|
||||
>
|
||||
Undelegate stake
|
||||
</Button>
|
||||
</Box>
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
@@ -1,170 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, AlertTitle, Box, Button, CircularProgress, Grid, IconButton } from '@mui/material';
|
||||
import { ArrowDropDown, ArrowDropUp } from '@mui/icons-material';
|
||||
import { DelegationResult, PendingUndelegate, TPagedDelegations } from '@nymproject/types';
|
||||
import { Epoch } from 'src/types';
|
||||
import { UndelegateForm } from './UndelegateForm';
|
||||
import { AppContext } from '../../context/main';
|
||||
import { EnumRequestStatus, NymCard, RequestStatus } from '../../components';
|
||||
import { getCurrentEpoch, getReverseMixDelegations } from '../../requests';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { removeObjectDuplicates } from '../../utils';
|
||||
import { PendingEvents } from './PendingEvents';
|
||||
|
||||
export const Undelegate = () => {
|
||||
const [message, setMessage] = useState<string>();
|
||||
const [status, setStatus] = useState<EnumRequestStatus>(EnumRequestStatus.initial);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [pagedDelegations, setPagesDelegations] = useState<TPagedDelegations>();
|
||||
const [pendingUndelegations /* , setPendingndelegations */] = useState<PendingUndelegate[]>();
|
||||
const [pendingDelegations /* , setPendingDelegations */] = useState<DelegationResult[]>();
|
||||
const [currentEndEpoch, setCurrentEndEpoch] = useState<Epoch['end']>();
|
||||
const [showPendingDelegations, setShowPendingDelegations] = useState(false);
|
||||
|
||||
const { clientDetails } = useContext(AppContext);
|
||||
|
||||
const refresh = async () => {
|
||||
const mixnodeDelegations = await getReverseMixDelegations();
|
||||
|
||||
// Temporarily comment out to fix breaking type change
|
||||
|
||||
// const pendingEvents = await getPendingDelegations();
|
||||
// const pendingVestingEvents = await getPendingVestingDelegations();
|
||||
// const pendingUndelegationEvents = [...pendingEvents, ...pendingVestingEvents]
|
||||
// .filter((evt): evt is { Undelegate: PendingUndelegate } => 'Undelegate' in evt)
|
||||
// .map((e) => ({ ...e.Undelegate }));
|
||||
// const pendingDelegationEvents = [...pendingEvents, ...pendingVestingEvents]
|
||||
// .filter((evt): evt is { Delegate: DelegationResult } => 'Delegate' in evt)
|
||||
// .map((e) => ({ ...e.Delegate }));
|
||||
const epoch = await getCurrentEpoch();
|
||||
|
||||
setCurrentEndEpoch(epoch.end);
|
||||
|
||||
// Temporarily comment out to fix breaking type change
|
||||
// setPendingUndelegations(pendingUndelegationEvents);
|
||||
// setPendingDelegations(pendingDelegationEvents);
|
||||
setPagesDelegations({
|
||||
...mixnodeDelegations,
|
||||
delegations: removeObjectDuplicates(mixnodeDelegations.delegations, 'node_identity'),
|
||||
});
|
||||
};
|
||||
|
||||
const initialize = async () => {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setStatus(EnumRequestStatus.error);
|
||||
setMessage(e as string);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initialize();
|
||||
}, [clientDetails]);
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
<NymCard title="Undelegate" subheader="Undelegate from a mixnode" noPadding>
|
||||
{isLoading && (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
p: 3,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={48} />
|
||||
</Box>
|
||||
)}
|
||||
<>
|
||||
{status === EnumRequestStatus.initial && pagedDelegations && (
|
||||
<UndelegateForm
|
||||
delegations={pagedDelegations?.delegations}
|
||||
pendingUndelegations={pendingUndelegations}
|
||||
currentEndEpoch={currentEndEpoch}
|
||||
onError={(m) => {
|
||||
setMessage(m);
|
||||
setStatus(EnumRequestStatus.error);
|
||||
refresh();
|
||||
}}
|
||||
onSuccess={(m) => {
|
||||
setMessage(m);
|
||||
setStatus(EnumRequestStatus.success);
|
||||
refresh();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{status !== EnumRequestStatus.initial && (
|
||||
<>
|
||||
<RequestStatus
|
||||
status={status}
|
||||
Error={
|
||||
<Alert severity="error" data-testid="request-error">
|
||||
An error occurred with the request: {message}
|
||||
</Alert>
|
||||
}
|
||||
Success={
|
||||
<Alert severity="success">
|
||||
<AlertTitle data-testid="undelegate-success">Undelegation request complete</AlertTitle>
|
||||
{message}
|
||||
</Alert>
|
||||
}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
p: 3,
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
data-testid="finish-button"
|
||||
variant="contained"
|
||||
disableElevation
|
||||
onClick={() => {
|
||||
setStatus(EnumRequestStatus.initial);
|
||||
initialize();
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
Finish
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</NymCard>
|
||||
</Grid>
|
||||
{pendingDelegations?.length && (
|
||||
<Grid item>
|
||||
<NymCard
|
||||
title="Pending events"
|
||||
subheader="Pending delegations"
|
||||
noPadding
|
||||
Action={
|
||||
<IconButton onClick={() => setShowPendingDelegations((show) => !show)}>
|
||||
{!showPendingDelegations ? <ArrowDropDown /> : <ArrowDropUp />}
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
{pendingDelegations ? (
|
||||
<PendingEvents pendingDelegations={pendingDelegations} show={showPendingDelegations} />
|
||||
) : (
|
||||
<div>No pending delegations</div>
|
||||
)}
|
||||
</NymCard>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user