Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b419c2b12 | |||
| 0049126a91 | |||
| 80c5194d8b | |||
| 27a6b99453 | |||
| 61982de511 | |||
| efd9883197 | |||
| ce4ae8d90c | |||
| 1d2722f994 | |||
| f7a0b305df | |||
| 746ec71a0d | |||
| cdfa5ee540 | |||
| 71853f69f3 | |||
| bedff1f258 | |||
| 71a10a9a8b | |||
| 605aed6f20 | |||
| 5aee4b1660 | |||
| 68a7bb67de | |||
| 31e93428cf | |||
| 5f56b3eeea | |||
| e69b05693a | |||
| 8ae2451340 | |||
| b3b3279345 | |||
| 94a451c79b | |||
| ec7b959028 | |||
| 7061beea6e | |||
| e408162e26 | |||
| 25ae3895cb | |||
| 60296f2a41 | |||
| 3b97844310 | |||
| b1fb8bb18c | |||
| 4f59678ded | |||
| 8a1d2af3cf |
@@ -80,7 +80,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
run: cargo install --version 0.110.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
@@ -99,9 +99,14 @@ jobs:
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/credential $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
run: cargo install --version 0.110.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
|
||||
@@ -39,7 +39,5 @@ validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
envs/qwerty.env
|
||||
Cargo.lock
|
||||
nym-connect/Cargo.lock
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
|
||||
@@ -4,6 +4,26 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
- Explorer - Updates ([#3168])
|
||||
- Fix contracts and nym-api audit findings ([#3026])
|
||||
- Website v2 - deploy infrastructure for strapi and CI ([#2213])
|
||||
- add blockstream green to sp list ([#3180])
|
||||
- mock-nym-api: fix .storybook lint error ([#3178])
|
||||
- Validating new interval config parameters to prevent division by zero ([#3153])
|
||||
|
||||
[#2979]: https://github.com/nymtech/nym/issues/2979
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
[#3168]: https://github.com/nymtech/nym/issues/3168
|
||||
[#3026]: https://github.com/nymtech/nym/issues/3026
|
||||
[#2213]: https://github.com/nymtech/nym/issues/2213
|
||||
[#3180]: https://github.com/nymtech/nym/pull/3180
|
||||
[#3178]: https://github.com/nymtech/nym/pull/3178
|
||||
[#3153]: https://github.com/nymtech/nym/pull/3153
|
||||
|
||||
## [v1.1.12] (2023-03-07)
|
||||
|
||||
- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093])
|
||||
|
||||
Generated
+17
-16
@@ -646,7 +646,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dashmap 5.4.0",
|
||||
@@ -855,9 +855,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.2.1"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5abeeb891e6d0098402e4d3d042f90451db52651d2fe14b170e69a1dd3e4115"
|
||||
checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
@@ -1612,7 +1612,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.1.4",
|
||||
@@ -3062,7 +3062,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.13"
|
||||
version = "1.1.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3150,7 +3150,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-bin-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"clap 4.1.4",
|
||||
"clap_complete",
|
||||
@@ -3179,7 +3179,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -3237,7 +3237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"clap 4.1.4",
|
||||
"client-core",
|
||||
@@ -3319,7 +3319,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -3412,7 +3412,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3479,7 +3479,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"cosmwasm-std",
|
||||
@@ -3498,7 +3498,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.13"
|
||||
version = "1.1.14"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atty",
|
||||
@@ -3566,7 +3566,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"clap 4.1.4",
|
||||
@@ -3604,7 +3604,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3704,7 +3704,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
dependencies = [
|
||||
"clap 4.1.4",
|
||||
"client-core",
|
||||
@@ -3967,6 +3967,7 @@ dependencies = [
|
||||
name = "nym-vesting-contract"
|
||||
version = "1.2.0-pre.1"
|
||||
dependencies = [
|
||||
"cosmwasm-derive",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw2",
|
||||
@@ -3982,7 +3983,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"nym-contracts-common",
|
||||
|
||||
+10
@@ -107,6 +107,16 @@ license = "Apache-2.0"
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1.64"
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-utils = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw2 = { version = "=0.13.4" }
|
||||
cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
cw4 = { version = "=0.13.4" }
|
||||
dotenvy = "0.15.6"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-bin-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Common code for nym binaries"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
@@ -40,13 +40,13 @@ async-trait = { workspace = true, optional = true }
|
||||
bip39 = { version = "1", features = ["rand"], optional = true }
|
||||
nym-config = { path = "../../config", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
cw3 = { version = "0.13.4", optional = true }
|
||||
cw4 = { version = "0.13.4", optional = true }
|
||||
cw3 = { workspace = true, optional = true }
|
||||
cw4 = { workspace = true, optional = true }
|
||||
prost = { version = "0.10", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0", optional = true }
|
||||
cosmwasm-std = { workspace = true, optional = true }
|
||||
zeroize = { version = "1.5.7", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -749,6 +749,12 @@ where
|
||||
Ok(self.nym_api.get_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
|
||||
@@ -144,6 +144,21 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
|
||||
@@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
|
||||
pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
|
||||
@@ -11,7 +11,7 @@ bs58 = "0.4"
|
||||
comfy-table = "6.0.0"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw-utils = { workspace = true }
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
@@ -26,7 +26,7 @@ url = "2.2"
|
||||
tap = "1"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
nym-multisig-contract-common = { path = "../multisig-contract" }
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
cw-utils = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Common library for Nym cosmwasm contracts"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
|
||||
@@ -6,6 +6,6 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw4 = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Common library for the Nym mixnet contract"
|
||||
rust-version = "1.62"
|
||||
edition = { workspace = true }
|
||||
@@ -10,12 +10,12 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
thiserror = "1.0"
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" }
|
||||
serde_json = "1.0.0"
|
||||
humantime-serde = "1.1.1"
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw3 = { version = "0.13.4" }
|
||||
cw3-fixed-multisig = { version = "0.13.4", features = ["library"] }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cosmwasm-std = "1.0.0"
|
||||
cw-utils = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace= true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Common library for the Nym vesting contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -8,9 +8,9 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.3.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
|
||||
@@ -19,7 +19,7 @@ thiserror = "1.0"
|
||||
url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Cargo.lock
|
||||
Generated
+2037
File diff suppressed because it is too large
Load Diff
@@ -27,3 +27,18 @@ codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[workspace.dependencies]
|
||||
cosmwasm-crypto = "=1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-controllers = "=0.13.4"
|
||||
cw-multi-test = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw-utils = "=0.13.4"
|
||||
cw2 = "=0.13.4"
|
||||
cw3 = "=0.13.4"
|
||||
cw3-fixed-multisig = "=0.13.4"
|
||||
cw4 = "=0.13.4"
|
||||
|
||||
@@ -13,10 +13,10 @@ nym-bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract"
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -11,18 +11,18 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cw4 = { version = "0.13.4" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -13,13 +13,13 @@ nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut
|
||||
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw3 = "0.13.4"
|
||||
cw4 = "0.13.4"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cw-utils = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
@@ -27,7 +27,7 @@ thiserror = "1.0.23"
|
||||
|
||||
nym-coconut-bandwidth = { path = "../coconut-bandwidth" }
|
||||
nym-coconut-dkg = { path = "../coconut-dkg" }
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-mixnet-contract"
|
||||
version = "1.2.0-pre.0"
|
||||
version = "1.2.0"
|
||||
description = "Nym mixnet contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -22,14 +22,15 @@ name = "mixnet_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" }
|
||||
#nym-config = { path = "../../common/config"}
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw-storage-plus = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cosmwasm-derive = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
|
||||
bs58 = "0.4.0"
|
||||
schemars = "0.8"
|
||||
@@ -39,7 +40,7 @@ time = { version = "0.3", features = ["macros"] }
|
||||
semver = { version = "1.0.16", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = "1.0.0"
|
||||
cosmwasm-schema = { workspace = true }
|
||||
rand_chacha = "0.2"
|
||||
#rand = "0.7"
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
|
||||
@@ -327,6 +327,14 @@ pub(crate) fn try_update_interval_config(
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_is_owner(info.sender, deps.storage)?;
|
||||
|
||||
if epochs_in_interval == 0 {
|
||||
return Err(MixnetContractError::EpochsInIntervalZero);
|
||||
}
|
||||
|
||||
if epoch_duration_secs == 0 {
|
||||
return Err(MixnetContractError::EpochDurationZero);
|
||||
}
|
||||
|
||||
let interval = storage::current_interval(deps.storage)?;
|
||||
if force_immediately || interval.is_current_interval_over(&env) {
|
||||
change_interval_config(
|
||||
|
||||
@@ -17,13 +17,13 @@ crate-type = ["cdylib", "rlib"]
|
||||
library = []
|
||||
|
||||
[dependencies]
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw3 = { version = "0.13.4" }
|
||||
cw3-fixed-multisig = { version = "0.13.4", features = ["library"] }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -33,4 +33,4 @@ nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { version = "1.0.0" }
|
||||
cw4-group = { path = "../cw4-group", version = "0.13.4" }
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
|
||||
@@ -26,15 +26,15 @@ library = []
|
||||
[dependencies]
|
||||
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw-controllers = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { version = "1.0.0" }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-vesting-contract"
|
||||
version = "1.2.0-pre.1"
|
||||
version = "1.2.0"
|
||||
description = "Nym vesting contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -20,13 +20,14 @@ name = "vesting_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.3.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.3.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.3.0" }
|
||||
|
||||
cosmwasm-std = { version = "1.0.0 "}
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4", features = ["iterator"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-derive = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true, features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
@@ -38,6 +39,7 @@ rand_chacha = "0.3.1"
|
||||
base64 = "0.21.0"
|
||||
hex = "0.4.3"
|
||||
serde_json = "1.0.66"
|
||||
cosmwasm-crypto = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -42,6 +42,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub operating_cost: Coin,
|
||||
pub profit_margin_percent: Percent,
|
||||
pub family_id: Option<u16>,
|
||||
pub blacklisted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -162,6 +162,7 @@ impl ThreadsafeMixNodesCache {
|
||||
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
|
||||
profit_margin_percent: rewarding_info.cost_params.profit_margin_percent,
|
||||
family_id,
|
||||
blacklisted: node.blacklisted,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ExplorerApiTasks {
|
||||
|
||||
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
|
||||
info!("About to retrieve all mixnode bonds...");
|
||||
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed)
|
||||
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed_unfiltered)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
API_BASE_URL,
|
||||
BLOCK_API,
|
||||
COUNTRY_DATA_API,
|
||||
GATEWAYS_API,
|
||||
@@ -27,6 +28,7 @@ import {
|
||||
StatusResponse,
|
||||
SummaryOverviewResponse,
|
||||
ValidatorsResponse,
|
||||
Environment,
|
||||
GatewayBondAnnotated,
|
||||
GatewayBond,
|
||||
DirectoryService,
|
||||
@@ -157,3 +159,8 @@ export class Api {
|
||||
return json;
|
||||
};
|
||||
}
|
||||
|
||||
export const getEnvironment = (): Environment => {
|
||||
const matchEnv = (env: Environment) => API_BASE_URL?.toLocaleLowerCase().includes(env) && env;
|
||||
return matchEnv('sandbox') || matchEnv('qa') || 'mainnet';
|
||||
};
|
||||
|
||||
@@ -36,16 +36,16 @@ export const EconomicsInfoColumns: ColumnsType[] = [
|
||||
tooltipInfo:
|
||||
'Monthly operational cost of running this node. This cost is set by the operator and it influences how the rewards are split between the operator and delegators.',
|
||||
},
|
||||
{
|
||||
field: 'avgUptime',
|
||||
title: 'Avg. Score',
|
||||
width: '10%',
|
||||
tooltipInfo: "Mixnode's average routing score in the last 24 hour",
|
||||
},
|
||||
{
|
||||
field: 'nodePerformance',
|
||||
title: 'Routing Score',
|
||||
width: '10%',
|
||||
tooltipInfo:
|
||||
"Mixnode's most recent score (measured in the last 15 minutes). Routing score is relative to that of the network. Each time a gateway is tested, the test packets have to go through the full path of the network (gateway + 3 nodes). If a node in the path drop packets it will affect the score of the gateway and other nodes in the test.",
|
||||
},
|
||||
{
|
||||
field: 'avgUptime',
|
||||
title: 'Avg. Score',
|
||||
tooltipInfo: "Mixnode's average routing score in the last 24 hour",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -53,7 +53,6 @@ export const DelegatorsInfoTable: FCWithChildren<UniversalTableProps<EconomicsIn
|
||||
{columnsData?.map((_, index: number) => {
|
||||
const { field } = columnsData[index];
|
||||
const value: EconomicsRowsType = (eachRow as any)[field];
|
||||
console.log(value);
|
||||
return (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
|
||||
@@ -20,6 +20,7 @@ export type MixnodeRowType = {
|
||||
stake_saturation: React.ReactNode;
|
||||
operating_cost: string;
|
||||
node_performance: NodePerformance['most_recent'];
|
||||
blacklisted: boolean;
|
||||
};
|
||||
|
||||
export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] {
|
||||
@@ -51,5 +52,6 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
stake_saturation: uncappedSaturation.toFixed(2),
|
||||
operating_cost: `${unymToNym(item.operating_cost?.amount, 6)} NYM`,
|
||||
node_performance: `${toPercentIntegerString(item.node_performance.most_recent)}%`,
|
||||
blacklisted: item.blacklisted,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,11 +27,18 @@ import { DarkLightSwitchMobile } from './Switch';
|
||||
|
||||
export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
const theme = useTheme();
|
||||
const { navState, updateNavState } = useMainContext();
|
||||
const { navState, updateNavState, environment } = useMainContext();
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false);
|
||||
|
||||
const explorerName =
|
||||
`${environment && environment.charAt(0).toUpperCase() + environment.slice(1)} Explorer` || 'Mainnet Explorer';
|
||||
|
||||
const switchNetworkText = environment === 'mainnet' ? 'Switch to Testnet' : 'Switch to Mainnet';
|
||||
const switchNetworkLink =
|
||||
environment === 'mainnet' ? 'https://sandbox-explorer.nymtech.net' : 'https://explorer.nymtech.net';
|
||||
|
||||
const toggleDrawer = () => {
|
||||
setDrawerOpen(!drawerOpen);
|
||||
};
|
||||
@@ -59,6 +66,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
@@ -72,7 +80,7 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
}}
|
||||
>
|
||||
<IconButton component="a" href={NYM_WEBSITE} target="_blank">
|
||||
<NymLogo height="40px" width="40px" />
|
||||
<NymLogo height="24px" width="24px" />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h6"
|
||||
@@ -81,16 +89,24 @@ export const MobileNav: FCWithChildren = ({ children }) => {
|
||||
color: theme.palette.nym.networkExplorer.nav.text,
|
||||
fontSize: '18px',
|
||||
fontWeight: 600,
|
||||
ml: 2,
|
||||
}}
|
||||
>
|
||||
<MuiLink component={Link} to="/overview" underline="none" color="inherit">
|
||||
Network Explorer
|
||||
<MuiLink component={Link} to="/overview" underline="none" color="inherit" fontSize={14} fontWeight={700}>
|
||||
{explorerName}
|
||||
</MuiLink>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
href={switchNetworkLink}
|
||||
sx={{ textTransform: 'none', width: 114, fontSize: '12px', fontWeight: 600, ml: 1 }}
|
||||
>
|
||||
{switchNetworkText}
|
||||
</Button>
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Box sx={{ mr: 1 }}>
|
||||
<DarkLightSwitchMobile />
|
||||
<Button onClick={toggleDrawer}>
|
||||
<Menu sx={{ color: theme.palette.primary.contrastText }} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ExpandLess, ExpandMore, Menu } from '@mui/icons-material';
|
||||
import { CSSObject, styled, Theme, useTheme } from '@mui/material/styles';
|
||||
import Button from '@mui/material/Button';
|
||||
import MuiLink from '@mui/material/Link';
|
||||
import Box from '@mui/material/Box';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
@@ -231,13 +232,20 @@ ExpandableButton.defaultProps = {
|
||||
};
|
||||
|
||||
export const Nav: FCWithChildren = ({ children }) => {
|
||||
const { updateNavState, navState } = useMainContext();
|
||||
const { updateNavState, navState, environment } = useMainContext();
|
||||
const [drawerIsOpen, setDrawerToOpen] = React.useState(false);
|
||||
const [fixedOpen, setFixedOpen] = React.useState(false);
|
||||
// Set maintenance banner to false by default to don't display it
|
||||
const [openMaintenance, setOpenMaintenance] = React.useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
const explorerName =
|
||||
`${environment && environment.charAt(0).toUpperCase() + environment.slice(1)} Explorer` || 'Mainnet Explorer';
|
||||
|
||||
const switchNetworkText = environment === 'mainnet' ? 'Switch to Testnet' : 'Switch to Mainnet';
|
||||
const switchNetworkLink =
|
||||
environment === 'mainnet' ? 'https://sandbox-explorer.nymtech.net' : 'https://explorer.nymtech.net';
|
||||
|
||||
const setToActive = (id: number) => {
|
||||
updateNavState(id);
|
||||
};
|
||||
@@ -302,8 +310,17 @@ export const Nav: FCWithChildren = ({ children }) => {
|
||||
}}
|
||||
>
|
||||
<MuiLink component={Link} to="/" underline="none" color="inherit">
|
||||
Network Explorer
|
||||
{explorerName}
|
||||
</MuiLink>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
href={switchNetworkLink}
|
||||
sx={{ borderRadius: 2, textTransform: 'none', width: 150, ml: 4, fontSize: 14, fontWeight: 600 }}
|
||||
>
|
||||
{switchNetworkText}
|
||||
</Button>
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
|
||||
@@ -54,7 +54,7 @@ export const DarkLightSwitch = styled(Switch)(({ theme }) => ({
|
||||
export const DarkLightSwitchMobile: FCWithChildren = () => {
|
||||
const { toggleMode } = useMainContext();
|
||||
return (
|
||||
<Button onClick={() => toggleMode()} data-testid="switch-button">
|
||||
<Button onClick={() => toggleMode()} data-testid="switch-button" sx={{ p: 0, minWidth: 0 }}>
|
||||
<LightSwitchSVG />
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -10,9 +10,10 @@ import {
|
||||
MixnodeStatus,
|
||||
SummaryOverviewResponse,
|
||||
ValidatorsResponse,
|
||||
Environment,
|
||||
} from '../typeDefs/explorer-api';
|
||||
import { EnumFilterKey } from '../typeDefs/filters';
|
||||
import { Api } from '../api';
|
||||
import { Api, getEnvironment } from '../api';
|
||||
import { NavOptionType, originalNavOptions } from './nav';
|
||||
|
||||
interface StateData {
|
||||
@@ -25,6 +26,7 @@ interface StateData {
|
||||
mode: PaletteMode;
|
||||
navState: NavOptionType[];
|
||||
validators?: ApiState<ValidatorsResponse>;
|
||||
environment?: Environment;
|
||||
serviceProviders?: ApiState<DirectoryService>;
|
||||
}
|
||||
|
||||
@@ -49,6 +51,9 @@ export const MainContext = React.createContext<State>({
|
||||
export const useMainContext = (): React.ContextType<typeof MainContext> => React.useContext<State>(MainContext);
|
||||
|
||||
export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
// network explorer environment
|
||||
const [environment, setEnvironment] = React.useState<Environment>('mainnet');
|
||||
|
||||
// light/dark mode
|
||||
const [mode, setMode] = React.useState<PaletteMode>('dark');
|
||||
|
||||
@@ -190,10 +195,12 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
fetchCountryData(),
|
||||
fetchServiceProviders(),
|
||||
]);
|
||||
setEnvironment(getEnvironment());
|
||||
}, []);
|
||||
|
||||
const state = React.useMemo<State>(
|
||||
() => ({
|
||||
environment,
|
||||
block,
|
||||
countryData,
|
||||
fetchMixnodes,
|
||||
@@ -210,6 +217,7 @@ export const MainContextProvider: FCWithChildren = ({ children }) => {
|
||||
serviceProviders,
|
||||
}),
|
||||
[
|
||||
environment,
|
||||
block,
|
||||
countryData,
|
||||
gateways,
|
||||
|
||||
@@ -111,7 +111,12 @@ const PageGatewayDetailsWithState = ({ selectedGateway }: { selectedGateway: Gat
|
||||
{uptimeStory && (
|
||||
<ContentCard title="Routing Score">
|
||||
{uptimeStory.error && <ComponentError text="There was a problem retrieving routing score." />}
|
||||
<UptimeChart loading={uptimeStory.isLoading} xLabel="date" uptimeStory={uptimeStory} />
|
||||
<UptimeChart
|
||||
loading={uptimeStory.isLoading}
|
||||
xLabel="Date"
|
||||
yLabel="Daily average"
|
||||
uptimeStory={uptimeStory}
|
||||
/>
|
||||
</ContentCard>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
@@ -153,9 +153,9 @@ export const PageGateways: FCWithChildren = () => {
|
||||
renderCell: (params: GridRenderCellParams) => (
|
||||
<MuiLink
|
||||
sx={{ ...cellStyles }}
|
||||
href={`${NYM_BIG_DIPPER}/account/${params.value}`}
|
||||
target="_blank"
|
||||
data-testid="owner"
|
||||
component={RRDLink}
|
||||
to={`/network-components/gateway/${params.row.identity_key}`}
|
||||
data-testid="version"
|
||||
>
|
||||
{params.value}
|
||||
</MuiLink>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { WorldMap } from '../../components/WorldMap';
|
||||
import { MixNodeDetailSection } from '../../components/MixNodes/DetailSection';
|
||||
import { MixnodeContextProvider, useMixnodeContext } from '../../context/mixnode';
|
||||
import { Title } from '../../components/Title';
|
||||
import { useIsMobile } from '../../hooks/useIsMobile';
|
||||
|
||||
const columns: ColumnsType[] = [
|
||||
{
|
||||
@@ -41,6 +42,7 @@ const columns: ColumnsType[] = [
|
||||
field: 'self_percentage',
|
||||
width: '10%',
|
||||
title: 'Bond %',
|
||||
tooltipInfo: "Percentage of the operator's bond to the total stake on the node",
|
||||
},
|
||||
|
||||
{
|
||||
@@ -64,7 +66,7 @@ const columns: ColumnsType[] = [
|
||||
*/
|
||||
const PageMixnodeDetailWithState: FCWithChildren = () => {
|
||||
const { mixNode, mixNodeRow, description, stats, status, uptimeStory, uniqDelegations } = useMixnodeContext();
|
||||
console.log(mixNodeRow);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<Box component="main">
|
||||
<Title text="Mixnode Detail" />
|
||||
@@ -73,6 +75,11 @@ const PageMixnodeDetailWithState: FCWithChildren = () => {
|
||||
{mixNodeRow && description?.data && (
|
||||
<MixNodeDetailSection mixNodeRow={mixNodeRow} mixnodeDescription={description.data} />
|
||||
)}
|
||||
{mixNodeRow?.blacklisted && (
|
||||
<Typography textAlign={isMobile ? 'left' : 'right'} fontSize="smaller" sx={{ color: 'error.main' }}>
|
||||
This node is having a poor performance
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container>
|
||||
@@ -134,7 +141,12 @@ const PageMixnodeDetailWithState: FCWithChildren = () => {
|
||||
{uptimeStory && (
|
||||
<ContentCard title="Routing Score">
|
||||
{uptimeStory.error && <ComponentError text="There was a problem retrieving routing score." />}
|
||||
<UptimeChart loading={uptimeStory.isLoading} xLabel="date" uptimeStory={uptimeStory} />
|
||||
<UptimeChart
|
||||
loading={uptimeStory.isLoading}
|
||||
xLabel="Date"
|
||||
yLabel="Daily average"
|
||||
uptimeStory={uptimeStory}
|
||||
/>
|
||||
</ContentCard>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
@@ -22,9 +22,10 @@ const SupportedApps = () => {
|
||||
<FormControl size="small">
|
||||
<Select value={selected} onChange={handleChange} displayEmpty sx={{ mr: 2 }}>
|
||||
<MenuItem value="">Supported Apps</MenuItem>
|
||||
<MenuItem sx={{ opacity: 1 }}>Keybase</MenuItem>
|
||||
<MenuItem>Keybase</MenuItem>
|
||||
<MenuItem>Telegram</MenuItem>
|
||||
<MenuItem>Electrum</MenuItem>
|
||||
<MenuItem>Blockstream Green</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@@ -89,6 +89,7 @@ export interface MixNodeResponseItem {
|
||||
uncapped_saturation: number;
|
||||
operating_cost: Amount;
|
||||
profit_margin_percent: string;
|
||||
blacklisted: boolean;
|
||||
}
|
||||
|
||||
export type MixNodeResponse = MixNodeResponseItem[];
|
||||
@@ -238,6 +239,8 @@ export type MixNodeEconomicDynamicsStatsResponse = {
|
||||
current_interval_uptime: number;
|
||||
};
|
||||
|
||||
export type Environment = 'mainnet' | 'sandbox' | 'qa';
|
||||
|
||||
export type DirectoryServiceProvider = {
|
||||
id: string;
|
||||
description: string;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.13"
|
||||
version = "1.1.14"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+6
-6
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.13"
|
||||
version = "1.1.14"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
@@ -68,12 +68,12 @@ nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contr
|
||||
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-coconut-interface = { path = "../common/coconut-interface" }
|
||||
nym-config = { path = "../common/config" }
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
nym-credential-storage = { path = "../common/credential-storage" }
|
||||
nym-credentials = { path = "../common/credentials" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
cw3 = { version = "0.13.4" }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw3 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
nym-dkg = { path = "../common/dkg", features = ["cw-types"] }
|
||||
gateway-client = { path = "../common/client-libs/gateway-client" }
|
||||
nym-inclusion-probability = { path = "../common/inclusion-probability" }
|
||||
@@ -106,5 +106,5 @@ sqlx = { version = "0.6.2", features = [
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
cw3 = "0.13.4"
|
||||
cw-utils = "0.13.4"
|
||||
cw3 = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmwasm-std = { version = "1.0.0", default-features = false }
|
||||
cosmwasm-std = { workspace = true, default-features = false }
|
||||
getset = "0.1.1"
|
||||
schemars = { version = "0.8", features = ["preserve_order"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@@ -111,6 +111,7 @@ pub struct MixNodeBondAnnotated {
|
||||
pub estimated_operator_apy: Decimal,
|
||||
pub estimated_delegators_apy: Decimal,
|
||||
pub family: Option<FamilyHead>,
|
||||
pub blacklisted: bool,
|
||||
}
|
||||
|
||||
impl MixNodeBondAnnotated {
|
||||
@@ -137,6 +138,7 @@ pub struct GatewayBondAnnotated {
|
||||
// NOTE: the performance field is deprecated in favour of node_performance
|
||||
pub performance: Performance,
|
||||
pub node_performance: NodePerformance,
|
||||
pub blacklisted: bool,
|
||||
}
|
||||
|
||||
impl GatewayBondAnnotated {
|
||||
|
||||
@@ -102,7 +102,7 @@ impl RewardedSetUpdater {
|
||||
|
||||
let epoch_end = interval.current_epoch_end();
|
||||
|
||||
let all_mixnodes = self.nym_contract_cache.mixnodes().await;
|
||||
let all_mixnodes = self.nym_contract_cache.mixnodes_filtered().await;
|
||||
if all_mixnodes.is_empty() {
|
||||
// that's a bit weird, but
|
||||
log::warn!("there don't seem to be any mixnodes on the network!")
|
||||
|
||||
+13
-3
@@ -89,10 +89,15 @@ impl NodeStatusCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn mixnodes_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
|
||||
pub(crate) async fn mixnodes_annotated_full(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
|
||||
self.get(|c| c.mixnodes_annotated.clone()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn mixnodes_annotated_filtered(&self) -> Option<Vec<MixNodeBondAnnotated>> {
|
||||
let full = self.mixnodes_annotated_full().await?;
|
||||
Some(full.value.into_iter().filter(|m| !m.blacklisted).collect())
|
||||
}
|
||||
|
||||
pub(crate) async fn rewarded_set_annotated(&self) -> Option<Cache<Vec<MixNodeBondAnnotated>>> {
|
||||
self.get(|c| c.rewarded_set_annotated.clone()).await
|
||||
}
|
||||
@@ -101,10 +106,15 @@ impl NodeStatusCache {
|
||||
self.get(|c| c.active_set_annotated.clone()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn gateways_annotated(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> {
|
||||
pub(crate) async fn gateways_annotated_full(&self) -> Option<Cache<Vec<GatewayBondAnnotated>>> {
|
||||
self.get(|c| c.gateways_annotated.clone()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn gateways_annotated_filtered(&self) -> Option<Vec<GatewayBondAnnotated>> {
|
||||
let full = self.gateways_annotated_full().await?;
|
||||
Some(full.value.into_iter().filter(|m| !m.blacklisted).collect())
|
||||
}
|
||||
|
||||
pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> {
|
||||
self.get(|c| c.inclusion_probabilities.clone()).await
|
||||
}
|
||||
@@ -126,7 +136,7 @@ impl NodeStatusCache {
|
||||
return (Some(bond.clone()), MixnodeStatus::Standby);
|
||||
}
|
||||
|
||||
let all_bonded = &self.mixnodes_annotated().await.unwrap().into_inner();
|
||||
let all_bonded = &self.mixnodes_annotated_filtered().await.unwrap();
|
||||
if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) {
|
||||
(Some(bond.clone()), MixnodeStatus::Inactive)
|
||||
} else {
|
||||
|
||||
+5
-1
@@ -6,7 +6,7 @@ use nym_mixnet_contract_common::{reward_params::Performance, Interval, MixId};
|
||||
use nym_mixnet_contract_common::{
|
||||
GatewayBond, IdentityKey, MixNodeDetails, RewardedSetNodeStatus, RewardingParams,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub(super) fn to_rewarded_set_node_status(
|
||||
rewarded_set: &[MixNodeDetails],
|
||||
@@ -84,6 +84,7 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
current_interval: Interval,
|
||||
rewarded_set: &HashMap<MixId, RewardedSetNodeStatus>,
|
||||
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
|
||||
blacklist: &HashSet<MixId>,
|
||||
) -> Vec<MixNodeBondAnnotated> {
|
||||
let mix_to_family = mix_to_family
|
||||
.into_iter()
|
||||
@@ -135,6 +136,7 @@ pub(super) async fn annotate_nodes_with_details(
|
||||
.cloned();
|
||||
|
||||
annotated.push(MixNodeBondAnnotated {
|
||||
blacklisted: blacklist.contains(&mixnode.mix_id()),
|
||||
mixnode_details: mixnode,
|
||||
stake_saturation,
|
||||
uncapped_stake_saturation,
|
||||
@@ -152,6 +154,7 @@ pub(crate) async fn annotate_gateways_with_details(
|
||||
storage: &Option<NymApiStorage>,
|
||||
gateway_bonds: Vec<GatewayBond>,
|
||||
current_interval: Interval,
|
||||
blacklist: &HashSet<IdentityKey>,
|
||||
) -> Vec<GatewayBondAnnotated> {
|
||||
let mut annotated = Vec::new();
|
||||
for gateway_bond in gateway_bonds {
|
||||
@@ -175,6 +178,7 @@ pub(crate) async fn annotate_gateways_with_details(
|
||||
.unwrap_or_default();
|
||||
|
||||
annotated.push(GatewayBondAnnotated {
|
||||
blacklisted: blacklist.contains(&gateway_bond.gateway.identity_key),
|
||||
gateway_bond,
|
||||
performance,
|
||||
node_performance,
|
||||
|
||||
+14
-4
@@ -109,13 +109,17 @@ impl NodeStatusCacheRefresher {
|
||||
log::info!("Updating node status cache");
|
||||
|
||||
// Fetch contract cache data to work with
|
||||
let mixnode_details = self.contract_cache.mixnodes().await;
|
||||
let mixnode_details = self.contract_cache.mixnodes_all().await;
|
||||
let interval_reward_params = self.contract_cache.interval_reward_params().await;
|
||||
let current_interval = self.contract_cache.current_interval().await;
|
||||
let rewarded_set = self.contract_cache.rewarded_set().await;
|
||||
let active_set = self.contract_cache.active_set().await;
|
||||
let mix_to_family = self.contract_cache.mix_to_family().await;
|
||||
let gateway_bonds = self.contract_cache.gateways().await;
|
||||
let gateway_bonds = self.contract_cache.gateways_all().await;
|
||||
|
||||
// get blacklists
|
||||
let mixnodes_blacklist = self.contract_cache.mixnodes_blacklist().await;
|
||||
let gateways_blacklist = self.contract_cache.gateways_blacklist().await;
|
||||
|
||||
let interval_reward_params =
|
||||
interval_reward_params.ok_or(NodeStatusCacheError::SourceDataMissing)?;
|
||||
@@ -140,6 +144,7 @@ impl NodeStatusCacheRefresher {
|
||||
current_interval,
|
||||
&rewarded_set_node_status,
|
||||
mix_to_family.to_vec(),
|
||||
&mixnodes_blacklist,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -147,8 +152,13 @@ impl NodeStatusCacheRefresher {
|
||||
let (rewarded_set, active_set) =
|
||||
split_into_active_and_rewarded_set(&mixnodes_annotated, &rewarded_set_node_status);
|
||||
|
||||
let gateways_annotated =
|
||||
annotate_gateways_with_details(&self.storage, gateway_bonds, current_interval).await;
|
||||
let gateways_annotated = annotate_gateways_with_details(
|
||||
&self.storage,
|
||||
gateway_bonds,
|
||||
current_interval,
|
||||
&gateways_blacklist,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Update the cache
|
||||
self.cache
|
||||
|
||||
@@ -24,17 +24,19 @@ async fn get_gateway_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
identity: &str,
|
||||
) -> Result<GatewayBondAnnotated, ErrorResponse> {
|
||||
let gateways = cache.gateways_annotated().await.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
let gateways = cache
|
||||
.gateways_annotated_filtered()
|
||||
.await
|
||||
.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
|
||||
gateways
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.find(|gateway| gateway.identity() == identity)
|
||||
.ok_or(ErrorResponse::new(
|
||||
"mixnode bond not found",
|
||||
"gateway bond not found",
|
||||
Status::NotFound,
|
||||
))
|
||||
}
|
||||
@@ -43,13 +45,15 @@ async fn get_mixnode_bond_annotated(
|
||||
cache: &NodeStatusCache,
|
||||
mix_id: MixId,
|
||||
) -> Result<MixNodeBondAnnotated, ErrorResponse> {
|
||||
let mixnodes = cache.mixnodes_annotated().await.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
let mixnodes = cache
|
||||
.mixnodes_annotated_filtered()
|
||||
.await
|
||||
.ok_or(ErrorResponse::new(
|
||||
"no data available",
|
||||
Status::ServiceUnavailable,
|
||||
))?;
|
||||
|
||||
mixnodes
|
||||
.into_inner()
|
||||
.into_iter()
|
||||
.find(|mixnode| mixnode.mix_id() == mix_id)
|
||||
.ok_or(ErrorResponse::new(
|
||||
@@ -374,7 +378,16 @@ pub(crate) async fn _get_mixnode_inclusion_probabilities(
|
||||
|
||||
pub(crate) async fn _get_mixnodes_detailed(cache: &NodeStatusCache) -> Vec<MixNodeBondAnnotated> {
|
||||
cache
|
||||
.mixnodes_annotated()
|
||||
.mixnodes_annotated_filtered()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_mixnodes_detailed_unfiltered(
|
||||
cache: &NodeStatusCache,
|
||||
) -> Vec<MixNodeBondAnnotated> {
|
||||
cache
|
||||
.mixnodes_annotated_full()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_inner()
|
||||
@@ -400,7 +413,16 @@ pub(crate) async fn _get_active_set_detailed(cache: &NodeStatusCache) -> Vec<Mix
|
||||
|
||||
pub(crate) async fn _get_gateways_detailed(cache: &NodeStatusCache) -> Vec<GatewayBondAnnotated> {
|
||||
cache
|
||||
.gateways_annotated()
|
||||
.gateways_annotated_filtered()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) async fn _get_gateways_detailed_unfiltered(
|
||||
cache: &NodeStatusCache,
|
||||
) -> Vec<GatewayBondAnnotated> {
|
||||
cache
|
||||
.gateways_annotated_full()
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_inner()
|
||||
|
||||
@@ -48,9 +48,11 @@ pub(crate) fn node_status_routes(
|
||||
routes::get_gateway_avg_uptime,
|
||||
routes::get_mixnode_inclusion_probabilities,
|
||||
routes::get_mixnodes_detailed,
|
||||
routes::get_mixnodes_detailed_unfiltered,
|
||||
routes::get_rewarded_set_detailed,
|
||||
routes::get_active_set_detailed,
|
||||
routes::get_gateways_detailed,
|
||||
routes::get_gateways_detailed_unfiltered,
|
||||
]
|
||||
} else {
|
||||
// in the minimal variant we would not have access to endpoints relying on existence
|
||||
|
||||
@@ -6,11 +6,11 @@ use super::NodeStatusCache;
|
||||
use crate::node_status_api::helpers::{
|
||||
_compute_mixnode_reward_estimation, _gateway_core_status_count, _gateway_report,
|
||||
_gateway_uptime_history, _get_active_set_detailed, _get_gateway_avg_uptime,
|
||||
_get_mixnode_avg_uptime, _get_mixnode_inclusion_probabilities,
|
||||
_get_mixnode_inclusion_probability, _get_mixnode_reward_estimation,
|
||||
_get_mixnode_stake_saturation, _get_mixnode_status, _get_mixnodes_detailed,
|
||||
_get_rewarded_set_detailed, _mixnode_core_status_count, _mixnode_report,
|
||||
_mixnode_uptime_history,
|
||||
_get_gateways_detailed_unfiltered, _get_mixnode_avg_uptime,
|
||||
_get_mixnode_inclusion_probabilities, _get_mixnode_inclusion_probability,
|
||||
_get_mixnode_reward_estimation, _get_mixnode_stake_saturation, _get_mixnode_status,
|
||||
_get_mixnodes_detailed, _get_mixnodes_detailed_unfiltered, _get_rewarded_set_detailed,
|
||||
_mixnode_core_status_count, _mixnode_report, _mixnode_uptime_history,
|
||||
};
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::storage::NymApiStorage;
|
||||
@@ -188,6 +188,14 @@ pub async fn get_mixnodes_detailed(
|
||||
Json(_get_mixnodes_detailed(cache).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/mixnodes/detailed-unfiltered")]
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
cache: &State<NodeStatusCache>,
|
||||
) -> Json<Vec<MixNodeBondAnnotated>> {
|
||||
Json(_get_mixnodes_detailed_unfiltered(cache).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/mixnodes/rewarded/detailed")]
|
||||
pub async fn get_rewarded_set_detailed(
|
||||
@@ -211,3 +219,11 @@ pub async fn get_gateways_detailed(
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_gateways_detailed(cache).await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "status")]
|
||||
#[get("/gateways/detailed-unfiltered")]
|
||||
pub async fn get_gateways_detailed_unfiltered(
|
||||
cache: &State<NodeStatusCache>,
|
||||
) -> Json<Vec<GatewayBondAnnotated>> {
|
||||
Json(_get_gateways_detailed_unfiltered(cache).await)
|
||||
}
|
||||
|
||||
+69
-72
@@ -67,50 +67,48 @@ impl NymContractCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mixnodes_blacklist(&self) -> Option<Cache<HashSet<MixId>>> {
|
||||
pub async fn mixnodes_blacklist(&self) -> Cache<HashSet<MixId>> {
|
||||
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => Some(cache.mixnodes_blacklist.clone()),
|
||||
Ok(cache) => cache.mixnodes_blacklist.clone(),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
None
|
||||
Cache::new(HashSet::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways_blacklist(&self) -> Option<Cache<HashSet<IdentityKey>>> {
|
||||
pub async fn gateways_blacklist(&self) -> Cache<HashSet<IdentityKey>> {
|
||||
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => Some(cache.gateways_blacklist.clone()),
|
||||
Ok(cache) => cache.gateways_blacklist.clone(),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
None
|
||||
Cache::new(HashSet::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_mixnodes_blacklist(&self, add: HashSet<MixId>, remove: HashSet<MixId>) {
|
||||
let blacklist = self.mixnodes_blacklist().await;
|
||||
if let Some(blacklist) = blacklist {
|
||||
let mut blacklist = blacklist
|
||||
.value
|
||||
.union(&add)
|
||||
.cloned()
|
||||
.collect::<HashSet<MixId>>();
|
||||
let to_remove = blacklist
|
||||
.intersection(&remove)
|
||||
.cloned()
|
||||
.collect::<HashSet<MixId>>();
|
||||
for key in to_remove {
|
||||
blacklist.remove(&key);
|
||||
let mut blacklist = blacklist
|
||||
.value
|
||||
.union(&add)
|
||||
.cloned()
|
||||
.collect::<HashSet<MixId>>();
|
||||
let to_remove = blacklist
|
||||
.intersection(&remove)
|
||||
.cloned()
|
||||
.collect::<HashSet<MixId>>();
|
||||
for key in to_remove {
|
||||
blacklist.remove(&key);
|
||||
}
|
||||
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
|
||||
Ok(mut cache) => {
|
||||
cache.mixnodes_blacklist.update(blacklist);
|
||||
}
|
||||
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
|
||||
Ok(mut cache) => {
|
||||
cache.mixnodes_blacklist.update(blacklist);
|
||||
return;
|
||||
}
|
||||
Err(err) => error!("{err}"),
|
||||
Err(err) => {
|
||||
error!("Failed to update mixnodes blacklist: {err}");
|
||||
}
|
||||
}
|
||||
error!("Failed to update mixnodes blacklist");
|
||||
}
|
||||
|
||||
pub async fn update_gateways_blacklist(
|
||||
@@ -119,49 +117,52 @@ impl NymContractCache {
|
||||
remove: HashSet<IdentityKey>,
|
||||
) {
|
||||
let blacklist = self.gateways_blacklist().await;
|
||||
if let Some(blacklist) = blacklist {
|
||||
let mut blacklist = blacklist
|
||||
.value
|
||||
.union(&add)
|
||||
.cloned()
|
||||
.collect::<HashSet<IdentityKey>>();
|
||||
let to_remove = blacklist
|
||||
.intersection(&remove)
|
||||
.cloned()
|
||||
.collect::<HashSet<IdentityKey>>();
|
||||
for key in to_remove {
|
||||
blacklist.remove(&key);
|
||||
let mut blacklist = blacklist
|
||||
.value
|
||||
.union(&add)
|
||||
.cloned()
|
||||
.collect::<HashSet<IdentityKey>>();
|
||||
let to_remove = blacklist
|
||||
.intersection(&remove)
|
||||
.cloned()
|
||||
.collect::<HashSet<IdentityKey>>();
|
||||
for key in to_remove {
|
||||
blacklist.remove(&key);
|
||||
}
|
||||
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
|
||||
Ok(mut cache) => {
|
||||
cache.gateways_blacklist.update(blacklist);
|
||||
}
|
||||
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
|
||||
Ok(mut cache) => {
|
||||
cache.gateways_blacklist.update(blacklist);
|
||||
return;
|
||||
}
|
||||
Err(err) => error!("{err}"),
|
||||
Err(err) => {
|
||||
error!("Failed to update gateways blacklist: {err}");
|
||||
}
|
||||
}
|
||||
error!("Failed to update gateways blacklist");
|
||||
}
|
||||
|
||||
pub async fn mixnodes(&self) -> Vec<MixNodeDetails> {
|
||||
pub async fn mixnodes_filtered(&self) -> Vec<MixNodeDetails> {
|
||||
let mixnodes = self.mixnodes_all().await;
|
||||
if mixnodes.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let blacklist = self.mixnodes_blacklist().await;
|
||||
let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => cache.mixnodes.clone(),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(blacklist) = blacklist {
|
||||
if !blacklist.is_empty() {
|
||||
mixnodes
|
||||
.value
|
||||
.iter()
|
||||
.into_iter()
|
||||
.filter(|mix| !blacklist.value.contains(&mix.mix_id()))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
mixnodes.value
|
||||
mixnodes
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn mixnodes_all(&self) -> Vec<MixNodeDetails> {
|
||||
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => cache.mixnodes.clone().value,
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,25 +182,21 @@ impl NymContractCache {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways(&self) -> Vec<GatewayBond> {
|
||||
let blacklist = self.gateways_blacklist().await;
|
||||
let gateways = match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => cache.gateways.clone(),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
pub async fn gateways_filtered(&self) -> Vec<GatewayBond> {
|
||||
let gateways = self.gateways_all().await;
|
||||
if gateways.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if let Some(blacklist) = blacklist {
|
||||
let blacklist = self.gateways_blacklist().await;
|
||||
|
||||
if !blacklist.is_empty() {
|
||||
gateways
|
||||
.value
|
||||
.iter()
|
||||
.into_iter()
|
||||
.filter(|mix| !blacklist.value.contains(mix.identity()))
|
||||
.cloned()
|
||||
.collect()
|
||||
} else {
|
||||
gateways.value
|
||||
gateways
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +274,7 @@ impl NymContractCache {
|
||||
return (Some(bond.clone()), MixnodeStatus::Standby);
|
||||
}
|
||||
|
||||
let all_bonded = &self.mixnodes().await;
|
||||
let all_bonded = &self.mixnodes_filtered().await;
|
||||
if let Some(bond) = all_bonded.iter().find(|mix| mix.mix_id() == mix_id) {
|
||||
(Some(bond.clone()), MixnodeStatus::Inactive)
|
||||
} else {
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::collections::HashSet;
|
||||
#[openapi(tag = "contract-cache")]
|
||||
#[get("/mixnodes")]
|
||||
pub async fn get_mixnodes(cache: &State<NymContractCache>) -> Json<Vec<MixNodeDetails>> {
|
||||
Json(cache.mixnodes().await)
|
||||
Json(cache.mixnodes_filtered().await)
|
||||
}
|
||||
|
||||
// DEPRECATED: this endpoint now lives in `node_status_api`. Once all consumers are updated,
|
||||
@@ -41,7 +41,7 @@ pub async fn get_mixnodes_detailed(
|
||||
#[openapi(tag = "contract-cache")]
|
||||
#[get("/gateways")]
|
||||
pub async fn get_gateways(cache: &State<NymContractCache>) -> Json<Vec<GatewayBond>> {
|
||||
Json(cache.gateways().await)
|
||||
Json(cache.gateways_filtered().await)
|
||||
}
|
||||
|
||||
#[openapi(tag = "contract-cache")]
|
||||
@@ -91,7 +91,12 @@ pub async fn get_active_set_detailed(
|
||||
pub async fn get_blacklisted_mixnodes(
|
||||
cache: &State<NymContractCache>,
|
||||
) -> Json<Option<HashSet<MixId>>> {
|
||||
Json(cache.mixnodes_blacklist().await.map(|c| c.value))
|
||||
let blacklist = cache.mixnodes_blacklist().await.value;
|
||||
if blacklist.is_empty() {
|
||||
Json(None)
|
||||
} else {
|
||||
Json(Some(blacklist))
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "contract-cache")]
|
||||
@@ -99,7 +104,12 @@ pub async fn get_blacklisted_mixnodes(
|
||||
pub async fn get_blacklisted_gateways(
|
||||
cache: &State<NymContractCache>,
|
||||
) -> Json<Option<HashSet<String>>> {
|
||||
Json(cache.gateways_blacklist().await.map(|c| c.value))
|
||||
let blacklist = cache.gateways_blacklist().await.value;
|
||||
if blacklist.is_empty() {
|
||||
Json(None)
|
||||
} else {
|
||||
Json(Some(blacklist))
|
||||
}
|
||||
}
|
||||
|
||||
#[openapi(tag = "contract-cache")]
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
- Wallet - in the vesting section separate the "Unlocked transferable tokens" into "Unlocked vesting tokens" and "Transferable rewards" for better clarity ([#3132])
|
||||
- Wallet - change the Bonding flow to include an additional step for the user to provide a unique signature when they bond their node ([#3109])
|
||||
|
||||
[#3132]: https://github.com/nymtech/nym/issues/3132
|
||||
[#3109]: https://github.com/nymtech/nym/issues/3109
|
||||
|
||||
## [v1.1.11] (2023-03-07)
|
||||
|
||||
- Wallet: optional gas and memo fields ([#2222])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.1.11",
|
||||
"version": "1.1.12",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.1.11"
|
||||
version = "1.1.12"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -126,6 +126,8 @@ fn main() {
|
||||
vesting::queries::locked_coins,
|
||||
vesting::queries::original_vesting,
|
||||
vesting::queries::spendable_coins,
|
||||
vesting::queries::spendable_vested_coins,
|
||||
vesting::queries::spendable_reward_coins,
|
||||
vesting::queries::get_historical_vesting_staking_reward,
|
||||
vesting::queries::get_spendable_vested_coins,
|
||||
vesting::queries::get_spendable_reward_coins,
|
||||
|
||||
@@ -28,7 +28,7 @@ pub(crate) async fn locked_coins(
|
||||
)
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< locked coins = {}", display);
|
||||
log::info!("<<< locked coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,43 @@ pub(crate) async fn spendable_coins(
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable coins = {}", display);
|
||||
log::info!("<<< spendable coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn spendable_vested_coins(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query spendable vested coins");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let res = client
|
||||
.nyxd
|
||||
.get_spendable_vested_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable vested coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn spendable_reward_coins(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<DecCoin, BackendError> {
|
||||
log::info!(">>> Query spendable reward coins");
|
||||
let guard = state.read().await;
|
||||
let client = guard.current_client()?;
|
||||
|
||||
let res = client
|
||||
.nyxd
|
||||
.get_spendable_reward_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable reward coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -73,7 +109,7 @@ pub(crate) async fn vested_coins(
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< vested coins = {}", display);
|
||||
log::info!("<<< vested coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -96,7 +132,7 @@ pub(crate) async fn vesting_coins(
|
||||
.await?;
|
||||
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< vesting coins = {}", display);
|
||||
log::info!("<<< vesting coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -161,7 +197,7 @@ pub(crate) async fn get_historical_vesting_staking_reward(
|
||||
.get_historical_vesting_staking_reward(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< historical vesting staking reward coins = {}", display);
|
||||
log::info!("<<< historical vesting staking reward coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -178,7 +214,7 @@ pub(crate) async fn get_spendable_vested_coins(
|
||||
.get_spendable_vested_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable vested coins = {}", display);
|
||||
log::info!("<<< spendable vested coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -195,7 +231,7 @@ pub(crate) async fn get_spendable_reward_coins(
|
||||
.get_spendable_reward_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< spendable reward coins = {}", display);
|
||||
log::info!("<<< spendable reward coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -212,7 +248,7 @@ pub(crate) async fn get_delegated_coins(
|
||||
.get_delegated_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< delegated coins = {}", display);
|
||||
log::info!("<<< delegated coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -229,7 +265,7 @@ pub(crate) async fn get_pledged_coins(
|
||||
.get_pledged_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< pledged coins = {}", display);
|
||||
log::info!("<<< pledged coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -246,7 +282,7 @@ pub(crate) async fn get_staked_coins(
|
||||
.get_staked_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< staked coins = {}", display);
|
||||
log::info!("<<< staked coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
@@ -263,7 +299,7 @@ pub(crate) async fn get_withdrawn_coins(
|
||||
.get_withdrawn_coins(client.nyxd.address().as_ref())
|
||||
.await?;
|
||||
let display = guard.attempt_convert_to_display_dec_coin(res)?;
|
||||
log::info!("<<< pledged coins = {}", display);
|
||||
log::info!("<<< pledged coins = {display}");
|
||||
Ok(display)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.1.11"
|
||||
"version": "1.1.12"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
+26
-23
@@ -1,9 +1,9 @@
|
||||
/* eslint-disable react/no-array-index-key */
|
||||
import React, { useContext } from 'react';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box, Tooltip, Typography } from '@mui/material';
|
||||
import { Box, Stack, Tooltip, Typography } from '@mui/material';
|
||||
import { format } from 'date-fns';
|
||||
import { AppContext } from '../../../context';
|
||||
import { AppContext } from 'src/context';
|
||||
|
||||
const calculateMarkerPosition = (arrLength: number, index: number) => (1 / arrLength) * 100 * index;
|
||||
|
||||
@@ -30,30 +30,33 @@ export const VestingTimeline: FCWithChildren<{ percentageComplete: number }> = (
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" gap={1} position="relative" width="100%">
|
||||
<svg width="100%" height="12">
|
||||
<rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" />
|
||||
<rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill={theme.palette.success.main} />
|
||||
{vestingAccountInfo?.periods.map((period, i, arr) => (
|
||||
<Box>
|
||||
<Stack direction="row" gap={1} alignItems="center">
|
||||
<Typography variant="body2">{percentageComplete}%</Typography>
|
||||
<svg width="100%" height="12">
|
||||
<rect y="2" width="100%" height="6" rx="0" fill="#E6E6E6" />
|
||||
<rect y="2" width={`${percentageComplete}%`} height="6" rx="0" fill={theme.palette.success.main} />
|
||||
{vestingAccountInfo?.periods.map((period, i, arr) => (
|
||||
<Marker
|
||||
position={`${calculateMarkerPosition(arr.length, i)}%`}
|
||||
color={
|
||||
Math.ceil(+percentageComplete) >= calculateMarkerPosition(arr.length, i)
|
||||
? theme.palette.success.main
|
||||
: '#B9B9B9'
|
||||
}
|
||||
tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')}
|
||||
key={i}
|
||||
/>
|
||||
))}
|
||||
<Marker
|
||||
position={`${calculateMarkerPosition(arr.length, i)}%`}
|
||||
color={
|
||||
Math.ceil(+percentageComplete) >= calculateMarkerPosition(arr.length, i)
|
||||
? theme.palette.success.main
|
||||
: '#B9B9B9'
|
||||
}
|
||||
tooltipText={format(new Date(Number(period.start_time) * 1000), 'HH:mm do MMM yyyy')}
|
||||
key={i}
|
||||
position="calc(100% - 4px)"
|
||||
color={percentageComplete === 100 ? theme.palette.success.main : '#B9B9B9'}
|
||||
tooltipText="End of vesting schedule"
|
||||
/>
|
||||
))}
|
||||
<Marker
|
||||
position="calc(100% - 4px)"
|
||||
color={percentageComplete === 100 ? theme.palette.success.main : '#B9B9B9'}
|
||||
tooltipText="End of vesting schedule"
|
||||
/>
|
||||
</svg>
|
||||
</svg>
|
||||
</Stack>
|
||||
{!!nextPeriod && (
|
||||
<Typography variant="caption" sx={{ color: 'nym.text.muted', position: 'absolute', top: 15, left: 0 }}>
|
||||
<Typography variant="caption" sx={{ color: 'nym.text.muted', ml: 6 }}>
|
||||
Next vesting period: {format(new Date(nextPeriod * 1000), 'HH:mm do MMM yyyy')}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Card, Stack, Button } from '@mui/material';
|
||||
import { ModalListItem } from 'src/components/Modals/ModalListItem';
|
||||
|
||||
export const TokenTransfer = ({
|
||||
onTransfer,
|
||||
unlockedTokens,
|
||||
unlockedRewards,
|
||||
unlockedTransferable,
|
||||
}: {
|
||||
unlockedTokens?: string;
|
||||
unlockedRewards?: string;
|
||||
unlockedTransferable?: string;
|
||||
onTransfer: () => void;
|
||||
}) => (
|
||||
<Card variant="outlined" sx={{ p: 3, height: '100%' }}>
|
||||
<Stack justifyContent="space-between" sx={{ height: '100%' }}>
|
||||
<Stack gap={1} sx={{ mb: 2 }}>
|
||||
<ModalListItem label="Unlocked tokens" value={unlockedTokens} />
|
||||
<ModalListItem label="Unlocked rewards" value={unlockedRewards} divider />
|
||||
<ModalListItem fontSize={16} label="Transferable tokens" value={unlockedTransferable} fontWeight={600} />
|
||||
</Stack>
|
||||
<Button size="large" fullWidth variant="contained" onClick={onTransfer} disableElevation>
|
||||
Transfer
|
||||
</Button>
|
||||
</Stack>
|
||||
</Card>
|
||||
);
|
||||
@@ -0,0 +1,109 @@
|
||||
import React, { useContext, useState, useEffect } from 'react';
|
||||
import {
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
Typography,
|
||||
TableCellProps,
|
||||
Card,
|
||||
} from '@mui/material';
|
||||
import { Period } from '@nymproject/types';
|
||||
import { AppContext } from 'src/context';
|
||||
import { VestingTimeline } from '../VestingTimeline';
|
||||
|
||||
const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [
|
||||
{ title: 'Locked', align: 'left' },
|
||||
{ title: 'Period', align: 'left' },
|
||||
{ title: 'Unlocked', align: 'right' },
|
||||
];
|
||||
|
||||
const vestingPeriod = (current?: Period, original?: number) => {
|
||||
if (current === 'After') return 'Complete';
|
||||
|
||||
if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`;
|
||||
|
||||
return 'N/A';
|
||||
};
|
||||
|
||||
export const VestingSchedule = () => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const [vestedPercentage, setVestedPercentage] = useState(0);
|
||||
|
||||
const calculatePercentage = () => {
|
||||
const { tokenAllocation, originalVesting } = userBalance;
|
||||
if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) {
|
||||
const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100;
|
||||
const rounded = percentage.toFixed(2);
|
||||
setVestedPercentage(+rounded);
|
||||
} else {
|
||||
setVestedPercentage(0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
calculatePercentage();
|
||||
}, [userBalance.tokenAllocation, calculatePercentage]);
|
||||
|
||||
return (
|
||||
<Card variant="outlined" sx={{ p: 3, height: '100%' }}>
|
||||
<TableContainer sx={{ mb: 2 }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnsHeaders.map((header) => (
|
||||
<TableCell
|
||||
key={header.title}
|
||||
sx={{ color: 'nym.text.muted', pt: 0, border: 'none', pb: 0 }}
|
||||
align={header.align}
|
||||
>
|
||||
{header.title}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="left"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
}}
|
||||
>
|
||||
{vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
align="right"
|
||||
>
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<Typography variant="body2" sx={{ color: 'nym.text.muted', mb: 3 }}>
|
||||
Percentage
|
||||
</Typography>
|
||||
<VestingTimeline percentageComplete={vestedPercentage} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
+8
-23
@@ -1,6 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, CircularProgress } from '@mui/material';
|
||||
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';
|
||||
@@ -8,36 +7,22 @@ import { FeeWarning } from 'src/components/FeeWarning';
|
||||
import { withdrawVestedCoins } from 'src/requests';
|
||||
import { Console } from 'src/utils/console';
|
||||
import { simulateWithdrawVestedCoins } from 'src/requests/simulate';
|
||||
import { SuccessModal } from './TransferModalSuccess';
|
||||
import { TResponseState, TTransactionDetails } from '../types';
|
||||
import { useGetFee } from 'src/hooks/useGetFee';
|
||||
import { SuccessModal, TTransactionDetails } from './TransferModalSuccess';
|
||||
import { TResponseState } from '../../../pages/balance/types';
|
||||
|
||||
export const TransferModal = ({ onClose }: { onClose: () => void }) => {
|
||||
const [state, setState] = useState<TResponseState>();
|
||||
const [fee, setFee] = useState<FeeDetails>();
|
||||
|
||||
const [tx, setTx] = useState<TTransactionDetails>();
|
||||
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
const getFee = async () => {
|
||||
if (userBalance.tokenAllocation?.spendable && clientDetails?.display_mix_denom) {
|
||||
try {
|
||||
const simulatedFee = await simulateWithdrawVestedCoins({
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom },
|
||||
});
|
||||
setFee(simulatedFee);
|
||||
await userBalance.refreshBalances();
|
||||
} catch (e) {
|
||||
setFee({
|
||||
amount: { amount: 'n/a', denom: clientDetails?.display_mix_denom.toUpperCase() as CurrencyDenom },
|
||||
fee: { Auto: null },
|
||||
});
|
||||
Console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
const { fee, getFee } = useGetFee();
|
||||
|
||||
useEffect(() => {
|
||||
getFee();
|
||||
getFee(simulateWithdrawVestedCoins, {
|
||||
amount: { amount: userBalance.tokenAllocation?.spendable, denom: clientDetails?.display_mix_denom },
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleTransfer = async () => {
|
||||
+2
-1
@@ -2,7 +2,8 @@ import React from 'react';
|
||||
import { Stack, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { ConfirmationModal } from 'src/components';
|
||||
import { TTransactionDetails } from '../types';
|
||||
|
||||
export type TTransactionDetails = { amount: string; url: string };
|
||||
|
||||
export const SuccessModal = ({ tx, onClose }: { tx?: TTransactionDetails; onClose: () => void }) => (
|
||||
<ConfirmationModal
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { Chip, IconButton, TableCell, TableRow, Tooltip, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { decimalToPercentage, DelegationWithEverything } from '@nymproject/types';
|
||||
import { LockOutlined } from '@mui/icons-material';
|
||||
import { isDelegation } from 'src/context/delegations';
|
||||
import { toPercentIntegerString } from 'src/utils';
|
||||
import { format } from 'date-fns';
|
||||
@@ -34,9 +35,6 @@ export const DelegationItem = ({
|
||||
if (nodeIsUnbonded) {
|
||||
return 'This node has unbonded and it does not exist anymore. You need to undelegate from it to get your stake and outstanding rewards (if any) back.';
|
||||
}
|
||||
if (item.uses_vesting_contract_tokens) {
|
||||
return 'Delegation made with locked tockens';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
@@ -80,6 +78,13 @@ export const DelegationItem = ({
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell sx={{ textTransform: 'uppercase', color: 'inherit' }}>{getRewardValue(item)}</TableCell>
|
||||
<TableCell>
|
||||
{item.uses_vesting_contract_tokens && (
|
||||
<Tooltip title="Delegation uses locked tokens">
|
||||
<LockOutlined sx={{ color: 'grey.800' }} fontSize="small" />
|
||||
</Tooltip>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="right" sx={{ color: 'inherit' }}>
|
||||
{!item.pending_events.length && !nodeIsUnbonded && (
|
||||
<DelegationsActionsMenu
|
||||
|
||||
@@ -46,6 +46,7 @@ const headCells: HeadCell[] = [
|
||||
{ id: 'delegated_on_iso_datetime', label: 'Delegated on', sortable: true, align: 'left' },
|
||||
{ id: 'amount', label: 'Delegation', sortable: true, align: 'left' },
|
||||
{ id: 'unclaimed_rewards', label: 'Reward', sortable: true, align: 'left' },
|
||||
{ id: 'uses_locked_tokens', label: '', sortable: false, align: 'left' },
|
||||
];
|
||||
|
||||
const EnhancedTableHead: FCWithChildren<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => {
|
||||
|
||||
@@ -7,17 +7,22 @@ export const ModalListItem: FCWithChildren<{
|
||||
divider?: boolean;
|
||||
hidden?: boolean;
|
||||
fontWeight?: TypographyProps['fontWeight'];
|
||||
fontSize?: TypographyProps['fontSize'];
|
||||
light?: boolean;
|
||||
value?: React.ReactNode;
|
||||
sxValue?: SxProps;
|
||||
}> = ({ label, value, hidden, fontWeight, divider, sxValue }) => (
|
||||
}> = ({ label, value, hidden, fontWeight, fontSize, divider, sxValue }) => (
|
||||
<Box sx={{ display: hidden ? 'none' : 'block' }}>
|
||||
<Stack direction="row" justifyContent="space-between">
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
{value && (
|
||||
<Typography fontSize="smaller" fontWeight={fontWeight} sx={{ color: 'text.primary', fontSize: 14, ...sxValue }}>
|
||||
<Typography
|
||||
fontSize="smaller"
|
||||
fontWeight={fontWeight}
|
||||
sx={{ color: 'text.primary', fontSize: fontSize || 14, ...sxValue }}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@@ -9,11 +9,19 @@ import {
|
||||
getOriginalVesting,
|
||||
getCurrentVestingPeriod,
|
||||
getVestingAccountInfo,
|
||||
getSpendableRewardCoins,
|
||||
getSpendableVestedCoins,
|
||||
} from '../requests';
|
||||
import { Console } from '../utils/console';
|
||||
|
||||
type TTokenAllocation = {
|
||||
[key in 'vesting' | 'vested' | 'locked' | 'spendable']: DecCoin['amount'];
|
||||
[key in
|
||||
| 'vesting'
|
||||
| 'vested'
|
||||
| 'locked'
|
||||
| 'spendable'
|
||||
| 'spendableRewardCoins'
|
||||
| 'spendableVestedCoins']: DecCoin['amount'];
|
||||
};
|
||||
|
||||
export type TUseuserBalance = {
|
||||
@@ -54,6 +62,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
vestedCoins,
|
||||
lockedCoins,
|
||||
spendableCoins,
|
||||
spendableVestedCoins,
|
||||
spendableRewardCoins,
|
||||
currentPeriod,
|
||||
vestingAccountDetail,
|
||||
] = await Promise.all([
|
||||
@@ -62,6 +72,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
getVestedCoins(clientDetails?.client_address),
|
||||
getLockedCoins(),
|
||||
getSpendableCoins(),
|
||||
getSpendableVestedCoins(),
|
||||
getSpendableRewardCoins(),
|
||||
getCurrentVestingPeriod(clientDetails?.client_address),
|
||||
getVestingAccountInfo(clientDetails?.client_address),
|
||||
]);
|
||||
@@ -72,6 +84,8 @@ export const useGetBalance = (clientDetails?: Account): TUseuserBalance => {
|
||||
vested: vestedCoins.amount,
|
||||
locked: lockedCoins.amount,
|
||||
spendable: spendableCoins.amount,
|
||||
spendableVestedCoins: spendableVestedCoins.amount,
|
||||
spendableRewardCoins: spendableRewardCoins.amount,
|
||||
});
|
||||
setVestingAccountInfo(vestingAccountDetail);
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
import { Alert, Grid, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { Network } from 'src/types';
|
||||
import { Balance } from '@nymproject/types';
|
||||
import { NymCard, ClientAddress } from '../../components';
|
||||
import { urls } from '../../context/main';
|
||||
|
||||
export const BalanceCard = ({
|
||||
userBalance,
|
||||
userBalanceError,
|
||||
network,
|
||||
clientAddress,
|
||||
}: {
|
||||
userBalance?: Balance;
|
||||
userBalanceError?: string;
|
||||
network?: Network;
|
||||
clientAddress?: string;
|
||||
}) => (
|
||||
<NymCard title="Balance" data-testid="check-balance" borderless Action={<ClientAddress withCopy showEntireAddress />}>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
{userBalanceError && (
|
||||
<Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}>
|
||||
{userBalanceError}
|
||||
</Alert>
|
||||
)}
|
||||
{!userBalanceError && (
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '600',
|
||||
fontSize: 28,
|
||||
}}
|
||||
variant="h5"
|
||||
>
|
||||
{userBalance?.printable_balance}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
{network && (
|
||||
<Grid item>
|
||||
<Link
|
||||
href={`${urls(network).mixnetExplorer}/account/${clientAddress}`}
|
||||
target="_blank"
|
||||
text="Last transactions"
|
||||
fontSize={14}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</NymCard>
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { Refresh } from '@mui/icons-material';
|
||||
import { Grid, IconButton, Typography } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { NymCard } from 'src/components';
|
||||
import { TokenTransfer } from 'src/components/Balance/cards/TokenTransfer';
|
||||
import { OriginalVestingResponse } from '@nymproject/types';
|
||||
import { VestingSchedule } from 'src/components/Balance/cards/VestingSchedule';
|
||||
|
||||
export const VestingCard = ({
|
||||
unlockedTokens,
|
||||
unlockedRewards,
|
||||
unlockedTransferable,
|
||||
originalVesting,
|
||||
onTransfer,
|
||||
fetchBalance,
|
||||
fetchTokenAllocation,
|
||||
}: {
|
||||
unlockedTokens?: string;
|
||||
unlockedRewards?: string;
|
||||
unlockedTransferable?: string;
|
||||
originalVesting?: OriginalVestingResponse;
|
||||
fetchTokenAllocation: () => Promise<void>;
|
||||
fetchBalance: () => Promise<void>;
|
||||
onTransfer: () => Promise<void>;
|
||||
}) => {
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const refreshBalances = async () => {
|
||||
await fetchBalance();
|
||||
await fetchTokenAllocation();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
closeSnackbar();
|
||||
fetchTokenAllocation();
|
||||
}, []);
|
||||
|
||||
if (!originalVesting) return null;
|
||||
|
||||
return (
|
||||
<NymCard
|
||||
title="Vesting Schedule"
|
||||
subheader={
|
||||
<Typography variant="caption" sx={{ color: 'nym.text.muted' }}>
|
||||
You can use up to 10% of your locked tokens for bonding and delegating
|
||||
</Typography>
|
||||
}
|
||||
borderless
|
||||
data-testid="check-unvested-tokens"
|
||||
Action={
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
await refreshBalances();
|
||||
enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true });
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12} xl={8}>
|
||||
<VestingSchedule />
|
||||
</Grid>
|
||||
<Grid item xs={12} xl={4}>
|
||||
<TokenTransfer
|
||||
onTransfer={onTransfer}
|
||||
unlockedTokens={unlockedTokens}
|
||||
unlockedRewards={unlockedRewards}
|
||||
unlockedTransferable={unlockedTransferable}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</NymCard>
|
||||
);
|
||||
};
|
||||
@@ -1,56 +0,0 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Alert, Grid, Typography } from '@mui/material';
|
||||
import { Link } from '@nymproject/react/link/Link';
|
||||
import { NymCard, ClientAddress } from '../../components';
|
||||
import { AppContext, urls } from '../../context/main';
|
||||
|
||||
export const BalanceCard = () => {
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
userBalance.fetchBalance();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<NymCard
|
||||
title="Balance"
|
||||
data-testid="check-balance"
|
||||
borderless
|
||||
Action={<ClientAddress withCopy showEntireAddress />}
|
||||
>
|
||||
<Grid container direction="column" spacing={2}>
|
||||
<Grid item>
|
||||
{userBalance.error && (
|
||||
<Alert severity="error" data-testid="error-refresh" sx={{ p: 2 }}>
|
||||
{userBalance.error}
|
||||
</Alert>
|
||||
)}
|
||||
{!userBalance.error && (
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '600',
|
||||
fontSize: 28,
|
||||
}}
|
||||
variant="h5"
|
||||
>
|
||||
{userBalance.balance?.printable_balance}
|
||||
</Typography>
|
||||
)}
|
||||
</Grid>
|
||||
{network && (
|
||||
<Grid item>
|
||||
<Link
|
||||
href={`${urls(network).mixnetExplorer}/account/${clientDetails?.client_address}`}
|
||||
target="_blank"
|
||||
text="Last transactions"
|
||||
fontSize={14}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</NymCard>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,46 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { AppContext } from '../../context/main';
|
||||
|
||||
import { BalanceCard } from './balance';
|
||||
import { VestingCard } from './vesting';
|
||||
import { BalanceCard } from './Balance';
|
||||
import { VestingCard } from './Vesting';
|
||||
import { PageLayout } from '../../layouts';
|
||||
import { TransferModal } from './components/TransferModal';
|
||||
import { TransferModal } from '../../components/Balance/modals/TransferModal';
|
||||
|
||||
export const Balance = () => {
|
||||
const [showTransferModal, setShowTransferModal] = useState(false);
|
||||
|
||||
const { userBalance } = useContext(AppContext);
|
||||
const { userBalance, clientDetails, network } = useContext(AppContext);
|
||||
|
||||
useEffect(() => {
|
||||
userBalance.fetchBalance();
|
||||
}, []);
|
||||
|
||||
const handleShowTransferModal = async () => {
|
||||
await userBalance.refreshBalances();
|
||||
setShowTransferModal(true);
|
||||
};
|
||||
|
||||
const appendDenom = (value: string = '') => `${value} ${clientDetails?.display_mix_denom.toUpperCase()}`;
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<Box display="flex" flexDirection="column" gap={4}>
|
||||
<BalanceCard />
|
||||
<VestingCard onTransfer={handleShowTransferModal} />
|
||||
<BalanceCard
|
||||
userBalance={userBalance.balance}
|
||||
userBalanceError={userBalance.error}
|
||||
clientAddress={clientDetails?.client_address}
|
||||
network={network}
|
||||
/>
|
||||
<VestingCard
|
||||
unlockedTokens={appendDenom(userBalance.tokenAllocation?.spendableVestedCoins)}
|
||||
unlockedRewards={appendDenom(userBalance.tokenAllocation?.spendableRewardCoins)}
|
||||
unlockedTransferable={appendDenom(userBalance.tokenAllocation?.spendable)}
|
||||
originalVesting={userBalance.originalVesting}
|
||||
onTransfer={handleShowTransferModal}
|
||||
fetchBalance={userBalance.fetchBalance}
|
||||
fetchTokenAllocation={userBalance.fetchTokenAllocation}
|
||||
/>
|
||||
{showTransferModal && <TransferModal onClose={() => setShowTransferModal(false)} />}
|
||||
</Box>
|
||||
</PageLayout>
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export type TResponseState = 'loading' | 'success' | 'fail';
|
||||
export type TTransactionDetails = { amount: string; url: string };
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Refresh } from '@mui/icons-material';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellProps,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { NymCard } from 'src/components';
|
||||
import { AppContext } from 'src/context/main';
|
||||
import { Period } from 'src/types';
|
||||
import { VestingTimeline } from './components/vesting-timeline';
|
||||
|
||||
const columnsHeaders: Array<{ title: string; align: TableCellProps['align'] }> = [
|
||||
{ title: 'Locked', align: 'left' },
|
||||
{ title: 'Period', align: 'left' },
|
||||
{ title: 'Percentage Vested', align: 'left' },
|
||||
{ title: 'Unlocked', align: 'right' },
|
||||
];
|
||||
|
||||
const vestingPeriod = (current?: Period, original?: number) => {
|
||||
if (current === 'After') return 'Complete';
|
||||
|
||||
if (typeof current === 'object' && typeof original === 'number') return `${current.In + 1}/${original}`;
|
||||
|
||||
return 'N/A';
|
||||
};
|
||||
|
||||
const VestingSchedule = () => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
const [vestedPercentage, setVestedPercentage] = useState(0);
|
||||
|
||||
const calculatePercentage = () => {
|
||||
const { tokenAllocation, originalVesting } = userBalance;
|
||||
if (tokenAllocation?.vesting && tokenAllocation.vested && tokenAllocation.vested !== '0' && originalVesting) {
|
||||
const percentage = (+tokenAllocation.vested / +originalVesting.amount.amount) * 100;
|
||||
const rounded = percentage.toFixed(2);
|
||||
setVestedPercentage(+rounded);
|
||||
} else {
|
||||
setVestedPercentage(0);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
calculatePercentage();
|
||||
}, [userBalance.tokenAllocation, calculatePercentage]);
|
||||
|
||||
return (
|
||||
<TableContainer sx={{ py: 1 }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnsHeaders.map((header) => (
|
||||
<TableCell key={header.title} sx={{ color: (t) => t.palette.nym.text.muted }} align={header.align}>
|
||||
{header.title}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
{userBalance.tokenAllocation?.vesting || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="left"
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
}}
|
||||
>
|
||||
{vestingPeriod(userBalance.currentVestingPeriod, userBalance.originalVesting?.number_of_periods)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Typography variant="body2">{`${vestedPercentage}%`}</Typography>
|
||||
<VestingTimeline percentageComplete={vestedPercentage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
color: 'text.primary',
|
||||
borderBottom: 'none',
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
align="right"
|
||||
>
|
||||
{userBalance.tokenAllocation?.vested || 'n/a'} / {userBalance.originalVesting?.amount.amount}{' '}
|
||||
{clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const TokenTransfer = () => {
|
||||
const { userBalance, clientDetails } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Box sx={{ my: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 3, fontWeight: '600' }}>
|
||||
Unlocked transferable tokens
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
data-testid="refresh-success"
|
||||
sx={{ color: 'text.primary', fontWeight: '600', fontSize: 28 }}
|
||||
variant="h5"
|
||||
textTransform="uppercase"
|
||||
>
|
||||
{userBalance.tokenAllocation?.spendable || 'n/a'} {clientDetails?.display_mix_denom.toUpperCase()}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const VestingCard = ({ onTransfer }: { onTransfer: () => Promise<void> }) => {
|
||||
const { userBalance } = useContext(AppContext);
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
|
||||
|
||||
const refreshBalances = async () => {
|
||||
await userBalance.fetchBalance();
|
||||
await userBalance.fetchTokenAllocation();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
closeSnackbar();
|
||||
userBalance.fetchTokenAllocation();
|
||||
}, []);
|
||||
|
||||
if (!userBalance.originalVesting) return null;
|
||||
|
||||
return (
|
||||
<NymCard
|
||||
title="Vesting Schedule"
|
||||
subheader={
|
||||
<Typography variant="caption" sx={{ color: 'nym.text.muted' }}>
|
||||
You can use up to 10% of your locked tokens for bonding and delegating
|
||||
</Typography>
|
||||
}
|
||||
borderless
|
||||
data-testid="check-unvested-tokens"
|
||||
Action={
|
||||
<IconButton
|
||||
onClick={async () => {
|
||||
await refreshBalances();
|
||||
enqueueSnackbar('Balances updated', { variant: 'success', preventDuplicate: true });
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</IconButton>
|
||||
}
|
||||
>
|
||||
<VestingSchedule />
|
||||
<TokenTransfer />
|
||||
<Box display="flex" justifyContent="end" alignItems="center">
|
||||
<Button size="large" variant="contained" onClick={onTransfer} disableElevation>
|
||||
Transfer
|
||||
</Button>
|
||||
</Box>
|
||||
</NymCard>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,10 @@ export const getLockedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoi
|
||||
|
||||
export const getSpendableCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_coins');
|
||||
|
||||
export const getSpendableVestedCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_vested_coins');
|
||||
|
||||
export const getSpendableRewardCoins = async (): Promise<DecCoin> => invokeWrapper<DecCoin>('spendable_reward_coins');
|
||||
|
||||
export const getVestingCoins = async (vestingAccountAddress: string): Promise<DecCoin> =>
|
||||
invokeWrapper<DecCoin>('vesting_coins', { vestingAccountAddress });
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.65"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.12"
|
||||
version = "1.1.13"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"run:caddy": "caddy run",
|
||||
"build": "tsc --noEmit false",
|
||||
"watch": "tsc --noEmit false -w",
|
||||
"lint": "eslint src .storybook",
|
||||
"lint:fix": "eslint src .storybook --fix"
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user